Ansible aws_ec2 plugin returns duplicate instances - amazon-ec2

Config:
inventory = inventory.aws_ec2.yaml
enable_plugins = aws_ec2
remote_user = ansible
private_key_file=~/.ssh/ansible
host_key_checking = False
Inventory:
---
plugin: aws_ec2
aws_profile: default
regions:
- "ap-southeast-1"
keyed_groups:
- key: tags.Environment
prefix: ""
separator: ""
filters:
tag:Environment:
dev
instance-state-name : running
compose:
ansible_host: public_ip_address
And here's the unexpected result:
$ ansible-inventory --graph
#all:
|--#aws_ec2:
| |--ec2-1-1-1-64.ap-southeast-1.compute.amazonaws.com
| |--ip-172-31-1-1.ap-southeast-1.compute.internal
|--#dev:
| |--ec2-1-1-1-64.ap-southeast-1.compute.amazonaws.com
| |--ip-172-31-1-1.ap-southeast-1.compute.internal
|--#ungrouped:
There's actually only ONE host in the "dev" group. Not two. This is the same host that it's listing twice, and it somehow thinks they are two distinct hosts. One is the public IP and one the private IP. It thinks they're both a host.
Any ideas what is going on here?

Related

Unexpected behavior with 'ansible-playbook --limit' and regular expression with lookahead

I have the following test setup and result:
$ cat hosts
[test]
abc-1
abc-ecs
abc-x
ecs-1
ecs-y
$ cat test-regex.yaml
- name: test
hosts:
- test
tasks:
- name: set var
set_fact:
myvar: "this is a test"
$ cat hosts | sed "s: ::g" | ggrep -P '^(?!ecs).*'
[test]
abc-1
abc-ecs
abc-x
$ ansible-playbook -i hosts -l ~'^(?!ecs).*' test-regex.yaml -D -C --list-hosts --list-tasks
playbook: test-regex.yaml
play #1 (test): test TAGS: []
pattern: ['test']
hosts (5):
abc-ecs
abc-x
ecs-1
ecs-y
abc-1
tasks:
set var TAGS: []
As shown with ggrep, ^(?!ecs).* does not match ecs-1 and ecs-y, but Ansible says it matches, can you explain?
I would like to apply to hosts starting with ecs only. I tested with Ansnile Core 2.13.3.
Also for testing purpose, can I maintain one file instead of two as shown here?
The question of the original post asks why -l ~'^(?!ecs).*' matches every host in the test group:
[test]
abc-1
abc-ecs
abc-x
ecs-1
ecs-y
but not those three abc-* hosts only as intended. This is because -l ~'^(?!ecs).*' not only matches hosts, but groups as well, and ansible creates two default groups: all and ungrouped. In this case, the regular expression matches:
test group which contains all 5 hosts listed above
all group which contains all 5 hosts listed above
ungrouped group which contains 0 hosts
abc-1 host
abc-x host
abc-ecs host
The union of all above consists the 5 hosts as shown in the output in the original post, so this is an expected behavior. To get what I intended, which is every hosts not starting with ecs, I should exclude the test and all groups also. The regular expression and the intended result are copied below.
$ ansible-playbook -i hosts -l ~'^(?!ecs|test|all).*' test-regex.yaml -D -C --list-hosts --list-tasks
playbook: test-regex.yaml
play #1 (test): regexcheck TAGS: []
pattern: ['test']
hosts (3):
abc-1
abc-x
abc-ecs
tasks:
set var TAGS: []
The "mystery" resolved.

ansible magic variables not returning values

I'm trying to build a /etc/hosts file; however it seems that I can't get the hostvars to show up when running a playbook, but with the command line it works to a point.
Here is my ansible.cfg file:
[defaults]
ansible_managed = Please do not change this file directly since it is managed by Ansible and will be overwritten
library = ./library
module_utils = ./module_utils
action_plugins = plugins/actions
callback_plugins = plugins/callback
filter_plugins = plugins/filter
roles_path = ./roles
# Be sure the user running Ansible has permissions on the logfile
log_path = $HOME/ansible/ansible.log
inventory = hosts
forks = 20
host_key_checking = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = $HOME/ansible/facts
fact_caching_timeout = 7200
nocows = 1
callback_whitelist = profile_tasks
stdout_callback = yaml
force_valid_group_names = ignore
inject_facts_as_vars = False
# Disable them in the context of https://review.openstack.org/#/c/469644
retry_files_enabled = False
# This is the default SSH timeout to use on connection attempts
# CI slaves are slow so by setting a higher value we can avoid the following error:
# Timeout (12s) waiting for privilege escalation prompt:
timeout = 60
[ssh_connection]
# see: https://github.com/ansible/ansible/issues/11536
control_path = %(directory)s/%%h-%%r-%%p
ssh_args = -o ControlMaster=auto -o ControlPersist=600s
pipelining = True
# Option to retry failed ssh executions if the failure is encountered in ssh itself
retries = 10
Here is my playbook:
- name: host file update
hosts: baremetal
become: true
gather_facts: true
vars:
primarydomain: "cephcluster.local"
tasks:
- name: print ipv4 info
debug:
msg: "IPv4 addresses: {{ansible_default_ipv4.address }}"
- name: update host file
lineinfile:
dest: /etc/hosts
regexp: '{{ hostvars[item].ansible_default_ipv4.address }}.*{{ item }} {{item}}.{{primarydomain}}$'
line: "{{ hostvars[item].ansible_default_ipv4.address }} {{item}} {{item}}.{{primarydomain}}"
state: present
with_items: "{{ groups.baremetal }}"
Here is my inventory file:
[baremetal]
svr1 ansible_host=192.168.59.10
svr2 ansible_host=192.168.59.11
svr3 ansible_host=192.168.59.12
When I run the playbook I get:
FAILED! =>
msg: |-
The task includes an option with an undefined variable. The error was: 'ansible_default_ipv4' is undefined
However when I run
ansible baremetal -m gather_facts -a "filter=ansible_default_ipv4"
I get:
| SUCCESS => {
"ansible_facts": {
"ansible_default_ipv4": {
"address": "192.168.59.20",
"alias": "eno1",
"broadcast": "192.168.59.255",
"gateway": "192.168.59.1",
"interface": "eno1",
"macaddress": "80:18:44:e0:4b:a4",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "192.168.59.0",
"type": "ether"
}
},
"changed": false,
"deprecations": [],
"warnings": []
}
But; if I run
ansible baremetal -m gather_facts -a "filter=ansible_default_ipv4.address"
I get nothing in the return:
SUCCESS => {
"ansible_facts": {},
"changed": false,
"deprecations": [],
"warnings": []
}
I have even tried in the playbook:
- debug:
msg: "IPv4 addresses: {{ansible_default_ipv4 }}"
and nothing gets returned.
Not sure what I'm missing.
You should get used to use and abuse the debug module.
Provided that the ansible ad-hoc command raised you that the facts are under ansible_facts, you could have done a simple task in your playbook:
- debug:
var: ansible_facts
From that, you would have discovered that the fact you are looking for is nested in the dictionary ansible_facts under the key default_ipv4. And that you can access its address with the property address.
So, your debug task ends up being:
- debug:
msg: "IPv4 addresses: {{ ansible_facts.default_ipv4.address }}"

yml format for inventory file...fails

---
all:
zones:
- name: accessswitch
hosts:
- name: accessswitch-x0
ip: 192.168.4.xx
- name: groupswitch
hosts:
- name: groupswitch-x1
ip: 192.168.4.xx
- name: groupswitch-x2
ip: 192.168.4.xx
- name: groupswitch-x3
ip: 192.168.4.xx
Basically i have a access switch and to that switch there are many group switches connected to it...Tried already "children" but this does not work. A typical ini file works....
Also i have some variables...which do apply to all zones aka. access witch & group switches...
in the future there will be more than 1 ..multiple access switches..
....
some docs use ansible-host:...confusing..
And yes..checked the uml structure...
cat#catwomen:~/workspace/ansible-simulator/inventories/simulator/host_vars$ sudo ansible -i testdata.yaml all -m ping
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match
'all'
cat#catwomen:~/workspace/ansible-simulator/inventories/simulator/host_vars$ sudo ansible -i testdata.yaml all -m ping
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match
'all'
You have yaml indentation problems which is an error (indentation in yaml is significant). Moreover, you must follow the specific format described in the documentation.
Very basically, the file is an imbrication of groups in parent/child relationship starting with the top element special group all. A group definition looks like:
---
my_group1: # group name, use `all` at top level
vars:
# definition of vars for this group. Apply to all hosts if defined for `all`
hosts:
# hosts in that group (host is ungrouped if it appears only in `all`)
children:
# mapping of children group definitions (repeat the current format)
A host definition looks like:
my_host1:
# definition of vars specific to that host if any
A var definition (either for vars in group or for a specific host) looks like:
my_variable1: some value
If I understand correctly from your example, here is how your yaml inventory should look like (inventories/so_example.yml)
---
all:
children:
accessswitch:
hosts:
accessswitch-x0:
ansible_host: 192.168.4.xx
groupswitch:
hosts:
groupswitch-x1:
ansible_host: 192.168.4.xx
groupswitch-x2:
ansible_host: 192.168.4.xx
groupswitch-x3:
ansible_host: 192.168.4.xx
You can then easilly see how the above is interpreted with the ansible-inventory command:
$ ansible-inventory -i inventories/so_example.yml --graph
#all:
|--#accessswitch:
| |--accessswitch-x0
|--#groupswitch:
| |--groupswitch-x1
| |--groupswitch-x2
| |--groupswitch-x3
|--#ungrouped:
$ ansible-inventory -i inventories/so_example.yml --list
{
"_meta": {
"hostvars": {
"accessswitch-x0": {
"ansible_host": "192.168.4.xx"
},
"groupswitch-x1": {
"ansible_host": "192.168.4.xx"
},
"groupswitch-x2": {
"ansible_host": "192.168.4.xx"
},
"groupswitch-x3": {
"ansible_host": "192.168.4.xx"
}
}
},
"accessswitch": {
"hosts": [
"accessswitch-x0"
]
},
"all": {
"children": [
"accessswitch",
"groupswitch",
"ungrouped"
]
},
"groupswitch": {
"hosts": [
"groupswitch-x1",
"groupswitch-x2",
"groupswitch-x3"
]
}
}
The incident is not correct for the line #6. Try below please.
---
all:
zones:
- name: accessswitch
hosts:
- name: accessswitch-x0
ip: 192.168.4.xx
- name: groupswitch
hosts:
- name: groupswitch-x1
ip: 192.168.4.xx
- name: groupswitch-x2
ip: 192.168.4.xx
- name: groupswitch-x3
ip: 192.168.4.xx

How to use ansible-playbook --limit with an IP address, rather than a hostname?

Our inventory in INI style looks like this:
foo-host ansible_host=1.2.3.4 some_var=bla
bar-host ansible_host=5.6.7.8 some_var=blup
I can limit a playbook run to a single host by using the host alias:
$ ansible-playbook playbook.yml --limit foo-host
But I can't limit the run by mentioning the host's IP address from the ansible_host variable:
$ ansible-playbook playbook.yml --limit 1.2.3.4
ERROR! Specified hosts and/or --limit does not match any hosts
The reason I want to do that is because Ansible is triggered by an external system that only knows the IP address, but not the alias.
Is there a way to make this work? Mangling the IP address (e.g. ip_1_2_3_4) would be acceptable.
Things I've considered:
Turn it on its head and identify all hosts by IP address:
1.2.3.4 some_var=bla
5.6.7.8 some_var=blup
But now we can't use the nice host aliases anymore, and the inventory file is less readable too.
Write a custom inventory script that is run after the regular inventory, and creates a group like ip_1_2_3_4 containing only that single host, so we can use --limit ip_1_2_3_4. But there's no way to access previously loaded inventory from inventory scripts, so I don't know which groups to create.
Create the new groups dynamically using the group_by module. But because this is a task, it is run only after --limit has already decided that there are no hosts matching the pattern, and at that point Ansible just gives up and doesn't run the group_by task anymore.
Better solutions still welcome, but currently I'm doing it with a small inventory plugin, which (as opposed to an inventory script) does have access to previously added inventory:
plugins/inventory/ip_based_groups.py
import os.path
import re
from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.inventory.group import Group
PATH_PLACEHOLDER = 'IP_BASED_GROUPS'
IP_RE = re.compile('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
class InventoryModule(BaseInventoryPlugin):
'''
This inventory plugin does not create any hosts, but just adds groups based
on IP addresses. For each host whose ansible_host looks like an IPv4
address (e.g. 1.2.3.4), a corresponding group is created by prefixing the
IP address with 'ip_' and replacing dots by underscores (e.g. ip_1_2_3_4).
Use it by putting the literal string IP_BASED_GROUPS at the end of the list
of inventory sources.
'''
NAME = 'ip_based_groups'
def verify_file(self, path):
return self._is_path_placeholder(path)
def parse(self, inventory, loader, path, cache=True):
if not self._is_path_placeholder(path):
return
for host_name, host in inventory.hosts.items():
ansible_host = host.vars.get('ansible_host', '')
if self._is_ip_address(ansible_host):
group = 'ip_' + ansible_host.replace('.', '_')
inventory.add_group(group)
inventory.add_host(host_name, group)
def _is_path_placeholder(self, path):
return os.path.basename(path) == PATH_PLACEHOLDER
def _is_ip_address(self, s):
return bool(IP_RE.match(s))
ansible.cfg
[defaults]
# Load plugins from these directories.
inventory_plugins = plugins/inventory
# Directory that contains all inventory files, and placeholder to create
# IP-based groups.
inventory = inventory/,IP_BASED_GROUPS
[inventory]
# Enable our custom inventory plugin.
enable_plugins = ip_based_groups, host_list, script, auto, yaml, ini, toml
Q: Playbook running a script running a playbook... it would work, but it's a bit hacky
A: FWIW. It's possible to use json_query and avoid the script. For example
- hosts: all
gather_facts: false
tasks:
- set_fact:
my_host: "{{ hostvars|dict2items|json_query(query)|first }}"
vars:
query: "[?value.ansible_host == '{{ my_host_ip }}' ].key"
run_once: true
- add_host:
hostname: "{{ my_host }}"
groups: my_group
run_once: true
- hosts: my_group
gather_facts: false
tasks:
- debug:
var: inventory_hostname
Q: "Unfortunately I'm running ansible-playbook from AWX, so no wrapper scripts allowed."
A: It is possible to run the script from the playbook. For example the playbook below
- hosts: all
gather_facts: false
tasks:
- set_fact:
my_host: "{{ lookup('pipe', playbook_dir ~ '/script.sh ' ~ my_host_ip) }}"
delegate_to: localhost
run_once: true
- add_host:
hostname: "{{ my_host }}"
groups: my_group
run_once: true
- hosts: my_group
gather_facts: false
tasks:
- debug:
var: inventory_hostname
gives
$ ansible-playbook -e 'my_host_ip=10.1.0.53' play.yml
PLAY [all] ********************
TASK [set_fact] ***************
ok: [test_01 -> localhost]
TASK [add_host] ***************
changed: [test_01]
PLAY [my_group] ***************
TASK [debug] ************
ok: [test_03] => {
"inventory_hostname": "test_03"
}
(Fit the script to print the hostname only.)
Q: I can limit a playbook run to a single host by using the host alias:
$ ansible-playbook playbook.yml --limit foo-host
But I can't limit the run by mentioning the host's IP address from the ansible_host variable
$ ansible-playbook playbook.yml --limit 1.2.3.4
A: ansible-inventory and jq are able to resolve the host. For example the script
#!/bin/bash
my_host="$(ansible-inventory --list | jq '._meta.hostvars | to_entries[] | select (.value.ansible_host=="'"$1"'") | .key')"
my_host="$(echo $my_host | sed -e 's/^"//' -e 's/"$//')"
echo host: $my_host
ansible-playbook -l $my_host play.yml
with the inventory
test_01 ansible_host=10.1.0.51
test_02 ansible_host=10.1.0.52
test_03 ansible_host=10.1.0.53
and with the playbook.yml
- hosts: all
gather_facts: false
tasks:
- debug:
var: inventory_hostname
gives
$ ./script.bash 10.1.0.53
host: test_03
PLAY [all] **********
TASK [debug] ************
ok: [test_03] => {
"inventory_hostname": "test_03"
}

Ansible create var from hosts

I am using ansible to create a env var list of hosts and ports to be used for a database connection string.
The application requires two vars: a host list and a port list and uses that to create a connection string.
hosts: 127.0.0.1,127.0.0.1,127.0.0.1
ports: 123,123,123
host and port are matched based on index postion.
I am able to get hosts and join them as required. What I am unable to do is dynamically create a ports string based on the number of hosts. We are currently using the same port so it should be easier.
What I would like to do is create ports:123,123,123 where the number of times 123 is repeated is equal to the number of hosts.
Looked at this link for gettingnumber of hsots: Ansible: Get number of hosts in group
How i just need to print 123 that numebr of times and assign it to ports.
You could use sequence plugin
vars:
hosts: ['127.0.0.1','127.0.0.1','127.0.0.1']
- name: ports fact with size of hosts
set_fact:
ports: "{{ ports | default([]) + [123]}}"
with_sequence: count="{{ hosts | length }}"
- debug: msg="{{ ports }}"
TASK [debug] ********************* ok: [localhost] => {
"ports": [
123,
123,
123
] }
Source: https://docs.ansible.com/ansible/2.5/plugins/lookup/sequence.html

Resources