Backstory:
I’m making an Ansible role that creates an SSH key for hosts that need one and automatically updates my configuration file with the latest information; lan ip, username, key, etc. I can’t get the Ansible to delete the existing block correctly though before adding the new one. Then when it adds the new one, it overwrites the other hosts block for some reason (so at the end, only 1 block exists even if there are 10 hosts. I’ll fix the latter later, but my main concern is to get the old block deleted correctly.
Question(s) & tl;dr:
Is there a better approach to doing this, does anyone see why this regex doesn’t work in Ansible, and could my regex be simplified/improved?
Here’s the relevant tasks from my Ansible role
- name: Read existing SSH config
slurp:
src: "{{ ssh_config_dir }}/config"
register: ssh_config_file
- name: Decode existing SSH config
set_fact:
ssh_config_content: "{{ ssh_config_file.content | b64decode }}"
- name: Parse existing SSH config into lines
set_fact:
ssh_config_lines: "{{ ssh_config_content.split('n') }}"
- name: Check if existing host entry matches
set_fact:
host_entry_valid: >
{{ ssh_config_lines | select('match', '^Host {{ inventory_hostname }}$') | list | length > 0 and
ssh_config_lines | select('match', '^\s*Hostname {{ hostvars[inventory_hostname].ansible_host }}$') | list | length > 0 and
ssh_config_lines | select('match', '^\s*User {{ ssh_remote_user }}$') | list | length > 0 and
ssh_config_lines | select('match', '^\s*Port {{ ssh_port }}$') | list | length > 0 and
ssh_config_lines | select('match', '^\s*IdentityFile {{ ssh_key_dir }}/{{ inventory_hostname }}{{ ssh_key_name_suffix }}$') | list | length > 0 }}
- name: Debug host entry validity
debug:
var: host_entry_valid
- name: Backup the existing SSH config
copy:
src: "{{ ssh_config_dir }}/config"
dest: "{{ ssh_config_dir }}/config.bak"
when: not host_entry_valid
- name: Define the regex pattern
set_fact:
my_regex: '^(s+)?Hosts+{{ inventory_hostname }}(s+)?$n^(((s+)?[A-Za-z0-9./_-]|#)+(s+)?)$n^(s+)?$'
- name: Print regex pattern
debug:
msg: "{{ my_regex | quote }}"
- name: Remove existing host entry if it doesn't match
lineinfile:
path: "{{ ssh_config_dir }}/config"
state: absent
regexp: '^(s+)?Hosts+{{ inventory_hostname }}(s+)?$n^(((s+)?[A-Za-z0-9./_-]|#)+(s+)?)$n^(s+)?$'
when: not host_entry_valid
Check if existing host entry matches
needs works, but it should trigger the Remove existing host entry if it doesn't match
task which I’m having problems with. The regex matches a block perfectly in VSCode, but in Sublime it matches multiple blocks and Ansible it doesn’t seem to work at all.
This started out as a ChatGPT creation, but I tweaked some of it and ended up writing the regex myself.
This works in VSCode:
^(s+)?Host test(s+)?$n^(((s+)?[A-Za-z0-9./_-]|#)+(s+)?)$n
but the negative(?) look ahead does not. It does work in Sublime, but sublime matches multiple blocks for some reason.
^(s+)?Host test(s+)?$n^(((s+)?[A-Za-z0-9./_-]|#)+(s+)?)$n(?=Host)
The idea is to match Host hostname
and then look for any lines that contain text until there is a blank line; and with the negative look ahead check to see if host exists after the blank line, but I’m not familiar with using lookaheads and this won’t work if the block is the last block in the file, so I’ll be removing that.
Here’s the relevant Ansible output and the old SSH block doesn’t get removed:
TASK [ssh-keys : Remove existing SSH agents and known hosts] ********************************************************************************************
included: /scripts/ansible/roles/ssh-keys/tasks/remove_existing_ssh_agents.yml for test
TASK [ssh-keys : Remove all SSH agents] *****************************************************************************************************************
skipping: [test]
TASK [ssh-keys : Remove host from known hosts] **********************************************************************************************************
skipping: [test]
TASK [ssh-keys : Copy SSH key to remote server] *********************************************************************************************************
included: /scripts/ansible/roles/ssh-keys/tasks/copy_key.yml for test
TASK [ssh-keys : Copy SSH key to remote server] *********************************************************************************************************
skipping: [test]
Example block:
Host test
HostName 10.0.0.4
User myuser
IdentityFile ...
Host test2
...