How to get IP address from controller's hosts file in Ansible? - ansible

I am having difficulty figuring out to set a fact in my Ansible playbook that contains the IP address of a server that is listed in the /etc/hosts file on my controller. I am running a playbook against my web server which needs the IP address of my file server. I run the command like this:
ansible-playbook deploy-webservers.yml -i inventory.ini -l webservers
My inventory file looks like this:
[fileservers]
prod-fs1.example.com
[webservers]
prod-web1.example.com
[localhost]
127.0.0.1 ansible_connection=local ansible_python_interpreter=/Users/jsmith/.virtualenvs/provision/bin/python
Here is the playbook:
---
hosts: all
gather_facts: yes
become: yes
pre_tasks:
- name: get file server's IP address
command: "grep prod-fs1 /etc/hosts | awk '{ print $1 }'"
register: fs_ip_addr
delegate_to: localhost
- debug: var={{ fs_ip_addr }}
When I run it, I get this error:
TASK [get file server's IP address] ****************************************************************************************
fatal: [prod-web1.example.com -> localhost]: FAILED! => {"changed": true, "cmd": ["grep", "prod-fs1", "/etc/hosts", "|", "awk", "{ print $0 }"], "delta": "0:00:00.010303", "end": "2020-03-03 12:24:36.207656",
"msg": "non-zero return code", "rc": 2, "start": "2020-03-03 12:24:36.197353", "stderr": "grep: |: No such file or directory\ngrep: awk: No such file or directory\ngrep: { print $0 }:
No such file or directory", "stderr_lines": ["grep: |: No such file or directory", "grep: awk: No such file or directory", "grep: { print $0 }: No such file or directory"], "stdout": "/etc/hosts:45.79.99.99 prod-fs1.example.com prod-fs1", "stdout_lines": ["/etc/hosts:45.79.99.99 prod-fs1.example.com prod-fs1"]}
PLAY RECAP ****************************************************************************************************************
prod-web1.example.com : ok=7 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0;
It looks like Ansible has a problem parsing the command when it reaches the pipe symbol. Is there a way around this problem?

Try this. No need to delegate to localhost. lookup is running always on the controller
- set_fact:
fs_ip_addr: "{{ (lookup('file', '/etc/hosts').splitlines()|
list|
select('search', search_host)|
list|
first).split().0 }}"
vars:
search_host: "prod-fs1"

The code can be simplified a bit. Note that search_host is a var.
- set_fact:
fs_ip_addr: "{{ lookup('file', '/etc/hosts').splitlines() |
select('search', search_host) |
first | split() | first }}"

Related

Rendering environment variables in an Ansible shell command

I'm trying to run a shell command on a remote host using Ansible, and this command must also include a local environment variable.
While the variable is correctly set, the output is not the one I'd expect and I don't understand why.
This is a sample playbook that I prepared for this case:
---
- name: "Ansible local variable in shell command"
hosts: localhost
vars:
variable: "{{ lookup('env', 'VARIABLE') }}"
connection: local
tasks:
- name: "Prints the local variable"
shell: "echo {{ vars.variable }}"
register: "output"
- debug: var=output.stdout_lines
If running VARIABLE=random ansible-playbook playbook.yml I would expect it to print random, what I get instead is this error:
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "echo {{ lookup('env', 'VARIABLE') }}", "delta": "0:00:00.004725", "end": "2023-02-17 21:56:21.282225", "msg": "non-zero return code", "rc": 2, "start": "2023-02-17 21:56:21.277500", "stderr": "/bin/sh: -c: line 1: syntax error near unexpected token `('\n/bin/sh: -c: line 1: `echo {{ lookup('env', 'VARIABLE') }}'", "stderr_lines": ["/bin/sh: -c: line 1: syntax error near unexpected token `('", "/bin/sh: -c: line 1: `echo {{ lookup('env', 'VARIABLE') }}'"], "stdout": "", "stdout_lines": []}
The most relevant part is the content of stderr, where we can see this one:
"/bin/sh: -c: line 1: syntax error near unexpected token `('\n/bin/sh: -c: line 1: `echo {{ lookup('env', 'VARIABLE') }}'
From my understanding, this means that vars.variable has been considered as a plain string instead of being rendered with the result of the lookup function.
But why does this happen? What am I missing on this one?
You must not use vars.variable (which contains the raw Jinja2 string as seen in your error message) but simply variable as demonstrated in the following fixed example:
---
- name: "Ansible local variable in shell command"
hosts: localhost
vars:
variable: "{{ lookup('env', 'VARIABLE') }}"
connection: local
tasks:
- name: "Prints the local variable"
#shell: "echo {{ vars.variable }}"
shell: "echo {{ variable }}"
register: "output"
- debug: var=output.stdout_lines
works fine for me:
$ VARIABLE="Hello, world" ansible-playbook playbook2.yml
PLAY [Ansible local variable in shell command] *******************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Prints the local variable] *********************************************************************************************************************************************************************************************************************************************
changed: [localhost]
TASK [debug] *****************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"output.stdout_lines": [
"Hello, world"
]
}
PLAY RECAP *******************************************************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Extract value from output and send to next task

I am trying to define a template in Ansible Tower, where I want to extract the id for the Active Controller in Kafka Broker and then use this value in another template / task that will perform the rolling restart but will make sure the active controller is started last
When I run this Ansible task
- name: Find active controller
shell: '/bin/zookeeper-shell 192.168.129.227 get /controller'
register: resultAC
I get the below result. I want to extract the brokerid and assign the value of 2 to a variable that can be used in a different task in the same template or pass it to another template when the templates are part of a workflow definition.
I tried using resultAC.stdout_lines[5].brokerid but that does not work.
The structure of resultAC:
{
"resultAC": {
"stderr_lines": [],
"changed": true,
"end": "2020-08-19 07:36:01.950347",
"stdout": "Connecting to 192.168.129.227\n\nWATCHER::\n\nWatchedEvent state:SyncConnected type:None path:null\n{\"version\":1,\"brokerid\":2,\"timestamp\":\"1597241391146\"}",
"cmd": "/bin/zookeeper-shell 192.168.129.227 get /controller",
"failed": false,
"delta": "0:00:02.843972",
"stderr": "",
"rc": 0,
"stdout_lines": [
"Connecting to 192.168.129.227",
"",
"WATCHER::",
"",
"WatchedEvent state:SyncConnected type:None path:null",
"{\"version\":1,\"brokerid\":2,\"timestamp\":\"1597241391146\"}"
],
"start": "2020-08-19 07:35:59.106375"
},
"_ansible_verbose_always": true,
"_ansible_no_log": false,
"changed": false
}
Because your JSON is just part of a list of strings, it is not parsed or considered as a JSON.
You will have to use the Ansible filter from_json in order to parse it back to a dictionary.
Given the playbook:
- hosts: all
gather_facts: no
vars:
resultAC:
stdout_lines:
- "Connecting to 192.168.129.227"
- ""
- "WATCHER::"
- ""
- "WatchedEvent state:SyncConnected type:None path:null"
- "{\"version\":1,\"brokerid\":2,\"timestamp\":\"1597241391146\"}"
tasks:
- debug:
msg: "{{ (resultAC.stdout_lines[5] | from_json).brokerid }}"
This gives the recap:
PLAY [all] *************************************************************************************************************************************************************
TASK [debug] ***********************************************************************************************************************************************************
ok: [localhost] => {
"msg": "2"
}
PLAY RECAP *************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Going further, maybe I would select and match the JSON in the stdout_lines list, just in case it is not always at the sixth line:
- hosts: all
gather_facts: no
vars:
resultAC:
stdout_lines:
- "Connecting to 192.168.129.227"
- ""
- "WATCHER::"
- ""
- "WatchedEvent state:SyncConnected type:None path:null"
- "{\"version\":1,\"brokerid\":2,\"timestamp\":\"1597241391146\"}"
tasks:
- debug:
msg: "{{ (resultAC.stdout_lines | select('match','{.*\"brokerid\":.*}') | first | from_json).brokerid }}"

Ansible single task variable on top of global variable

I have a playbook where I am configuring environment variables for multiple hosts.
these are the global vars set in defaults\main.yml:
environment:
http_proxy: blabla
https_proxy: blabla
Now I have a single task where I need to set another environment variable for the python library.
However, when I set the environment var for that single task it overwrites the global environment vars.
- name: some task
command: command
environment:
ENV_VAR: "blabla"
I want the ENV_VAR for the single task to be added on top of the global vars. But is that even possible?
https://docs.ansible.com/ansible/latest/user_guide/playbooks_environment.html
this page didn't give me anything conclusive.
Could I use the with_items option to achieve this possibly?
The following playbook demonstrates a possible implementation for your requirement using the combine filter associated with default. Run this with -v option to see output of shell commands:
---
- name: Parse several results as json strings
hosts: localhost
gather_facts: false
# This will automatically combine `default_env` (default to empty if it does not exists)
# with `more_env` if this latest var is defined somewhere
environment: "{{ default_env | default({}) | combine(more_env | default({})) }}"
vars:
# Define default environment variables
default_env:
http_proxy: blabla
https_proxy: blabla
tasks:
- name: Show some vars
vars:
# Inject more environment variables for this task
more_env:
toto: titi
shell: |-
echo $http_proxy
echo $https_proxy
echo $toto
- name: Same with default env
shell: |-
echo $http_proxy
echo $https_proxy
echo $toto
Which gives:
$ ansible-playbook test.yml -v
PLAY [Default env override] ****************************************************************************************************************************************************************************************************************************
TASK [Show some vars] ***************************************************************************************************************************************************************************************************************************************************
changed: [localhost] => {"changed": true, "cmd": "echo $http_proxy\necho $https_proxy\necho $toto", "delta": "0:00:00.002207", "end": "2020-01-07 09:57:30.178989", "rc": 0, "start": "2020-01-07 09:57:30.176782", "stderr": "", "stderr_lines": [], "stdout": "blabla\nblabla\ntiti", "stdout_lines": ["blabla", "blabla", "titi"]}
TASK [Same with default env] ********************************************************************************************************************************************************************************************************************************************
changed: [localhost] => {"changed": true, "cmd": "echo $http_proxy\necho $https_proxy\necho $toto", "delta": "0:00:00.001875", "end": "2020-01-07 09:57:30.626163", "rc": 0, "start": "2020-01-07 09:57:30.624288", "stderr": "", "stderr_lines": [], "stdout": "blabla\nblabla", "stdout_lines": ["blabla", "blabla"]}
PLAY RECAP **************************************************************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

basic telnet script to copy to flash drive

I am trying to use ansible to telnet into cisco switches and apply a copy startup-config disk0 command.
Ansible seems to never be able to pass
(?i)"Destination filename": "work please" through the expect command
---
- hosts: all
gather_facts: false
connection: local
tasks:
- name: telnet,login and execute command
ignore_errors: true
expect:
command: telnet "{{ inventory_hostname }}"
responses:
(?i)password: "{{ password}}"
(?i)#: copy startup-config disk0
(?i)"Destination filename": "{{ lookup('pipe','date') }"
echo: yes
register: telnet_output
What i am getting as an output
ansible-playbook 2.7.6
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/site-packages/ansible
executable location = /usr/bin/ansible-playbook
python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
Using /etc/ansible/ansible.cfg as config file
/var/lib/awx/projects/6500/hosts did not meet host_list requirements, check plugin documentation if this is unexpected
/var/lib/awx/projects/6500/hosts did not meet script requirements, check plugin documentation if this is unexpected
PLAYBOOK: copy-startup.yml *************************************************************************************************************************************************************************************************************
1 plays in copy-startup.yml
PLAY [all] *****************************************************************************************************************************************************************************************************************************
META: ran handlers
TASK [telnet,login and execute command] ************************************************************************************************************************************************************************************************
task path: /var/lib/awx/projects/6500/copy-startup.yml:6
fatal: [66.90.19.18]: FAILED! => {"changed": true, "cmd": "telnet \"66.90.19.18\"", "delta": "0:00:30.370396", "end": "2019-02-12 10:09:41.473716", "msg": "command exceeded timeout", "rc": null, "start": "2019-02-12 10:09:11.103320", "stdout": "Trying 66.90.19.18...\r\r\nConnected to 66.90.19.18.\r\r\nEscape character is '^]'.\r\r\n\r\n\r\nUser Access Verification\r\n\r\nPassword: \r\nLAB-6500-SUP2T#copy startup-config disk0\r\nDestination filename [disk0]? ", "stdout_lines": ["Trying 66.90.19.18...", "", "Connected to 66.90.19.18.", "", "Escape character is '^]'.", "", "", "", "User Access Verification", "", "Password: ", "LAB-6500-SUP2T#copy startup-config disk0", "Destination filename [disk0]? "]}
...ignoring
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
66.90.19.18 : ok=2 changed=1 unreachable=0 failed=0
It seems to never want to write the Destination Filename[disk0]?
Any ideas
(?i)"Destination filename" matches for string with double quotes.
You need:
responses:
'(?i)password': "{{ password}}"
'(?i)#': copy startup-config disk0
'(?i)Destination filename': "{{ lookup('pipe','date') }"
---
- hosts: '6500'
gather_facts: true
connection: local
tasks:
- name: telnet,login and execute command
ignore_errors: true
expect:
command: telnet "{{ inventory_hostname }}"
responses:
(?i)Password: {{ password }}
(?i)Destination filename [disk0]? : "{{ lookup('pipe','date +%Y-%m-%d-%H-%M') }} {{ inventory_hostname }}"
(?i)#: copy startup-config disk0
(?i){{COMMAND}}: exit
echo: yes
register: telnet_output
This seems to be the best solution to what I need. I changed the order of operations and it was rocking,

Ansible copy from the remote server to ansible host fails

I need to copy the latest log file from remote linux server to the ansible host. This is what I have tried so far.
- hosts: [host]
remote_user: root
tasks:
- name: Copy the file
command: bash -c "ls -rt | grep install | tail -n1"
register: result
args:
chdir: /root
- name: Copying the file
copy:
src: "/root/{{ result.stdout }}"
dest: /home
But I am getting the following error .
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok
TASK [Copy the file] **********************************************************************************************************************************************************************************************
changed: => {"changed": true, "cmd": ["bash", "-c", "ls -rt | grep install | tail -n1"], "delta": "0:00:00.011388", "end": "2017-06-14 07:53:26.475344", "rc": 0, "start": "2017-06-14 07:53:26.463956", "stderr": "", "stdout": "install.20170614-051027.log", "stdout_lines": ["install.20170614-051027.log"], "warnings": []}
TASK [Copying the file] *******************************************************************************************************************************************************************************************
fatal: FAILED! => {"changed": false, "failed": true, "msg": "Unable to find 'install.20170614-051027.log' in expected paths."}
PLAY RECAP ********************************************************************************************************************************************************************************************************
: ok=2 changed=1 unreachable=0 failed=1
But that file is right there.Please help me resolve this issue.
Ansible Copy copies files from ansible host to remote host. Use Ansible fetch instead.
http://docs.ansible.com/ansible/fetch_module.html
This one works , i have to use fetch instead of copy to get the file from remote .
- name: Copy the file
command: bash -c "ls -rt | grep install | tail -n1"
register: result
args:
chdir: /root
- name: Copying the file
fetch:
src: "/root/{{ result.stdout }}"
dest: /home
flat: yes

Resources