Ansible: append to config line when entry is not present

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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>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
</code>
<code>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 </code>
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>---
- 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
</code>
<code>--- - 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 </code>
---
- 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?

2

Let’s parse the content of the file first. For example, if you replace the equal signs with colons you get YAML

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> _regex: 's*='
_replace: ':'
login_dict: "{{ lookup('file', 'login.cfg') |
regex_replace(_regex, _replace) |
from_yaml }}"
</code>
<code> _regex: 's*=' _replace: ':' login_dict: "{{ lookup('file', 'login.cfg') | regex_replace(_regex, _replace) | from_yaml }}" </code>
  _regex: 's*='
  _replace: ':'
  login_dict: "{{ lookup('file', 'login.cfg') |
                  regex_replace(_regex, _replace) |
                  from_yaml }}"

gives

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> 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
</code>
<code> 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 </code>
  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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> shells: "{{ login_dict.usw.shells | split(',') }}"
</code>
<code> shells: "{{ login_dict.usw.shells | split(',') }}" </code>
 shells: "{{ login_dict.usw.shells | split(',') }}"

gives

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> 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
</code>
<code> 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 </code>
  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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> shells_append: /usr/bin/bash
shells_update: "{{ (shells_append in shells) |
ternary(shells, shells + [shells_append]) |
join(',') }}"
</code>
<code> shells_append: /usr/bin/bash shells_update: "{{ (shells_append in shells) | ternary(shells, shells + [shells_append]) | join(',') }}" </code>
  shells_append: /usr/bin/bash
  shells_update: "{{ (shells_append in shells) |
                     ternary(shells, shells + [shells_append]) |
                     join(',') }}"

Use shells_update in the module lineinfile

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> - name: add entry if not there
lineinfile:
backrefs: true
path: login.cfg
regexp: '^(s*shells)s*=.*$'
line: '1 = {{ shells_update }}'
</code>
<code> - name: add entry if not there lineinfile: backrefs: true path: login.cfg regexp: '^(s*shells)s*=.*$' line: '1 = {{ shells_update }}' </code>
    - 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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> usw_update:
usw:
shells: "{{ shells_update }}"
login_update: "{{ login_dict | combine(usw_update, recursive='true') }}"
</code>
<code> usw_update: usw: shells: "{{ shells_update }}" login_update: "{{ login_dict | combine(usw_update, recursive='true') }}" </code>
  usw_update:
    usw:
      shells: "{{ shells_update }}"
  login_update: "{{ login_dict | combine(usw_update, recursive='true') }}"

gives

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> 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
</code>
<code> 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 </code>
  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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> - debug:
msg: |
{% for section,conf in login_update.items() %}
{{ section }}:
{% for k,v in conf.items() %}
{{ k }} = {{ v }}
{% endfor %}
{% endfor %}
</code>
<code> - debug: msg: | {% for section,conf in login_update.items() %} {{ section }}: {% for k,v in conf.items() %} {{ k }} = {{ v }} {% endfor %} {% endfor %} </code>
    - debug:
        msg: |
          {% for section,conf in login_update.items() %}
          {{ section }}:
          {% for k,v in conf.items() %}
                  {{ k }} = {{ v }}
          {% endfor %}
          {% endfor %}

gives

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> 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
</code>
<code> 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 </code>
  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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> - 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 %}
</code>
<code> - 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 %} </code>
    - 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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>- 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
</code>
<code>- 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 </code>
- 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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>- 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
</code>
<code>- 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 </code>
- 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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>- 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
</code>
<code>- 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 </code>
- 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

1

One quick and cheap way to do it could be the following:

First, it would be necessary to gather facts about the Remote Nodes. Even if it is not the best way it could simply be done via a minimal example

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>---
- hosts: test
become: false
gather_facts: false
vars:
FACT: "shells"
SEARCH_STRING: "/usr/bin/bash"
SEARCH_FILE: "login.cfg"
tasks:
# grep string from Remote File
- name: Gathering Custom Facts
shell:
cmd: "grep {{ FACT }} {{ SEARCH_FILE }} | tr -d ' ' | cut -d '=' -f 2"
register: shells
# Since this is a reporting, gathering facts task
# it needs to deliver a result in any case
failed_when: shells.rc != 0 and shells.rc != 1
check_mode: false
changed_when: false
- name: Show available remote shells
debug:
msg: "{{ shells.stdout }}"
when: ansible_check_mode
</code>
<code>--- - hosts: test become: false gather_facts: false vars: FACT: "shells" SEARCH_STRING: "/usr/bin/bash" SEARCH_FILE: "login.cfg" tasks: # grep string from Remote File - name: Gathering Custom Facts shell: cmd: "grep {{ FACT }} {{ SEARCH_FILE }} | tr -d ' ' | cut -d '=' -f 2" register: shells # Since this is a reporting, gathering facts task # it needs to deliver a result in any case failed_when: shells.rc != 0 and shells.rc != 1 check_mode: false changed_when: false - name: Show available remote shells debug: msg: "{{ shells.stdout }}" when: ansible_check_mode </code>
---
- hosts: test
  become: false
  gather_facts: false

  vars:

    FACT: "shells"
    SEARCH_STRING: "/usr/bin/bash"
    SEARCH_FILE: "login.cfg"

  tasks:

  # grep string from Remote File

  - name: Gathering Custom Facts
    shell:
      cmd: "grep {{ FACT }} {{ SEARCH_FILE }} | tr -d ' ' | cut -d '=' -f 2"
    register: shells
    # Since this is a reporting, gathering facts task
    # it needs to deliver a result in any case
    failed_when: shells.rc != 0 and shells.rc != 1
    check_mode: false
    changed_when: false

  - name: Show available remote shells
    debug:
      msg: "{{ shells.stdout }}"
    when: ansible_check_mode

resulting into an output of

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>TASK [Show available remote shells] *********************************************************************************************************************************
ok: [test.example.com] =>
msg: /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
</code>
<code>TASK [Show available remote shells] ********************************************************************************************************************************* ok: [test.example.com] => msg: /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 </code>
TASK [Show available remote shells] *********************************************************************************************************************************
ok: [test.example.com] =>
  msg: /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

Q: “Is there a method to avoid the testing using the shell module?

A better approach is shown under How to search for a string in a Remote File using Ansible?, Ansible: How to append an option? or just use Custom facts directly.

Second, append entry if it is not contained already

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> - name: Append entry if it is not contained already
lineinfile:
path: "{{ SEARCH_FILE }}"
regexp: ".*{{ FACT }} =.*"
line: " {{ FACT }} = {{ shells.stdout }},{{ SEARCH_STRING }}"
when: not shells.stdout is search(SEARCH_STRING)
</code>
<code> - name: Append entry if it is not contained already lineinfile: path: "{{ SEARCH_FILE }}" regexp: ".*{{ FACT }} =.*" line: " {{ FACT }} = {{ shells.stdout }},{{ SEARCH_STRING }}" when: not shells.stdout is search(SEARCH_STRING) </code>
  - name: Append entry if it is not contained already
    lineinfile:
      path: "{{ SEARCH_FILE }}"
      regexp: ".*{{ FACT }} =.*"
      line: "        {{ FACT }} = {{ shells.stdout }},{{ SEARCH_STRING }}"
    when: not shells.stdout is search(SEARCH_STRING)

My main question was how to find out if an entry is present in a config file, without using shell: module. My answer is to use lineinfile: with check_mode: yes

By the way, up to this moment I was thinking that quotation marks and apostrophes are interchangeable in Ansible playbooks. The experience shows that they are not. The line: row, which contains regex bacrefs, gave me syntax error with quotation marks, but is working fine with apostrophes

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>---
- name: testing the behavior of check_mode option
hosts: localhost
vars:
searchstr: "/usr/bin/bash"
logincfgfile: "{{ playbook_dir }}/login.cfg"
become: no
tasks:
- name: check if line is present
lineinfile:
path: "{{ logincfgfile }}"
regex: "shells = .*{{ searchstr }}.*"
state: absent
check_mode: yes
register: entrypresent
- name: add shell entry if it was not present
lineinfile:
backrefs: yes
path: "{{ logincfgfile }}"
regex: "(?P<shellsline> *shells = .*)"
line: 'g<shellsline>,{{ searchstr }}'
# line: "g<shellsline>,{{ searchstr }}" # not working with quotation marks
when: entrypresent.found == 0
</code>
<code>--- - name: testing the behavior of check_mode option hosts: localhost vars: searchstr: "/usr/bin/bash" logincfgfile: "{{ playbook_dir }}/login.cfg" become: no tasks: - name: check if line is present lineinfile: path: "{{ logincfgfile }}" regex: "shells = .*{{ searchstr }}.*" state: absent check_mode: yes register: entrypresent - name: add shell entry if it was not present lineinfile: backrefs: yes path: "{{ logincfgfile }}" regex: "(?P<shellsline> *shells = .*)" line: 'g<shellsline>,{{ searchstr }}' # line: "g<shellsline>,{{ searchstr }}" # not working with quotation marks when: entrypresent.found == 0 </code>
---
- name: testing the behavior of check_mode option
  hosts: localhost
  vars:
    searchstr: "/usr/bin/bash"
    logincfgfile: "{{ playbook_dir }}/login.cfg"
  become: no
  tasks:
  - name: check if line is present
    lineinfile:
      path: "{{ logincfgfile }}"
      regex: "shells = .*{{ searchstr }}.*"
      state: absent
    check_mode: yes
    register: entrypresent
  - name: add shell entry if it was not present
    lineinfile:
      backrefs: yes
      path: "{{ logincfgfile }}"
      regex: "(?P<shellsline> *shells = .*)"
      line: 'g<shellsline>,{{ searchstr }}'
      # line: "g<shellsline>,{{ searchstr }}"    # not working with quotation marks
    when: entrypresent.found == 0

For the sake of simplicity I am using a local file to experiment on. This is defined in logincfgfile. I am trying do delete the line with the shell definitions, including the shell to be added, but I do it in check_mode, so there is no real change to the file, just collecting data – and registering into entrypresent variable. The output of lineinfile has a field, called found, which holds the number of matches. If it is zero, thus no matches, the next task adds the new shell entry.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật