Categories
DevOps

How to dynamically define Ansible SSH user

Create an additional Ansible role to dynamically set Ansible SSH user depending on the defined conditionals.

How it works

The idea is very straightforward as it is based on the inventory hostname. Checks are performed locally, the defined Ansible SSH user fact is used later to perform the SSH connection.

  1. Determine remote address, use ansible_host variable if available, inventory_hostname otherwise.
  2. Set SSH username to vagrant if it does contain “vagrant” string.
  3. Set SSH username to ansible if it does not contain “vagrant” string.
    This step can be skipped as the default value can be set inside the Ansible configuration file.
  4. Check SSH connection using defined SSH user and register output.
  5. Set SSH username to root when the connection was unsuccessful.
  6. Check SSH connection to identify unreachable hosts and fail early.
    This step can be skipped as it is here to simply fail early.

Fallback to the root user means that the host is uninitialized.

The inventory

The inventory contains hosts. Ansible SSH user is not defined here.

---
lxd:
  hosts:
    vagrant01:
      ansible_host: 192.168.50.150
    vagrant02:
      ansible_host: 192.168.50.151
    kraken:
    cerberus:

The playbook

At first, execute the ansible_user role, then continue as always.

---
- hosts: lxd
  gather_facts: no
  roles:
    - ansible_user

- hosts: lxd
  tasks:
    - name: Determine target hostname
      set_fact:
        local_hostname: "{{ appliance.hostname | default(inventory_hostname) }}"
    - name: Set hostname
      hostname:
        name: "{{ local_hostname }}"

The role

Inspect the following code. Some parts are not necessary, like default user which can be defined inside the local Ansible configuration file,

---
- name: Determine remote address
  set_fact:
    ansible_user__connect_to: "{{ hostvars[inventory_hostname].ansible_host | default(inventory_hostname) }}"

- name: Set SSH username to vagrant
  set_fact:
    ansible_ssh_user: vagrant
  when: "'vagrant' in inventory_hostname"
  changed_when: false
  delegate_to: localhost

- name: Set SSH username to ansible
  set_fact:
    ansible_ssh_user: ansible
  when: "'vagrant' not in inventory_hostname"
  changed_when: false
  delegate_to: localhost

- block:
  - name: Check SSH connection to verify {{ ansible_ssh_user }} user
    wait_for_connection:
      timeout: 5
    register: ansible_user__check_command_output
    check_mode: no
    changed_when: false
  rescue:
    - name: Display unitialized or unreachable hosts
      debug:
        msg: "Hostname {{ inventory_hostname }} is not initialized"

- name: Set SSH username to root
  set_fact:
    ansible_ssh_user: root
  changed_when: false    
  when: ansible_user__check_command_output.failed == true

- name: Check SSH connection to identify unreachable hosts and fail early
  wait_for_connection:
    timeout: 5
  check_mode: no
  changed_when: false

You can modify and extend it to solve your problems, like automatically discovering and using the initial dietpi or rpi users.

Execute the playbook

Execute the playbook.

PLAY [lxd] ***

TASK [ansible_user : Determine remote address] ****
ok: [vagrant01]
ok: [vagrant02]
ok: [kraken]
ok: [cerberus]

TASK [ansible_user : Set SSH username to vagrant] *
ok: [vagrant01]
ok: [vagrant02]

TASK [ansible_user : Set SSH username to ansible] *
ok: [kraken]
ok: [cerberus]

TASK [ansible_user : Check SSH connection to verify vagrant user] *
ok: [vagrant01]
ok: [kraken]
ok: [vagrant02]
ok: [cerberus]

TASK [ansible_user : Set SSH username to root] ****

TASK [ansible_user : Check SSH connection to identify unreachable hosts and fail early] *
ok: [vagrant01]
ok: [vagrant02]
ok: [kraken]
ok: [cerberus]

PLAY [lxd] 

TASK [Gathering Facts] 
ok: [vagrant02]
ok: [vagrant01]
ok: [kraken]
ok: [cerberus]

TASK [Determine target hostname] ****
ok: [vagrant01]
ok: [vagrant02]
ok: [kraken]
ok: [cerberus]

TASK [Set hostname] ***
--- before
+++ after
@@ -1 +1 @@
-hostname = ubuntu-focal
+hostname = vagrant01

changed: [vagrant01]
--- before
+++ after
@@ -1 +1 @@
-hostname = ubuntu-focal
+hostname = vagrant02

changed: [vagrant02]
ok: [kraken]
ok: [cerberus]

PLAY RECAP 
cerberus                   : ok=7    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
kraken                     : ok=7    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
vagrant01                  : ok=7    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
vagrant02                  : ok=7    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

Additional fallback behaviour.

PLAY [lxd] ***********************************************************************************************************************************************[29/1933]

TASK [ansible_user : Determine remote address] ********************************************************************************************************************
ok: [vagrant01]
ok: [vagrant02]
ok: [kraken]
ok: [cerberus]

TASK [ansible_user : Set SSH username to vagrant] *****************************************************************************************************************
ok: [vagrant01]
ok: [vagrant02]

TASK [ansible_user : Set SSH username to ansible] *****************************************************************************************************************
ok: [kraken]
ok: [cerberus]

TASK [ansible_user : Check SSH connection to verify vagrant user] *************************************************************************************************
[WARNING]: Unhandled error in Python interpreter discovery for host kraken: Failed to connect to the host via ssh: Warning: Permanently added '172.16.110.120'
(ECDSA) to the list of known hosts.  ansible1@172.16.110.120: Permission denied (publickey).
[WARNING]: Unhandled error in Python interpreter discovery for host cerberus: Failed to connect to the host via ssh: Warning: Permanently added '172.16.110.210'
(ECDSA) to the list of known hosts.  ansible1@172.16.110.210: Permission denied (publickey).
ok: [vagrant01]
ok: [vagrant02]
fatal: [kraken]: FAILED! => {"changed": false, "elapsed": 5, "msg": "timed out waiting for ping module test: Data could not be sent to remote host \"kraken\". Make
 sure this host can be reached over ssh: Warning: Permanently added '172.16.110.120' (ECDSA) to the list of known hosts.\r\nansible1@172.16.110.120: Permission denie
d (publickey).\r\n"}
fatal: [cerberus]: FAILED! => {"changed": false, "elapsed": 5, "msg": "timed out waiting for ping module test: Data could not be sent to remote host \"cerberus\".
Make sure this host can be reached over ssh: Warning: Permanently added '172.16.110.210' (ECDSA) to the list of known hosts.\r\nansible1@172.16.110.210: Permission
 denied (publickey).\r\n"}

TASK [ansible_user : Display unitialized or unreachable hosts] ****************************************************************************************************
ok: [kraken] => {
    "msg": "Hostname kraken is not initialized"
}
ok: [cerberus] => {
    "msg": "Hostname cerberus is not initialized"
}

TASK [ansible_user : Set SSH username to root] ********************************************************************************************************************
ok: [kraken]
ok: [cerberus]

TASK [ansible_user : Check SSH connection to identify unreachable hosts and fail early] ***************************************************************************
ok: [vagrant01]
ok: [vagrant02]
ok: [kraken]
ok: [cerberus]

PLAY [lxd] ********************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************
ok: [vagrant01]
ok: [vagrant02]
ok: [kraken]
ok: [cerberus]

TASK [Determine target hostname] **********************************************************************************************************************************
ok: [vagrant01]
ok: [vagrant02]
ok: [kraken]
ok: [cerberus]

TASK [Set hostname] ***********************************************************************************************************************************************
ok: [vagrant01]
ok: [vagrant02]
ok: [kraken]
ok: [cerberus]

PLAY RECAP ********************************************************************************************************************************************************
cerberus                   : ok=8    changed=0    unreachable=0    failed=0    skipped=1    rescued=1    ignored=0
kraken                     : ok=8    changed=0    unreachable=0    failed=0    skipped=1    rescued=1    ignored=0
vagrant01                  : ok=7    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0
vagrant02                  : ok=7    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0