I am trying to automate a config entry modification. I have AIX servers which have a file login.cfg and there is a line on which available shells are configured. It is like this:
usw:
shells = /bin/sh,/bin/bsh,/bin/csh,/bin/ksh,/bin/tsh,/bin/ksh93,/usr/bin/sh,/usr/bin/bsh,/usr/bin/csh,/usr/bin/ksh,/usr/bin/tsh,/usr/bin/ksh93,/usr/bin/rksh,/usr/bin/rksh93,/usr/sbin/uucp/uucico,/usr/sbin/snappd,/usr/sbin/sliplogin
maxlogins = 32767
logintimeout = 60
My goal is to append ,/usr/bin/bash to the end of the shells line, when it is not already present. The order of shells is not uniform among the hosts, for a reason.
My attempt to achieve this modification is like this snippet below - for testing purposes I just edit login.cfg locally. It has two steps: first testing if bash is already present and then edit the line if not.
---
- name: test adding /bin/bash to the end of the line IF NOT THERE
hosts: localhost
tasks:
- name: Check if bash is already there
shell: "grep -E 'shells = .*/usr/bin/bash' {{ playbook_dir }}/login.cfg"
ignore_errors: yes
register: result
- name: add entry if not there
lineinfile:
backrefs: yes
line: '\g<list>,/usr/bin/bash'
path: "{{ playbook_dir }}/login.cfg"
regexp: "(?P<list> +shells =.*)"
when: result.rc != 0
My question is if there is a method to avoid the testing using shell: module. Is there any more Ansible-ish way to do the same?
Let's parse the content of the file first. For example, if you replace the equal signs with colons you get YAML
_regex: '\s*='
_replace: ':'
login_dict: "{{ lookup('file', 'login.cfg') |
regex_replace(_regex, _replace) |
from_yaml }}"
gives
login_dict:
usw:
logintimeout: 60
maxlogins: 32767
shells: /bin/sh,/bin/bsh,/bin/csh,/bin/ksh,/bin/tsh,/bin/ksh93,/usr/bin/sh,/usr/bin/bsh,/usr/bin/csh,/usr/bin/ksh,/usr/bin/tsh,/usr/bin/ksh93,/usr/bin/rksh,/usr/bin/rksh93,/usr/sbin/uucp/uucico,/usr/sbin/snappd,/usr/sbin/sliplogin
Split the shells
shells: "{{ login_dict.usw.shells | split(',') }}"
gives
shells:
- /bin/sh
- /bin/bsh
- /bin/csh
- /bin/ksh
- /bin/tsh
- /bin/ksh93
- /usr/bin/sh
- /usr/bin/bsh
- /usr/bin/csh
- /usr/bin/ksh
- /usr/bin/tsh
- /usr/bin/ksh93
- /usr/bin/rksh
- /usr/bin/rksh93
- /usr/sbin/uucp/uucico
- /usr/sbin/snappd
- /usr/sbin/sliplogin
and, as required, "append /usr/bin/bash to the end of the shells line, when it is not already present"
shells_append: /usr/bin/bash
shells_update: "{{ (shells_append in shells) |
ternary(shells, shells + [shells_append]) |
join(',') }}"
Use shells_update in the module lineinfile
- name: add entry if not there
lineinfile:
backrefs: true
path: login.cfg
regexp: '^(\s*shells)\s*=.*$'
line: '\1 = {{ shells_update }}'
Optionally, you can update the dictionary
usw_update:
usw:
shells: "{{ shells_update }}"
login_update: "{{ login_dict | combine(usw_update, recursive='true') }}"
gives
login_update:
usw:
logintimeout: 60
maxlogins: 32767
shells: /bin/sh,/bin/bsh,/bin/csh,/bin/ksh,/bin/tsh,/bin/ksh93,/usr/bin/sh,/usr/bin/bsh,/usr/bin/csh,/usr/bin/ksh,/usr/bin/tsh,/usr/bin/ksh93,/usr/bin/rksh,/usr/bin/rksh93,/usr/sbin/uucp/uucico,/usr/sbin/snappd,/usr/sbin/sliplogin,/usr/bin/bash
and create a template
- debug:
msg: |
{% for section,conf in login_update.items() %}
{{ section }}:
{% for k,v in conf.items() %}
{{ k }} = {{ v }}
{% endfor %}
{% endfor %}
gives
msg: |-
usw:
shells = /bin/sh,/bin/bsh,/bin/csh,/bin/ksh,/bin/tsh,/bin/ksh93,/usr/bin/sh,/usr/bin/bsh,/usr/bin/csh,/usr/bin/ksh,/usr/bin/tsh,/usr/bin/ksh93,/usr/bin/rksh,/usr/bin/rksh93,/usr/sbin/uucp/uucico,/usr/sbin/snappd,/usr/sbin/sliplogin,/usr/bin/bash
maxlogins = 32767
logintimeout = 60
Copy the content to the file
- name: copy content
copy:
dest: login.cfg
content: |
{% for section,conf in login_update.items() %}
{{ section }}:
{% for k,v in conf.items() %}
{{ k }} = {{ v }}
{% endfor %}
{% endfor %}
Both tasks are idempotent and give the same result. There might be a systemic option on how to parse this format in AIX.
Example of a complete playbook for testing
- hosts: localhost
vars:
_regex: '\s*='
_replace: ':'
login_dict: "{{ lookup('file', 'login.cfg') |
regex_replace(_regex, _replace) |
from_yaml }}"
shells: "{{ login_dict.usw.shells | split(',') }}"
shells_append: /usr/bin/bash
shells_update: "{{ (shells_append in shells) |
ternary(shells, shells + [shells_append]) |
join(',') }}"
usw_update:
usw:
shells: "{{ shells_update }}"
login_update: "{{ login_dict | combine(usw_update, recursive='true') }}"
tasks:
- debug:
var: login_dict
- debug:
var: shells
- debug:
var: shells_update
- name: add entry if not there
lineinfile:
backrefs: true
path: login.cfg
regexp: '^(\s*shells)\s*=.*$'
line: '\1 = {{ shells_update }}'
when: lineinfile | d(false) | bool
- debug:
var: usw_update
- debug:
var: login_update
- debug:
msg: |
{% for section,conf in login_update.items() %}
{{ section }}:
{% for k,v in conf.items() %}
{{ k }} = {{ v }}
{% endfor %}
{% endfor %}
- name: copy content
copy:
dest: login.cfg
content: |
{% for section,conf in login_update.items() %}
{{ section }}:
{% for k,v in conf.items() %}
{{ k }} = {{ v }}
{% endfor %}
{% endfor %}
when: content | d(false) | bool
The optimized play below will skip the task if the shell is in the list
- hosts: localhost
vars:
_regex: '\s*='
_replace: ':'
login_dict: "{{ lookup('file', 'login.cfg') |
regex_replace(_regex, _replace) |
from_yaml }}"
shells: "{{ login_dict.usw.shells | split(',') }}"
shells_append: /usr/bin/bash
tasks:
- name: add entry if not there
lineinfile:
backrefs: true
path: login.cfg
regexp: '^(\s*shells)\s*=.*$'
line: '\1 = {{ (shells + [shells_append]) | join(",") }}'
when: shells_append not in shells
This can be further simplified
- hosts: localhost
vars:
_regex: '\s*='
_replace: ':'
login_conf: "{{ lookup('file', 'login.cfg') |
regex_replace(_regex, _replace) |
from_yaml }}"
shells: "{{ login_conf.usw.shells }}"
shells_append: /usr/bin/bash
tasks:
- name: add entry if not there
lineinfile:
backrefs: true
path: login.cfg
regexp: '^(\s*shells)\s*=.*$'
line: '\1 = {{ [shells, shells_append] | join(",") }}'
when: shells_append not in shells
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With