'Ansible uncomment line in file

I want to uncomment a line in file sshd_config by using Ansible and I have the following working configuration:

- name: Uncomment line from /etc/ssh/sshd_config
    lineinfile:
      dest: /etc/ssh/sshd_config
      regexp: '^#AuthorizedKeysFile'
      line: 'AuthorizedKeysFile    .ssh/authorized_keys'

However this config only works if the line starts by #AuthorizedKeysFile, but it won't work if the line starts by # AuthorizedKeysFile or # AuthorizedKeysFile (spaces between # and the words).

How can I configure the regexp so it won't take into account any number of spaces after '#'?


I've tried to add another lineinfile option with a space after '#', but this is not a good solution:

- name: Uncomment line from /etc/ssh/sshd_config
    lineinfile:
      dest: /etc/ssh/sshd_config
      regexp: '# AuthorizedKeysFile'
      line: 'AuthorizedKeysFile    .ssh/authorized_keys'


Solution 1:[1]

If you need zero or more white spaces after the '#' character, the following should suffice:

- name: Uncomment line from /etc/ssh/sshd_config
    lineinfile:
      dest: /etc/ssh/sshd_config
      regexp: '^#\s*AuthorizedKeysFile.*$'
      line: 'AuthorizedKeysFile    .ssh/authorized_keys'

The modification to your original code is the addition of the \s* and the .*$ in the regex.

Explanation:

\s - matches whitespace (spaces, tabs, line breaks and form feeds)

* - specifies that the expression to it's left (\s) can have zero or more instances in a match

.* - matches zero or more of any character

$ - matches the end of the line

Solution 2:[2]

Firstly, you are using the wrong language. With Ansible, you don't tell it what to do, but define the desired state. So it shouldn't be Uncomment line form /etc/ssh/sshd_config, but Ensure AuthorizedKeysFile is set to .ssh/authorized_keys.

Secondly, it doesn't matter what the initial state is (if the line is commented, or not). You must specify a single, unique string that identifies the line.

With sshd_config this is possible as the AuthorizedKeysFile directive occurs only once in the file. With other configuration files this might be more difficult.

- name: Ensure AuthorizedKeysFile is set to .ssh/authorized_keys
  lineinfile:
    dest: /etc/ssh/sshd_config
    regexp: AuthorizedKeysFile
    line: 'AuthorizedKeysFile    .ssh/authorized_keys'

It will match any line containing AuthorizedKeysFile string (no matter if it's commented or not, or how many spaces are there) and ensure the full line is:

AuthorizedKeysFile .ssh/authorized_keys

If the line were different, Ansible will report "changed" state.

On the second run, Ansible will find the AuthorizedKeysFile again and discover the line is already in the desired state, so it will end the task with "ok" state.


One caveat with the above task is that if any of the lines contains a comment such as a real, intentional comment (for example an explanation in English containing the string AuthorizedKeysFile), Ansible will replace that line with the value specified in line.

Solution 3:[3]

I should caveat this with @techraf's point that 99% of the time a full template of a configuration file is almost always better.

Times I have done lineinfile include weird and wonderful configuration files that are managed by some other process, or laziness for config I don't fully understand yet and may vary by distro/version and I don't want to maintain all the variants... yet.

Go forth and learn more Ansible... it is great because you can keep iterating on it from raw bash shell commands right up to best practice.

lineinfile module

Still good to see how best to configuration manage one or two settings just a little better with this:

tasks:
- name: Apply sshd_config settings
  lineinfile:
    path: /etc/ssh/sshd_config
    # might be commented out, whitespace between key and value
    regexp: '^#?\s*{{ item.key }}\s'
    line: "{{ item.key }} {{ item.value }}"
    validate: '/usr/sbin/sshd -T -f %s'
  with_items:
  - key: MaxSessions
    value: 30
  - key: AuthorizedKeysFile    
    value: .ssh/authorized_keys
  notify: restart sshd

handlers:
- name: restart sshd
  service: 
    name: sshd
    state: restarted
  • validate don't make the change if the change is invalid
  • notify/handlers the correct way to restart once only at the end
  • with_items (soon to become loop) if you have multiple settings
  • ^#? the setting might be commented out - see the other answer
  • \s*{{ item.key }}\s will not match other settings (i.e. SettingA cannot match NotSettingA or SettingAThisIsNot)

Still might clobber a comment like # AuthorizedKeysFile - is a setting which we have to live with because there could be a setting like AuthorizedKeysFile /some/path # is a setting... re-read the caveat.

template module

- name: Configure sshd
  template:
    src: sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: "0644"
    validate: '/usr/sbin/sshd -T -f %s'
  notify: restart sshd
handlers:
- name: restart sshd
  service: 
    name: sshd
    state: restarted

multiple distro support

And if you are not being lazy about supporting all your distros see this tip

- name: configure ssh
  template: src={{ item }} dest={{ SSH_CONFIG }} backup=yes
  with_first_found:
    - "{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.sshd_config.j2"
    - "{{ ansible_distribution }}.sshd_config.j2"

https://ansible-tips-and-tricks.readthedocs.io/en/latest/modifying-files/modifying-files/

(needs to be updated to a loop using the first_found lookup)

Solution 4:[4]

Is it possible to achieve the same goal with replace module.

https://docs.ansible.com/ansible/latest/modules/replace_module.html

- name: Uncomment line from /etc/ssh/sshd_config
  replace:
    path: /etc/ssh/sshd_config
    regexp: '^\s*#+AuthorizedKeysFile.*$'
    replace: 'AuthorizedKeysFile    .ssh/authorized_keys'

Solution 5:[5]

If you want to simply uncomment a line without setting the value, you can use replace with backreferences, eg (with a handy loop):

- name: Enable sshd AuthorizedKeysFile
  replace:
    path: /etc/ssh/sshd_config
    # Remove comment and first space from matching lines
    regexp: '^#\s?(\s*){{ item }}(.+)$'
    replace: '\1{{ item }}\2'
  loop:
    - 'AuthorizedKeysFile'

This will only remove the first space after the #, and so retain any original indenting. It will also retain anything after the key (eg the default setting, and any following comments)

Thanks to the other helpful answers that provided a solid starting point.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Willem van Ketwich
Solution 2 Willem van Ketwich
Solution 3
Solution 4 sgargel
Solution 5