Making prompted vars usable across the playbook - ansible

I have one playbook with multiple tasks that have to run on different hosts.
In the beginning of the play I want to prompt the operator for their credentials which are the same for every host in the play. I want to have those credentials "stored" somewhere so they can be used across the tasks to log in on the provided host(s).
Playbook looks as followed,
---
- name: Ask for credentials
vars_prompt:
- name: username
prompt: "Username?"
- name: password
prompt: "Password?"
tasks:
- set_fact:
username: "{{username}}"
- set_fact:
password: "{{password}}"
- hosts: Host1
vars:
ansible_user: "{{ username }}"
ansible_password: "{{ password }}"
tasks:
- name: Do stuff
- hosts: Host2
vars:
ansible_user: "{{username}}"
ansible_password: "{{password}}"
tasks:
- name: Do stuff
...
From the moment the play hits the first task it will fail with the flowing error,
msg: 'The field ''remote_user'' has an invalid value, which includes an undefined variable. The error was: ''username'' is undefined'
Anyone that has experience in making prompted vars usable across the whole play and all tasks?

Q: "Make prompted vars usable across the whole play and all tasks>"
A: Run the first play with the group of all hosts that should be connected later. Run once the set_fact task. This will create the variables username and password for all hosts in the group.
For example if the group test_jails comprises hosts test_01, test_02, test_03 the play
- hosts: test_jails
vars_prompt:
- name: "username"
prompt: "Username?"
- name: "password"
prompt: "Password?"
tasks:
- set_fact:
username: "{{ username }}"
password: "{{ password }}"
run_once: true
- hosts: test_01
vars:
ansible_user: "{{ username }}"
ansible_password: "{{ password }}"
tasks:
- debug:
msg: "{{ ansible_user }} {{ ansible_password }}"
gives
ok: [test_01] => {
"msg": "admin 1234"
}

Related

Ansible: Get Variable with inventory_hostname

I have the following passwords file vault.yml:
---
server1: "pass1"
server2: "pass2"
server3: "pass3"
I am loading these values in a variable called passwords:
- name: Get Secrets
set_fact:
passwords: "{{ lookup('template', './vault.yml')|from_yaml }}"
delegate_to: localhost
- name: debug it
debug:
var: passwords.{{ inventory_hostname }}
The result of the debugging task shows me the result I want to get: The password for the specific host.
But if I set the following in a variables file:
---
ansible_user: root
ansible_password: passwords.{{ inventory_hostname }}
This will not give me the desired result. The ansible_password takes "passwords" literally and not as a variable.
How can I achieve the same result I got when debugging the passwords.{{ inventory_hostname }}?
Regarding the part
... if I set the following in a variables file ...
I am not sure since I miss some information about your use case and data flow. However, in general the syntax ansible_password: "{{ PASSWORDS[inventory_hostname] }}" might work for you.
---
- hosts: localhost
become: false
gather_facts: false
vars:
PASSWORDS:
SERVER1: "pass1"
SERVER2: "pass2"
SERVER3: "pass3"
localhost: "pass_local"
tasks:
- name: Debug var
debug:
var: PASSWORDS
- name: Set Fact 'ansible_password'
set_fact:
ansible_password: "{{ PASSWORDS[inventory_hostname] }}"
- name: Debug var
debug:
var: ansible_password
In that way you can access a element by name.

How to check the OS version of host which in dynamically added to inventory

I'm trying to get server name as user input and if the server OS is RHEL7 it will proceed for further tasks. I'm trying with hostvars but it is not helping, kindly help me to find the OS version with when condition:
---
- name: Add hosts
hosts: localhost
vars:
- username: test
password: test
vars_prompt:
- name: server1
prompt: Server_1 IP or hostname
private: no
- name: server2
prompt: Server_2 IP or hostname
private: no
tasks:
- add_host:
name: "{{ server1 }}"
groups:
- cluster_nodes
- primary
- management
ansible_user: "{{ username }}"
ansible_password: "{{ password}}"
- add_host:
name: "{{ server2 }}"
groups:
- cluster_nodes
- secondary
ansible_user: "{{ username }}"
ansible_password: "{{ password}}"
- debug:
msg: "{{ hostvars['server1'].ansible_distribution_major_version }}"
When I execute the playbook, I'm getting below error:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: \"hostvars['server1']\" is undefined\n\nThe error appears to be in '/var/lib/awx/projects/pacemaker_RHEL_7_ST/main_2.yml': line 33, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
You need to gather_facts on the newly added host before you consume the variable. As an example, this will do it with automatic facts gathering.
---
- name: Add hosts
hosts: localhost
vars:
- username: test
password: test
vars_prompt:
- name: server1
prompt: Server_1 IP or hostname
private: no
- name: server2
prompt: Server_2 IP or hostname
private: no
tasks:
- add_host:
name: "{{ server1 }}"
groups:
- cluster_nodes
- primary
- management
ansible_user: "{{ username }}"
ansible_password: "{{ password}}"
- add_host:
name: "{{ server2 }}"
groups:
- cluster_nodes
- secondary
ansible_user: "{{ username }}"
ansible_password: "{{ password}}"
- name: Gather facts for newly added targets
hosts: cluster_nodes
# gather_facts: true <= this is the default
- name: Do <whatever> targeting localhost again
hosts: localhost
gather_facts: false # already gathered in play1
tasks:
# Warning!! bad practice. Looping on a group usually
# shows you should have a play targeting that specific group
- debug:
msg: "OS version for {{ item }} is 7"
when: hostvars[item].ansible_distribution_major_version | int == 7
loop: "{{ groups['cluster_nodes'] }}"
If you don't want to rely on automatic gathering, you can manually play the setup module, e.g. for the second play:
- name: Gather facts for newly added targets
hosts: cluster_nodes
gather_facts: false
tasks:
- name: get facts from targets
setup:

Ansible Cisco configuration compliance check for invalid users

I am attempting to validate a Cisco configuration with Ansible. I want to be able to tell whether any users have been configured other than the valid ones.
Valid users:
username admin,
username readonly
Invalid users:
username secretbackdoor
I have tried to create a list of users, then flag any which are not valid. The code i have so far is as follows:
---
- hosts: cisco
gather_facts: no
tasks:
- name: show run
ios_command:
commands:
- show run
register: cisco_show_run
- name: list_cisco_usernames
set_fact: cisco_usernames="{{ cisco_show_run.stdout[0] | regex_findall('username (\S+)', multiline=True) }}"
- name: print usernames
debug:
msg: {{ item }}
with_items: "{{ cisco_usernames }}"
This will print out the three users. Not sure where to go next.
"Set Theory Filters" might be next option. For example
- hosts: localhost
vars:
valid_users: [admin, readonly]
invalid_users: [secretbackdoor]
cisco_usernames: [admin, readonly, secretbackdoor]
tasks:
- name: Display users not in valid_users
debug:
msg: Not among valid users {{ not_valid }}
when: not_valid|length > 0
vars:
not_valid: "{{ cisco_usernames|difference(valid_users) }}"
- name: Display users in invalid_users
debug:
msg: Among invalid users {{ not_valid }}
when: not_valid|length > 0
vars:
not_valid: "{{ cisco_usernames|intersect(invalid_users) }}"
gives (abridged)
ok: [localhost] =>
msg: Not among valid users ['secretbackdoor']
ok: [localhost] =>
msg: Among invalid users ['secretbackdoor']
Thanks for this. Your solution is working fine. I put in the first option, as I do not always know what the 'incorrect' users are.

How to use variable in different playbook?

How to use a variable in a different playbook? (ansible 2.7.10)
username.yml
- hosts: host
vars_prompt:
- name: username
prompt: 'Username...'
private: no
tasks:
- name: Show username
debug:
msg: "{{username}}"
- import_playbook: dns.yml
dns.yml
- hosts: DNS
tasks:
- name: Mesaj
debug:
msg: "{{username}}"
FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'username' is undefined
The scope of a variable declared by vars_prompt is the play. Use set_fact to share such variable in the whole playbook.
The set_fact module takes key=value pairs as variables to set in the playbook scope.
- hosts: host
vars_prompt:
- name: username
prompt: 'Username...'
private: no
tasks:
- name: Show username
debug:
msg: "{{ username }}"
- set_fact:
username: "{{ username }}"
In the second play (dns.yml) use hostvars to reference the variables cached by host in the first play.
- hosts: DNS
tasks:
- name: Mesaj
debug:
msg: "{{ hostvars['host'].username }}"
Without disturbing the dns.yml playbook. So, you can pass -e username=myuser and execute it separately if needed.
username.yml
Adding set fact with different variable name (uname) and passing it to the playbook (dns.yml)
- hosts: localhost
vars_prompt:
- name: username
prompt: 'Username...'
private: no
tasks:
- name: Show username
debug:
msg: "{{username}}"
- name: Set fact
set_fact:
uname: "{{ username }}"
- import_playbook: dns.yml
vars:
username: "{{ uname }}"
dns.yml
No change to this playbook.
- hosts: DNS
tasks:
- name: Mesaj
debug:
msg: "{{username}}"

How add hosts from user's input in a ansible playbook?

I need to add a host from the user's input. Now I'm trying to use the ansible in-memory inventory, add_host module and prompt to add the target host to execute the remaining tasks. This is the content of my playbook:
Deploy.yml
- name: Adding the host server
hosts: localhost
- vars_prompt:
- name: "Server IP"
prompt: "Server"
private: no
- name: "Username (default: Ubuntu)"
prompt: "User"
default: "Ubuntu"
private: no
- name: "Password"
prompt: "Passwd"
private: yes
encrypt: "sha512_crypt"
- name: "Identity file path"
prompt: "IdFile"
private: no
when: Passwd is undefined
tasks:
- name: Add host server
add_host:
name: "{{ Server }}"
ansible_ssh_user: "{{ User }}"
ansible_ssh_private_key_file: "{{ IdFile }}"
when: IdFile is defined
- name: Add host server
add_host:
name: "{{ Server }}"
ansible_ssh_user: "{{ User }}"
ansible_ssh_pass: "{{ Passwd }}"
when: Passwd is defined
- hosts: "{{ Server }}"
tasks:
- name: Copy the script file to the server
copy:
src: script.sh
dest: "{{ ansible_env.HOME }}/folder/"
mode: 755
force: yes
attr:
- +x
When I run this playbook with this command $ ansible-playbook Deploy.yml, The output is:
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [Adding the host server] ***********************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************
ok: [localhost]
Server: <server-ip>
User [Ubuntu]:
Passwd:
IdFile: <path/to/id/file>
ERROR! the field 'hosts' is required but was not set
I don't know why it throws this error:
ERROR! the field 'hosts' is required but was not set
How can I do what I need to do?
UPDATE:
It still not working. This is the content of my playbook:
Deploy.yml
- name: Adding the host server
hosts: localhost
vars_prompt:
- name: "Server"
prompt: "Server IP"
private: no
- name: "User"
prompt: "Username"
default: "Ubuntu"
private: no
- name: "Passwd"
prompt: "Password"
private: yes
encrypt: "sha512_crypt"
- name: "IdFile"
prompt: "Identity file path"
private: no
when: Passwd is undefined
tasks:
- name: Add host server
add_host:
name: "{{ Server }}"
ansible_ssh_user: "{{ User }}"
ansible_ssh_private_key_file: "{{ IdFile }}"
when: IdFile is defined
- name: Add host server
add_host:
name: "{{ Server }}"
ansible_ssh_user: "{{ User }}"
ansible_ssh_pass: "{{ Passwd }}"
when: IdFile is undefined
- hosts: "{{ Server }}"
tasks:
- name: Copy the script file to the server
copy:
src: script.sh
dest: "{{ ansible_env.HOME }}/folder/"
mode: 755
force: yes
attr:
- +x
When I run this playbook with this command $ ansible-playbook Deploy.yml, The output is:
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [Adding the host server] ***********************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************
ok: [localhost]
Server IP: <server-ip>
Username [Ubuntu]:
Password:
Identity file path: <path/to/id/file>
ERROR! the field 'hosts' is required but was not set
I don't know why it throws this error:
ERROR! The field 'hosts' has an invalid value, which includes an undefined variable. The error was: 'Server' is undefined
Here is a flowchart of how the playbook should works:
+------------------+ +---------------+ +-----------------+
|Use ansible to run| |Get host IP fom| |Get ssh User from|
| this playbook +---->+ user's input +---->+ user's input |
+------------------+ +---------------+ +--------+--------+
|
v
+------------+--------+
|Get ssh password from|
| user's input |
+------------+--------+
|
v
+---------------+ *************************
|Add a host with| Yes | Did the user inputted |
v----------+ password +<---+| a password? |
+----------------+ +---------------+ ***************+*********
||Run some tasks|| |No
||in recently || v
||added host || +---------------+ +------------+--------+
+----------------+ |Add a host with| |Get ssh identity file|
^----------+ identity file +<------+ from user's input |
+---------------+ +---------------------+
Ok I've updated my answer to suit the changes in your question, with the original answer left for historic reasons.
To solve the substitution error you are seeing, which results in an empty host list in your second play, I would instead use an inventory group.
There are also two other syntax errors in the second play
The file mode needs to be octal (i.e. 0700)
The attribute is invalid. My assumption is you are trying to make the file executable, so fix the file mode and remove the attribute.
Here is an updated playbook:
- name: Adding the host server
hosts: localhost
vars_prompt:
- name: "Server"
prompt: "Server IP"
private: no
- name: "User"
prompt: "Username"
default: "Ubuntu"
private: no
- name: "Passwd"
prompt: "Password"
private: yes
encrypt: "sha512_crypt"
- name: "IdFile"
prompt: "Identity file path"
private: no
when: Passwd is undefined
tasks:
- name: Add host server
add_host:
name: "{{ Server }}"
ansible_ssh_user: "{{ User }}"
ansible_ssh_private_key_file: "{{ IdFile }}"
group: added_hosts
when: IdFile is defined
- name: Add host server
add_host:
name: "{{ Server }}"
ansible_ssh_user: "{{ User }}"
ansible_ssh_pass: "{{ Passwd }}"
group: added_hosts
when: IdFile is undefined
- hosts: added_hosts
tasks:
- name: Copy the script file to the server
copy:
src: script.sh
dest: "{{ ansible_env.HOME }}/folder/"
mode: 0755
force: yes
=== OLD ANSWER ===
User input is stored in the whatever variable you are using for the name attribute in each of the variable prompts.
You need to switch around your name and prompt values under vars_prompt
There are also YAML formatting issues
For example:
- vars_prompt:
- name: "Server IP"
prompt: "Server"
private: no
should be:
vars_prompt:
- name: "server"
prompt: "Server IP"
private: no
Then you can refer to the {{ server }} variable in your tasks
Your ansible script is having a problem.
vars_prompt:
remove - from vars_prompt line it will work properly.
I tried in my local server the same script is working properly.
- name: Adding the host server
hosts: localhost
vars_prompt:
- name: "Server"
prompt: "Server IP"
private: no
- name: "User"
prompt: "Username"
default: "Ubuntu"
private: no
- name: "Passwd"
prompt: "Password"
private: yes
encrypt: "sha512_crypt"
- name: "IdFile"
prompt: "Identity file path"
private: no
when: Passwd is undefined
tasks:
- name: Add host server
add_host:
name: "{{ Server }}"
ansible_ssh_user: "{{ User }}"
ansible_ssh_private_key_file: "{{ IdFile }}"
when: IdFile is defined
- name: Add host server
add_host:
name: "{{ Server }}"
ansible_ssh_user: "{{ User }}"
ansible_ssh_pass: "{{ Passwd }}"
when: Passwd is defined
- name: Create a file
shell: touch newfile
delegate_to: "{{ Server }}"
In the last task update to your task and run it.
- name: Create a file
shell: touch newfile
delegate_to: "{{ Server }}"

Resources