How can I get the private IP put into conf file ansible? - ansible

I have this role task file in roles/make_elasticsearch_conf/tasks/main.yml:
---
# tasks file for make_elasticsearch_conf
#
- name: Get private IP address
command:
cmd: "hostname -I | awk '{print $2}'"
register: "cluster_ip"
- name: Create /etc/elasticsearch/elasticsearch.yml File
ansible.builtin.template:
src: elasticsearch.yml.j2
dest: /etc/elasticsearch/elasticsearch.yml
I also have a template in roles/make_elasticsearch_conf/templates/elasticsearch.yml.j2:
cluster.name: {{ ansible_host }}
node.name: {{ ansible_host }}
network.host: {{ cluster_ip }}
I use it in this make_elastic_search_conf.yml playbook:
---
- name: Make Elastic Search Config.
hosts: all
become: True
gather_facts: True
roles:
- roles/make_elasticsearch_conf
When I run my playbook I get this error:
FAILED! => {"changed": true, "cmd": ["hostname", "-I", "|", "awk", "{print $2}"], "delta": "0:00:00.006257", "end": "2022-12-06 21:54:47.612238", "msg": "non-zero return code", "rc": 255, "start": "2022-12-06 21:54:47.605981", "stderr": "Usage: hostname [-b] {hostname|-F file} set host name (from file)\n hostname [-a|-A|-d|-f|-i|-I|-s|-y] display formatted name\n hostname display host name\n\n {yp,nis,}domainname {nisdomain|-F file} set NIS domain name (from file)\n {yp,nis,}domainname display NIS domain name\n\n dnsdomainname display dns domain name\n\n hostname -V|--version|-h|--help print info and exit\n\nProgram name:\n {yp,nis,}domainname=hostname -y\n dnsdomainname=hostname -d\n\nProgram options:\n -a, --alias alias names\n -A, --all-fqdns all long host names (FQDNs)\n -b, --boot set default hostname if none available\n -d, --domain DNS domain name\n -f, --fqdn, --long long host name (FQDN)\n -F, --file read host name or NIS domain name from given file\n -i, --ip-address addresses for the host name\n -I, --all-ip-addresses all addresses for the host\n -s, --short short host name\n -y, --yp, --nis NIS/YP domain name\n\nDescription:\n This command can get or set the host name or the NIS domain name. You can\n also get the DNS domain or the FQDN (fully qualified domain name).\n Unless you are using bind or NIS for host lookups you can change the\n FQDN (Fully Qualified Domain Name) and the DNS domain name (which is\n part of the FQDN) in the /etc/hosts file.", "stderr_lines": ["Usage: hostname [-b] {hostname|-F file} set host name (from file)", " hostname [-a|-A|-d|-f|-i|-I|-s|-y] display formatted name", " hostname display host name", "", " {yp,nis,}domainname {nisdomain|-F file} set NIS domain name (from file)", " {yp,nis,}domainname display NIS domain name", "", " dnsdomainname display dns domain name", "", " hostname -V|--version|-h|--help print info and exit", "", "Program name:", " {yp,nis,}domainname=hostname -y", " dnsdomainname=hostname -d", "", "Program options:", " -a, --alias alias names", " -A, --all-fqdns all long host names (FQDNs)", " -b, --boot set default hostname if none available", " -d, --domain DNS domain name", " -f, --fqdn, --long long host name (FQDN)", " -F, --file read host name or NIS domain name from given file", " -i, --ip-address addresses for the host name", " -I, --all-ip-addresses all addresses for the host", " -s, --short short host name", " -y, --yp, --nis NIS/YP domain name", "", "Description:", " This command can get or set the host name or the NIS domain name. You can", " also get the DNS domain or the FQDN (fully qualified domain name).", " Unless you are using bind or NIS for host lookups you can change the", " FQDN (Fully Qualified Domain Name) and the DNS domain name (which is", " part of the FQDN) in the /etc/hosts file."], "stdout": "", "stdout_lines": []}
I have tried all sorts of ways to get the private ip of the host but nothing I have tried gave the expected result.

The problem is caused by |.
As per the documentation of the command module:
If you want to run a command through the shell (say you are using <,> >, |, and so on), you actually want the ansible.builtin.shell module instead. Parsing shell metacharacters can lead to unexpected commands
being executed if quoting is not done correctly so it is more secure
to use the command module when possible.
You might want to use shell module. Or better get the IP address from the ansible facts.

Related

Ansible cli_command prompt not working as expected

I'm trying to do a ping command from a Cisco device. I have to use the extended ping command, I cannot specify the arguments in only one line.
This is what the router is expecting:
ping
Protocol [ip]:
Target IP address: <IP HERE>
Repeat count [5]:
Datagram size [100]:
Timeout in seconds [2]:
Extended commands [n]: y
Ingress ping [n]:
Source address or interface: <INT HERE>
DSCP Value [0]:
Type of service [0]:
Set DF bit in IP header? [no]:
Validate reply data? [no]:
Data pattern [0x0000ABCD]:
Loose, Strict, Record, Timestamp, Verbose[none]:
Sweep range of sizes [n]:
As you see, I'm only defining the target IP, the extended command flag and the source interface.
This is how my task looks like now:
- name: Run Cisco command (ping)
cli_command:
command: ping
prompt:
- "Protocol [ip]:"
- "Target IP address:"
- "Repeat count [5]:"
- "Datagram size [100]:"
- "Timeout in seconds [2]:"
- "Extended commands [n]:"
- "Ingress ping [n]:"
- "Source address or interface:"
- "DSCP Value [0]:"
- "Type of service [0]:"
- "Set DF bit in IP header? [no]:"
- "Validate reply data? [no]:"
- "Data pattern [0x0000ABCD]:"
- "Loose, Strict, Record, Timestamp, Verbose[none]:"
- "Sweep range of sizes [n]:"
answer:
- ''
- "{{ item }}"
- "{{ count }}" (I'm specifying the number of packets here but its optional, same for the size)
- "{{ size }}"
- ''
- y
- ''
- loopback1
- ''
- ''
- ''
- ''
- ''
- ''
- ''
with_lines: cat "{{ dest_file }}"
ignore_errors: yes
when: ansible_network_os == 'ios'
And this is what ansible returns:
"results":[
{
"ansible_loop_var":"item",
"item": first target here,
"changed":false,
"failed":true,
"invocation":{
"module_args":{
"sendonly":false,
"prompt":[
"Protocol [ip]",
"Target IP address",
"Repeat count [5]",
"Datagram size [100]:",
"Timeout in seconds [2]:",
"Extended commands [n]",
"Ingress ping [n]",
"Source address or interface",
"DSCP Value [0]",
"Type of service [0]",
"Set DF bit in IP header? [no]",
"Validate reply data? [no]",
"Data pattern [0x0000ABCD]",
"Loose, Strict, Record, Timestamp, Verbose[none]",
"Sweep range of sizes [n]"
],
"check_all":true,
"newline":true,
"command":"ping",
"answer":[
"",
"192.168.1.1", (first target here)
"5",
"72",
"",
"y",
"",
"loopback 1",
"",
"",
"",
"",
"",
"",
""
]
}
},
"ansible_facts":{
"discovered_interpreter_python":"/usr/bin/python"
},
**"msg":"timeout value 30 seconds reached while trying to send command: ping"**
},
{
"ansible_loop_var":"item",
**"stdout":"% Unknown protocol - \"ping\", type \"ping ?\" for help",**
"failed":false,
"changed":false,
"item":"second target here",
"invocation":{
"module_args":{
"sendonly":false,
"prompt":[
"Protocol [ip]",
"Target IP address",
"Repeat count [5]",
"Datagram size [100]:",
"Timeout in seconds [2]:",
"Extended commands [n]",
"Ingress ping [n]",
"Source address or interface",
"DSCP Value [0]",
"Type of service [0]",
"Set DF bit in IP header? [no]",
"Validate reply data? [no]",
"Data pattern [0x0000ABCD]",
"Loose, Strict, Record, Timestamp, Verbose[none]",
"Sweep range of sizes [n]"
],
"check_all":true,
"newline":true,
"command":"ping",
"answer":[
"",
"192.168.1.2", (second target here)
"5",
"72",
"",
"y",
"",
"loopback 1",
"",
"",
"",
"",
"",
"",
""
]
}
},
"stdout_lines":[
**"% Unknown protocol - \"ping\", type \"ping ?\" for help"**
]
}
First, the first of each pair of IPs always timeouts, even if its reachable. It doesn't matter if its a 30 second or 120 second timeout.
Second, why is the first prompt (protocol) taking the value "ping" instead of the value ""? "% Unknown protocol - "ping", type "ping ?" for help"
I changed the empty answer to : "", '', \n, "ip" but none worked, the result is always the same.
I changed also the prompt to: "Protocol [ip]:", "Protocol [ip]", "Protocol [ip]: ", Protocol [ip], Protocol [ip] but none worked.
The idea is that the 15 answers answer to the 15 prompts in order.
Any idea on what I'm doing wrong?

Ansible Tower server claims it was sent a bad request

I have written a playbook to create credentials with custom credentials, below is the ansible playbook
---
- name: Trigger an Atower API
hosts: localhost
connection: local
tasks:
- name: Create a valid SCM credential from a private_key file
command: tower-cli credential create --organization "Default" --name "DevOps User" --credential-type "csa-test2" --inputs "{'user':'devops', 'stg01_ssh_key':\"$( sed -z 's/\n/\\n/g' test.pem )\" }"
no_log: false
I am getting this error related to quotaions
name", "DevOps User", "--credential-type", "csa-test2", "--inputs", "{'user':'devops', 'stg01_ssh_key':\"$( sed -z 's/\\n/\\n/g' test.pem )\" }"], "delta": "0:00:01.319114", "end": "2021-01-07 16:00:22.763388", "msg": "non-zero return code", "rc": 40, "start": "2021-01-07 16:00:21.444274", "stderr": "Error: The Tower server claims it was sent a bad request.\n\nPOST http://x.x.x.x:13080/api/v2/credentials/\nParams: None\nData: {\"inputs\": {\"stg01_ssh_key\": \"$( sed -z 's/\\n/\\n/g' test.pem )\", \"user\": \"devops\"}, \"credential_type\": 36, \"organization\": 1, \"name\": \"DevOps User\"}\n\nResponse: {\"inputs\":{\"stg01_ssh_key\":[\"Invalid certificate or key: $( sed -z 's/\\n/\\n/g' test.pem )...\"]}}", "stderr_lines": ["Error: The Tower server claims it was sent a bad request.", "", "POST http://x.x.x.x:13080/api/v2/credentials/", "Params: None", "Data: {\"inputs\": {\"stg01_ssh_key\": \"$( sed -z 's/\\n/\\n/g' test.pem )\", \"user\": \"devops\"}, \"credential_type\": 36, \"organization\": 1, \"name\": \"DevOps User\"}", "", "Response: {\"inputs\":{\"stg01_ssh_key\":[\"Invalid certificate or key: $( sed -z 's/\\n/\\n/g' test.pem )...\"]}}"], "stdout": "", "stdout_lines": []}
Also, when i try manually "tower-cli create" command, it is working fine. Can anyone help me in this. I dunno what is wrong here.
try to use shell instead of command, documentation says
The command(s) will not be processed through the shell, so variables like $HOSTNAME and operations like "*", "<", ">", "|", ";" and "&" will not work. Use the ansible.builtin.shell module if you need these features.
source: Ansible Documentation

How do I pass a dictionary to an ansible ad-hoc command?

If I have an ansible ad-hoc command that wants a dictionary or list valued argument, like the queries argument to postgresql_query, how do I invoke that in ansible ad-hoc commands?
Do I have to write a one-command playbook? I'm looking for a way to minimise the numbers of layers of confusing quoting (shell, yaml/json, etc) involved.
The ansible docs mention accepting structured forms for variables. So I tried the yaml and json syntax for the arguments:
ansible -m postgresql_query -sU postgres -a '{"queries":["SELECT 1", "SELECT 2"]}'
... but got ERROR! this task 'postgresql_query' has extra params, which is only allowed in the following modules: ....
the same is true if I #include a file with yaml or json contents like
cat > 'query.yml' <<'__END__'
queries:
- "SELECT 1"
- "SELECT 2"
__END__
ansible -m postgresql_query -sU postgres -a #queries.yml
You can define a dictionary in a JSON variable to pass it as parameter next:
ansible -m module_name -e '{"dict": {"key": "value"}}' -a "param={{ dict }}"
(parameters positions are arbitrary)
I have most of a solution - a way to express something like a shell script or query payload without extra quoting. But it's ugly:
ansible hostname -m postgresql_query -sU postgres -a 'query="{{query}}"' -e #/dev/stdin <<'__END__'
query: |
SELECT 'no special quotes needed' AS "multiline
identifier works fine" FROM
generate_series(1,2)
__END__
Not only is that shamefully awful, but it doesn't seem to work for lists (arrays):
ansible hostname -m postgresql_query -sU postgres -vvv -a 'query="{{query}}"' -e #/dev/stdin <<'__END__'
queries:
- |
SELECT 1
- |
SELECT 2
__END__
fails with
hostname | FAILED! => {
"changed": false,
"err": "syntax error at or near \"<\"\nLINE 1: <bound method Templar._query_lookup of <ansible.template.Tem...\n ^\n",
"invocation": {
"module_args": {
"autocommit": false,
"conninfo": "",
"queries": null,
"query": "<bound method Templar._query_lookup of <ansible.template.Templar object at 0x7f72531c61d0>>"
}
},
"msg": "Database query failed"
}
so it looks like some kind of lazy evaluation is breaking things.

Empty string in Ansible responses

I am developing RouterOS network module for Ansible 2.5.
RouterOS shell can print a few messages that should be detected in on_open_shell() event and either skipped or dismissed automatically. These are Do you want to see the software license? [Y/n]: and a few others, all of which are well documented here in the MikroTik Wiki.
Here is how I'm doing this:
def on_open_shell(self):
try:
if not prompt.strip().endswith(b'>'):
self._exec_cli_command(b' ')
except AnsibleConnectionFailure:
raise AnsibleConnectionFailure('unable to bypass license prompt')
It indeed does bypass the license prompt. However it seems that the \n response from the RouterOS device counts as a reply for the actual commands that follow. So, if I have two tasks in my playbook like this:
---
- hosts: routeros
gather_facts: no
connection: network_cli
tasks:
- routeros_command:
commands:
- /system resource print
- /system routerboard print
register: result
- name: Print result
debug: var=result.stdout_lines
This is the output I get:
ok: [example] => {
"result.stdout_lines": [
[
""
],
[
"uptime: 12h33m29s",
" version: 6.42.1 (stable)",
" build-time: Apr/23/2018 10:46:55",
" free-memory: 231.0MiB",
" total-memory: 249.5MiB",
" cpu: Intel(R)",
" cpu-count: 1",
" cpu-frequency: 2700MHz",
" cpu-load: 2%",
" free-hdd-space: 943.8MiB",
" total-hdd-space: 984.3MiB",
" write-sect-since-reboot: 7048",
" write-sect-total: 7048",
" architecture-name: x86",
" board-name: x86",
" platform: MikroTik"
]
]
}
As you can see, the output seems to be offset by 1. What should I do to correct this?
It turns out that the problem was in the regular expression that defined the shell prompt. I had it defined like this:
terminal_stdout_re = [
re.compile(br"\[\w+\#[\w\-\.]+\] ?>"),
# other cases
]
It did not match the end of prompt which caused Ansible to think that there was a newline before the actual command output. Here is the correct regexp:
terminal_stdout_re = [
re.compile(br"\[\w+\#[\w\-\.]+\] ?> ?$"),
# other cases
]

generate file and populate values

I'm tasked with creating about a hundred files for use with puppet. I'm creating .yaml files with unique filenames that will contain site-specific IP and hostname information, these must have the same format (ideally from a template).
I want to create a file generator that fills in variables for IP, subnet, network, and hostname from an input file (.csv?) What's the best way to approach this?
sample format:
---
network::interfaces::interfaces:
eth0:
method: 'static'
address: '10.20.30.1'
netmask: '255.255.240.0'
broadcast: '10.20.30.255'
network: '10.20.30.0'
gateway: '10.20.30.1'
network::interfaces::auto:
- 'eth0'
hosts::host_entries:
HOSTNAME:
ip: '10.20.30.2'
hosts::purge_hosts: true
dhcpd::range_start: '10.20.30.11'
dhcpd::range_stop: '10.20.30.240'
dhcpd::gateway: '10.20.30.1'
hornetq::site: 'test'
Write a skeleton like this:
network::interfaces::interfaces:
eth0:
method: 'static'
address: '__IP__'
netmask: '__MASK__'
broadcast: '__BC__'
network: '__NET__'
gateway: '__GW__'
etc.
Generate files with a loop like
cat input-file | while read OUTPUT IP MASK BC NET GW ; do
sed -e s/__IP__/$IP/ \
-e s/__MASK__/$MASK/ \
-e s/__BC__/$BC/ \
-e s/__NET__/$NET/ \
-e s/__GW__/$GW/ \
<$SKELETON >$OUTPUT
done
This assumes that the fileds in the input file are separated by whitespace, with the name of the respective output file in the first column.

Resources