Ansible playbook, difficults with with_items multiples vars - yaml

I use ansible to configure a server. I have to see if some line exist and if I have to change them so I use lineinfile module. To have a code more clean I would use with_items like this:
- name: Postegresql configuration
lineinfile: dest={{ item.dest }} line={{ item.line }} regexp={{ item.regexp }}
with_items:
- { dest: '/var/lib/pgsql/data/postgresql.conf', line: 'port = 5433', regexp: '^port =' }
- { dest: '/var/lib/pgsql/data/postgresql.conf', line: 'log_truncate_on_rotation = on', regexp: '^log_truncate_on_rotation =' }
- { dest: '/var/lib/pgsql/data/postgresql.conf', line: 'log_rotation_age = 1d', regexp: '^log_rotation_age =' }
- { dest: '/var/lib/pgsql/data/postgresql.conf', line: 'log_rotation_size = 0MB', regexp: '^log_rotation_size =' }
notify: restart postgresql
tags: verif
But it doesn't work. I he=ave this error:
a duplicate parameter was found in the argument string ()
I this it's a syntax error, can you help me ?

Try putting quotes around any inserted variables that you use. This will ensure that they are treated as a whole together and get parsed as you intend.
lineinfile: dest="{{ item.dest }}" line="{{ item.line }}" regexp="{{ item.regexp }}"

Related

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

Ansible write result command to local file with loop

I've write ansible-playbook to collect the result from many network devices. Below playbook is working fine. But if I have to collect result with lot of commands. Let say 20 commands, I've to create the many task to write the results into file in my playbook.
For now, I manually create the tasks to write to logs into file. Below is example with 3 commands.
- name: run multiple commands and evaluate the output
hosts: <<network-host>>
gather_facts: no
connection: local
vars:
datetime: "{{ lookup('pipe', 'date +%Y%m%d%H') }}"
backup_dir: "/backup/"
cli:
host: "{{ ansible_host }}"
username: <<username>>
password: <<password>>
tasks:
- sros_command:
commands:
- show version
- show system information
- show port
provider: "{{ cli }}"
register: result
- name: Writing output
local_action:
module: lineinfile
dest: "{{ backup_dir }}/{{ inventory_hostname }}-{{ datetime }}.txt"
line: "{{ inventory_hostname }}:# show version\n{{ result.stdout[0] }}"
create: yes
changed_when: False
- name: Writing output
local_action:
module: lineinfile
dest: "{{ backup_dir }}/{{ inventory_hostname }}-{{ datetime }}.txt"
line: "{{ inventory_hostname }}:# show system information\n{{ result.stdout[1] }}"
create: yes
changed_when: False
- name: Writing output
local_action:
module: lineinfile
dest: "{{ backup_dir }}/{{ inventory_hostname }}-{{ datetime }}.txt"
line: "{{ inventory_hostname }}:# show port\n{{ cmd_result.stdout[2] }}"
create: yes
changed_when: False
Is it possible to loop commands and result within one task?
Please kindly advice.
Thanks
try this one task alone in place above three tasks..
- name: Writing output
local_action:
module: lineinfile
dest: "{{ backup_dir }}/{{ inventory_hostname }}-{{ datetime }}.txt"
line: "{{ inventory_hostname }}:# show {{ item.command }}\n{{ cmd_result.stdout{{ item.outnum }} }}"
create: yes
changed_when: False
with_items:
- { command: version, outnum: [0] }
- { command: system information, outnum: [1] }
- { command: port, outnum: [2] }
Below playbook is worked for me
- name: Writing output
local_action:
module: lineinfile
dest: "{{ backup_dir }}/{{ inventory_hostname }}-{{ datetime }}.txt"
line: "{{ inventory_hostname }}:# show {{ item.command }}\n{{ item.cmdoutput}}"
create: yes
changed_when: False
with_items:
- { command: "version", cmdoutput: "{{ cmd_result.stdout[0] }}" }
- { command: "system information", cmdoutput: "{{ cmd_result.stdout[1] }}" }
- { command: "port", cmdoutput: "{{ cmd_result.stdout[2] }}" }

Unable to add string between variables in Ansible lineinfile module

I want my filedet.yaml to look like
10.9.75.78: /app/tmp/tmp.log, /vars/tmp/test.out
10.9.55.74: /app/tmp/tmp1.log, /vars/tmp/admin.out
The below works fine and logs the data correctly but when i add ': ' the syntax breaks and I get error
- name: Logging the deployment's file details to a Ansible variable file
local_action: lineinfile line={{ inventory_hostname }}': '{{ vars['fdetails_' + Layer].results|map(attribute='stdout')|list }} path={{ playbook_dir }}/vars/filedets.yaml
Output Error:
The offending line appears to be:
local_action: lineinfile line={{ inventory_hostname }}': '{{ > vars['fdetails_' + Layer].results|map(attribute='stdout')|list > }} path={{ playbook_dir }}/vars/filedets.yaml
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
I also tried this code but it too fails with syntax error:
line="{{ inventory_hostname }}': '{{ vars['fdetails_' + Layer].results|map(attribute='stdout')|list }}" path="{{ playbook_dir }}/vars/filedets.yaml"
Can you please suggest how can I inject the colons and space ': ' between the the variable in line ?
Just wrap the strings you want to insert between the variables in {{ }}
line="{{ inventory_hostname }}{{': '}}{{ vars['fdetails_' + Layer].results|map(attribute='stdout')|list }}" path="{{ playbook_dir }}/vars/filedets.yaml"
If the : colon is a problem you can mask it by using:
line="{{ inventory_hostname }}{{'%c '%58}}{{ vars['fdetails_' + Layer].results|map(attribute='stdout')|list }}" path="{{ playbook_dir }}/vars/filedets.yaml"
58 is the ASCII Code of :.

Modify a line and append if needed

Using Ansible i want to modify an existing configuration file and change a specific setting (variable) depending on one or more vars that i specify in a customer specific playbook.
The configuration file contains:
JVM_SUPPORT_RECOMMENDED_ARGS=""
The following option should always be added;
-Datlassian.plugins.enable.wait=300
Thus resulting in:
JVM_SUPPORT_RECOMMENDED_ARGS="-Datlassian.plugins.enable.wait=300"
However, I have also some optional options to set, like when proxy settings are required;
-Datlassian.plugins.enable.wait=300 -Dhttp.proxyHost=proxy.xxx.com -Dhttp.proxyPort=3128 -Dhttps.proxyHost=proxy.xxx.com -Dhttps.proxyPort=3128 -Dhttp.nonProxyHosts='localhost'"
Or when we need to disable some sort of SSL Endpoint Identification:
JVM_SUPPORT_RECOMMENDED_ARGS="-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true"
Some customers do not require the proxy or endpoint setting, some require both and some only one of these.
I tried to provide 'as-is' lines so it will just replace the entire thing, but this is not neat and is not desired either as there can be multiple combinations and I don't want to program all of them on beforehand. I rather have the variable build by adding the options I need.
lineinfile:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS="'
line: "JVM_SUPPORT_RECOMMENDED_ARGS=\"-Datlassian.plugins.enable.wait=300\""
when: file.stat.exists
- name: Ensure JVM_SUPPORT_RECOMMENDED_ARGS proxy settings are present
lineinfile:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS="'
line: "JVM_SUPPORT_RECOMMENDED_ARGS=\"-Datlassian.plugins.enable.wait=300
-Dhttp.proxyHost={{ jira_http_proxy }} -Dhttp.proxyPort={{ jira_http_proxyport }} -Dhttps.proxyHost={{ jira_https_proxy }}
-Dhttps.proxyPort={{ jira_https_proxyport }} -Dhttp.nonProxyHosts='{{ jira_non_proxy_hosts|default([])|join('|') }}'\""
when: file.stat.exists and (jira_http_proxy|default(false) or jira_https_proxy|default(false))
edit
I can do it like this;
jira_base_args: "-Datlassian.plugins.enable.wait=300"
jira_proxy_args: "-Dhttp.proxyHost={{ jira_http_proxy }} -Dhttp.proxyPort={{ jira_http_proxyport }}
-Dhttps.proxyHost={{ jira_https_proxy }} -Dhttps.proxyPort={{ jira_https_proxyport }}
-Dhttp.nonProxyHosts='{{ jira_non_proxy_hosts|default([])|join('|') }}'"
jira_ldap_args: "-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true"
And in the Playbook role;
replace:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS=(.*)'
replace: "JVM_SUPPORT_RECOMMENDED_ARGS=\"{{ jira_base_args }}\""
when: file.stat.exists
- name: Ensure JVM_SUPPORT_RECOMMENDED_ARGS proxy settings are present
replace:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS=(.*)'
replace: "JVM_SUPPORT_RECOMMENDED_ARGS=\"{{ jira_base_args }} {{ jira_proxy_args }}\""
when: file.stat.exists and (jira_http_proxy|default(false) or jira_https_proxy|default(false))
- name: Ensure JVM_SUPPORT_RECOMMENDED_ARGS ldap settings are present
replace:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS=(.*)'
replace: "JVM_SUPPORT_RECOMMENDED_ARGS=\"{{ jira_base_args }} {{ jira_ldap_args }}\""
when: file.stat.exists and (jira_disable_endpoint_ident|default(false))
But how to handle situations where both proxy and ldap settings are needed? Because the latter is now overwriting the proxy ones.
edit
Fixed it like this, not very nice but who has something better?
# We know that the default plugin timeout for JIRA is too low in most cases.
- name: Set plugin timeout to 300s
replace:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS=(.*)'
replace: "JVM_SUPPORT_RECOMMENDED_ARGS=\"{{ jira_base_args }}\""
when:
- file.stat.exists
- not jira_http_proxy|default(false)
- not jira_https_proxy|default(false)
- not jira_disable_endpoint_ident|default(false)
- name: Ensure JVM_SUPPORT_RECOMMENDED_ARGS proxy settings are present
replace:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS=(.*)'
replace: "JVM_SUPPORT_RECOMMENDED_ARGS=\"{{ jira_base_args }} {{ jira_proxy_args }}\""
when:
- file.stat.exists
- jira_http_proxy|default(false)
- jira_https_proxy|default(false)
- not jira_disable_endpoint_ident|default(false)
- name: Ensure JVM_SUPPORT_RECOMMENDED_ARGS ldap settings are present
replace:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS=(.*)'
replace: "JVM_SUPPORT_RECOMMENDED_ARGS=\"{{ jira_base_args }} {{ jira_ldap_args }}\""
when:
- file.stat.exists
- not jira_http_proxy|default(false)
- not jira_https_proxy|default(false)
- jira_disable_endpoint_ident|default(false)
- name: Ensure JVM_SUPPORT_RECOMMENDED_ARGS proxy + ldap settings are present
replace:
name: "{{ file.stat.path }}"
regexp: '^JVM_SUPPORT_RECOMMENDED_ARGS=(.*)'
replace: "JVM_SUPPORT_RECOMMENDED_ARGS=\"{{ jira_base_args }} {{ jira_proxy_args }} {{ jira_ldap_args }}\""
when:
- file.stat.exists
- jira_http_proxy|default(false)
- jira_https_proxy|default(false)
- jira_disable_endpoint_ident|default(false)
You can leverage insertbefore and backrefs:yes in lineinfile module.
Example:
- name: Set Atlassian enable.wait to 300s
lineinfile:
path: file.txt
regexp: '^(JVM_SUPPORT_RECOMMENDED_ARGS=".*)"$'
insertbefore: '"$'
line: '\1 -Datlassian.plugins.enable.wait=300"'
state: present
backrefs: yes

Ansible loops in lineinfile

Commenting out multiple lines should work with a standard loop [1] like this:
- name: "Allow /srv folder accessed by default. Just comment out the lines to allow."
lineinfile: dest=/etc/apache2/apache2.conf regexp={{ item.regexp }} line={{ item.line }} state=present
with_items:
- { regexp: '#<Directory /srv/>', line: '<Directory /srv/>' }
But I got an error:
failed: [192.168.101.101] => (item={'regexp': '#<Directory /srv/>', 'line': '<Directory /srv/>'}) => {"failed": true, "item": {"line": "<Directory /srv/>", "regexp": "#<Directory /srv/>"}}
msg: this module requires key=value arguments (['dest=/etc/apache2/apache2.conf', 'regexp=#<Directory', '/srv/>', 'line=<Directory', '/srv/>', 'state=present'])
FATAL: all hosts have already failed -- aborting
So how to get this working with multiple lines/items?
[1] http://docs.ansible.com/playbooks_loops.html#standard-loops
Thank you, tedder42! You we're more than right.
To be idempotent, the lineinfile task needs to match both the commented and uncommented state of the line so we start it: ^#?
So the fully functioning play set out to be:
- name: "Allow /srv folder accessed by default. Comment out the lines to allow. "
lineinfile:
dest=/etc/apache2/apache2.conf
regexp="{{ item.regexp }}"
line="{{ item.line }}"
state=present
with_items:
- { regexp: '^#?<Directory /srv/>', line: '<Directory /srv/>' }
- { regexp: '^#?\tOptions Indexes FollowSymLinks', line: '\tOptions Indexes FollowSymLinks' }
- { regexp: '^#?\tAllowOverride None', line: '\tAllowOverride None' }
- { regexp: '^#?\tRequire all granted', line: '\tRequire all granted' }
- { regexp: '^#?</Directory>', line: '</Directory>'}
This is actually not a good idea. Definitely better is use the copy with backup=yes.
You were really close to having it working. Simply add quotes around the regexp and line.
lineinfile: dest=/etc/apache2/apache2.conf regexp="{{ item.regexp }}" line="{{ item.line }}" state=present
I wasn't entirely sure, but the error message implied there were problems with seeing the regexp and line args, so I tried a few things.
As a reminder, lineinfile is somewhat of an antipattern. When you find yourself using it, that's a sign you should consider switching to copy or template.

Resources