Compare commits
10 Commits
76756e07f5
...
main
Author | SHA1 | Date | |
---|---|---|---|
a2c2cbb6ab | |||
fc078fb11c | |||
84da4f79bd | |||
3ef8841c48 | |||
6198e2f341 | |||
a0ccc1fafc | |||
3942108d2d | |||
dd30292a09 | |||
ffe68073f7 | |||
18f49ceed8 |
@@ -1,54 +1,81 @@
|
||||
---
|
||||
plugin: linode.cloud.inventory
|
||||
access_token: "{{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||
|
||||
# Use Linode labels as hostnames
|
||||
strict: false
|
||||
|
||||
# Create groups
|
||||
keyed_groups:
|
||||
# Group by region: region_us_southeast
|
||||
- key: region
|
||||
prefix: region
|
||||
separator: "_"
|
||||
|
||||
# Group by instance type: type_g6_standard_6
|
||||
- key: type
|
||||
prefix: type
|
||||
separator: "_"
|
||||
|
||||
# Group by status: status_running
|
||||
- key: status
|
||||
prefix: status
|
||||
separator: "_"
|
||||
|
||||
# Group by tags: tag_k3s, tag_debian, etc.
|
||||
- key: tags
|
||||
prefix: tag
|
||||
separator: "_"
|
||||
|
||||
# Set host variables
|
||||
plugin: advanced_host_list
|
||||
compose:
|
||||
ansible_host: ipv4[0]
|
||||
ansible_user: phlux
|
||||
linode_id: id
|
||||
linode_region: region
|
||||
linode_type: type
|
||||
linode_status: status
|
||||
linode_tags: tags
|
||||
# This is a workaround - we'll use a playbook to generate the inventory instead
|
||||
|
||||
# Add convenience variables for your tags
|
||||
is_k3s: "'k3s' in (tags | default([]))"
|
||||
is_control_plane: "'control-plane' in (tags | default([]))"
|
||||
is_worker_node: "'worker-node' in (tags | default([]))"
|
||||
is_debian: "'Debian' in (tags | default([]))"
|
||||
is_ubuntu: "'Ubuntu' in (tags | default([]))"
|
||||
# Alternative: use the script plugin explicitly
|
||||
plugin: script
|
||||
script: |
|
||||
#!/usr/bin/env python3
|
||||
import json, sys, os, urllib.request
|
||||
from argparse import ArgumentParser
|
||||
|
||||
# Optional: Only include running instances
|
||||
filters:
|
||||
- status == "running"
|
||||
def main():
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('--list', action='store_true')
|
||||
parser.add_argument('--host')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Cache settings
|
||||
cache: true
|
||||
cache_plugin: memory
|
||||
cache_timeout: 300
|
||||
if args.host:
|
||||
print('{}')
|
||||
return
|
||||
|
||||
token = os.environ.get('LINODE_API_TOKEN')
|
||||
if not token:
|
||||
print('{"_meta": {"hostvars": {}}}')
|
||||
return
|
||||
|
||||
try:
|
||||
req = urllib.request.Request('https://api.linode.com/v4/linode/instances')
|
||||
req.add_header('Authorization', f'Bearer {token}')
|
||||
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
data = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
inventory = {"_meta": {"hostvars": {}}, "all": {"children": []}}
|
||||
groups = {}
|
||||
|
||||
for instance in data.get('data', []):
|
||||
if instance['status'] != 'running':
|
||||
continue
|
||||
|
||||
hostname = instance['label']
|
||||
ip = instance['ipv4'][0] if instance.get('ipv4') else None
|
||||
if not ip:
|
||||
continue
|
||||
|
||||
tags = instance.get('tags', [])
|
||||
inventory['_meta']['hostvars'][hostname] = {
|
||||
'ansible_host': ip,
|
||||
'ansible_user': 'phlux',
|
||||
'linode_region': instance['region'],
|
||||
'linode_type': instance['type'],
|
||||
'linode_tags': tags,
|
||||
'is_k3s': 'k3s' in tags,
|
||||
'is_control_plane': 'control-plane' in tags,
|
||||
'is_worker_node': 'worker-node' in tags
|
||||
}
|
||||
|
||||
for tag in tags:
|
||||
group = f"tag_{tag.replace('-', '_')}"
|
||||
if group not in groups:
|
||||
groups[group] = []
|
||||
groups[group].append(hostname)
|
||||
|
||||
region_group = f"region_{instance['region'].replace('-', '_')}"
|
||||
if region_group not in groups:
|
||||
groups[region_group] = []
|
||||
groups[region_group].append(hostname)
|
||||
|
||||
for group, hosts in groups.items():
|
||||
inventory[group] = {"hosts": hosts}
|
||||
inventory['all']['children'].append(group)
|
||||
|
||||
print(json.dumps(inventory, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
print(f'{{"error": "{e}"}}', file=sys.stderr)
|
||||
print('{"_meta": {"hostvars": {}}}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
80
inventory/linode.py
Normal file
80
inventory/linode.py
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import urllib.request
|
||||
from argparse import ArgumentParser
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('--list', action='store_true')
|
||||
parser.add_argument('--host')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.host:
|
||||
print('{}')
|
||||
return
|
||||
|
||||
token = os.environ.get('LINODE_API_TOKEN')
|
||||
if not token:
|
||||
print('{"_meta": {"hostvars": {}}}')
|
||||
return
|
||||
|
||||
try:
|
||||
req = urllib.request.Request('https://api.linode.com/v4/linode/instances')
|
||||
req.add_header('Authorization', f'Bearer {token}')
|
||||
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
data = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
inventory = {"_meta": {"hostvars": {}}, "all": {"children": []}}
|
||||
groups = {}
|
||||
|
||||
for instance in data.get('data', []):
|
||||
if instance['status'] != 'running':
|
||||
continue
|
||||
|
||||
hostname = instance['label']
|
||||
ip = instance['ipv4'][0] if instance.get('ipv4') else None
|
||||
if not ip:
|
||||
continue
|
||||
|
||||
# Host vars
|
||||
tags = instance.get('tags', [])
|
||||
inventory['_meta']['hostvars'][hostname] = {
|
||||
'ansible_host': ip,
|
||||
'ansible_user': 'phlux',
|
||||
'linode_region': instance['region'],
|
||||
'linode_type': instance['type'],
|
||||
'linode_tags': tags,
|
||||
'is_k3s': 'k3s' in tags,
|
||||
'is_control_plane': 'control-plane' in tags,
|
||||
'is_worker_node': 'worker-node' in tags
|
||||
}
|
||||
|
||||
# Create groups
|
||||
for tag in tags:
|
||||
group = f"tag_{tag.replace('-', '_')}"
|
||||
if group not in groups:
|
||||
groups[group] = []
|
||||
groups[group].append(hostname)
|
||||
|
||||
region_group = f"region_{instance['region'].replace('-', '_')}"
|
||||
if region_group not in groups:
|
||||
groups[region_group] = []
|
||||
groups[region_group].append(hostname)
|
||||
|
||||
# Add groups to inventory
|
||||
for group, hosts in groups.items():
|
||||
inventory[group] = {"hosts": hosts}
|
||||
inventory['all']['children'].append(group)
|
||||
|
||||
print(json.dumps(inventory, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
print(f'{{"error": "{e}"}}', file=sys.stderr)
|
||||
print('{"_meta": {"hostvars": {}}}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -1,52 +1,46 @@
|
||||
---
|
||||
plugin: community.general.linode
|
||||
plugin: linode.cloud.instance
|
||||
api_token: "{{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||
|
||||
# Your Linode API token (will be injected by AWX credential)
|
||||
api_token: "{{ linode_api_token | default(lookup('env', 'LINODE_API_TOKEN')) }}"
|
||||
|
||||
# Use Linode labels as hostnames (exactly what you wanted!)
|
||||
strict: false
|
||||
|
||||
# Create groups based on Linode attributes
|
||||
# Create groups by Linode attributes
|
||||
keyed_groups:
|
||||
# Group by region: linode_region_us_southeast
|
||||
# Group by region: region_us_southeast
|
||||
- key: region
|
||||
prefix: linode_region
|
||||
prefix: region
|
||||
separator: "_"
|
||||
|
||||
# Group by instance type: linode_type_g6_standard_6
|
||||
# Group by type: type_g6_standard_6
|
||||
- key: type
|
||||
prefix: linode_type
|
||||
prefix: type
|
||||
separator: "_"
|
||||
|
||||
# Group by status: linode_status_running
|
||||
# Group by status: status_running
|
||||
- key: status
|
||||
prefix: linode_status
|
||||
prefix: status
|
||||
separator: "_"
|
||||
|
||||
# Group by tags: linode_tag_k3s, linode_tag_debian, etc.
|
||||
# Group by each tag: tag_k3s, tag_debian, etc.
|
||||
- key: tags
|
||||
prefix: linode_tag
|
||||
prefix: tag
|
||||
separator: "_"
|
||||
|
||||
# Create logical groups based on tags
|
||||
groups:
|
||||
k3s_cluster: "'k3s' in (tags|list)"
|
||||
control_plane: "'control-plane' in (tags|list)"
|
||||
worker_nodes: "'worker-node' in (tags|list)"
|
||||
debian_hosts: "'Debian' in (tags|list)"
|
||||
ubuntu_hosts: "'Ubuntu' in (tags|list)"
|
||||
|
||||
# Set host variables
|
||||
compose:
|
||||
# Use primary IPv4 as ansible_host
|
||||
ansible_host: ipv4[0]
|
||||
ansible_user: phlux
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
||||
|
||||
# Add convenience variables for your tags
|
||||
is_k3s: "'k3s' in (tags | default([]))"
|
||||
is_control_plane: "'control-plane' in (tags | default([]))"
|
||||
is_worker_node: "'worker-node' in (tags | default([]))"
|
||||
is_debian: "'Debian' in (tags | default([]))"
|
||||
is_ubuntu: "'Ubuntu' in (tags | default([]))"
|
||||
|
||||
# Optional: Only include running instances
|
||||
filters:
|
||||
- status == "running"
|
||||
|
||||
# Cache settings (optional but recommended)
|
||||
cache: true
|
||||
cache_plugin: memory
|
||||
cache_timeout: 300
|
||||
# Add convenience booleans
|
||||
is_k3s: "'k3s' in (tags|list)"
|
||||
is_control_plane: "'control-plane' in (tags|list)"
|
||||
is_worker_node: "'worker-node' in (tags|list)"
|
||||
is_debian: "'Debian' in (tags|list)"
|
||||
is_ubuntu: "'Ubuntu' in (tags|list)"
|
||||
|
33
playbooks/inventory/debug_collections.yml
Normal file
33
playbooks/inventory/debug_collections.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
- name: Debug Collections in Execution Environment
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Check installed collections
|
||||
ansible.builtin.shell: |
|
||||
echo "=== Ansible Collections Info ==="
|
||||
ansible-galaxy collection list
|
||||
echo ""
|
||||
echo "=== Specific Linode Collection Check ==="
|
||||
ansible-galaxy collection list | grep -i linode || echo "No Linode collections found"
|
||||
echo ""
|
||||
echo "=== Collection Paths ==="
|
||||
python3 -c "
|
||||
import ansible.plugins.inventory
|
||||
print('Inventory plugin paths:', ansible.plugins.inventory.__path__)
|
||||
"
|
||||
echo ""
|
||||
echo "=== Try to import linode.cloud ==="
|
||||
python3 -c "
|
||||
try:
|
||||
from ansible_collections.linode.cloud.plugins.inventory.inventory import InventoryModule
|
||||
print('linode.cloud.inventory plugin found!')
|
||||
except ImportError as e:
|
||||
print(f'Import error: {e}')
|
||||
"
|
||||
register: collection_debug
|
||||
ignore_errors: true
|
||||
|
||||
- name: Display collection debug info
|
||||
ansible.builtin.debug:
|
||||
var: collection_debug.stdout_lines
|
@@ -1,200 +1,26 @@
|
||||
---
|
||||
- name: Update Linode Dynamic Inventory
|
||||
- name: Sync Linode Hosts to AWX Inventory
|
||||
hosts: localhost
|
||||
gather_facts: true
|
||||
gather_facts: false
|
||||
connection: local
|
||||
|
||||
vars:
|
||||
# Override these variables as needed
|
||||
linode_inventory_output_dir: "/tmp/linode_inventory"
|
||||
inventory_format: "json" # or "ini"
|
||||
awx_integration: true
|
||||
cleanup_temp_files: false
|
||||
|
||||
# Git integration settings
|
||||
push_to_git: true
|
||||
|
||||
# AWX API integration (optional - for automatic project sync)
|
||||
awx_api_integration: false # Set to true if you want automatic project refresh
|
||||
# awx_host: "https://your-awx-host"
|
||||
# awx_project_id: "8" # Your project ID in AWX
|
||||
# awx_token: "{{ lookup('env', 'AWX_TOKEN') }}" # Set via credential or env var
|
||||
|
||||
# Optional filters
|
||||
include_only_running: false
|
||||
specific_regions: [] # e.g., ['us-east', 'us-west']
|
||||
specific_tags: [] # e.g., ['production', 'web']
|
||||
|
||||
pre_tasks:
|
||||
- name: Check for Linode API token (will be injected by AWX credential)
|
||||
ansible.builtin.fail:
|
||||
msg: "Linode API Token credential must be attached to this job template in AWX"
|
||||
when: linode_api_token is undefined or linode_api_token == ""
|
||||
|
||||
- name: Display configuration
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Linode Inventory Configuration:
|
||||
Output directory: {{ linode_inventory_output_dir }}
|
||||
Output format: {{ inventory_format }}
|
||||
AWX integration: {{ awx_integration }}
|
||||
Include only running: {{ include_only_running }}
|
||||
|
||||
roles:
|
||||
- role: roles/inventory/linode
|
||||
vars:
|
||||
linode_api_token: "{{ linode_api_token }}"
|
||||
- roles/inventory/linode
|
||||
|
||||
post_tasks:
|
||||
- name: Display next steps
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Inventory update complete!
|
||||
|
||||
✅ Inventory file created: inventory/linode_hosts.json
|
||||
✅ Changes committed to Git: {{ 'Yes' if git_commit_result is defined and git_commit_result.rc == 0 else 'Check logs for details' }}
|
||||
✅ Repository: git@git.ewnix.net:phlux/ewnix-automation.git
|
||||
|
||||
Next steps:
|
||||
1. {% if not (awx_api_integration | default(false)) %}Manually sync your AWX project to pull the latest inventory{% else %}Project sync triggered automatically{% endif %}
|
||||
2. Create a new inventory source in AWX:
|
||||
- Source: "Sourced from a Project"
|
||||
- Inventory File: "inventory/linode_hosts.json"
|
||||
- No credential needed (it's a static file)
|
||||
3. Sync the inventory source to import your Linode hosts
|
||||
|
||||
Your Linode hosts will be available in groups:
|
||||
- tag_k3s (k3s cluster nodes)
|
||||
- tag_control_plane (control plane nodes)
|
||||
- tag_worker_node (worker nodes)
|
||||
- region_us_southeast (regional grouping)
|
||||
- type_* (by instance type)
|
||||
|
||||
Check the role output above for the exact number of hosts discovered and their details.
|
||||
|
||||
# Optional: Run against discovered Linode hosts
|
||||
- name: Debug and use discovered Linode hosts
|
||||
hosts: localhost
|
||||
# Optional: Example of using the discovered hosts
|
||||
- name: Test connectivity to discovered Linode hosts
|
||||
hosts: linode_all
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Check if inventory file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ linode_inventory_output_dir | default('/tmp/linode_inventory') }}/{{ linode_inventory_output_file | default('linode_inventory.json') }}"
|
||||
register: inventory_file_stat
|
||||
|
||||
- name: Display inventory file status
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Inventory file path: {{ linode_inventory_output_dir | default('/tmp/linode_inventory') }}/{{ linode_inventory_output_file | default('linode_inventory.json') }}
|
||||
File exists: {{ inventory_file_stat.stat.exists }}
|
||||
File size: {{ inventory_file_stat.stat.size | default(0) }} bytes
|
||||
|
||||
- name: Load and display inventory contents
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ linode_inventory_output_dir | default('/tmp/linode_inventory') }}/{{ linode_inventory_output_file | default('linode_inventory.json') }}"
|
||||
register: inventory_content
|
||||
when: inventory_file_stat.stat.exists
|
||||
|
||||
- name: Parse inventory JSON
|
||||
ansible.builtin.set_fact:
|
||||
dynamic_inventory: "{{ inventory_content.content | b64decode | from_json }}"
|
||||
when: inventory_file_stat.stat.exists
|
||||
|
||||
- name: Display parsed inventory summary
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Inventory loaded successfully!
|
||||
Total hostvars: {{ dynamic_inventory._meta.hostvars | length }}
|
||||
Groups: {{ dynamic_inventory.keys() | reject('equalto', '_meta') | list }}
|
||||
Hosts in hostvars: {{ dynamic_inventory._meta.hostvars.keys() | list }}
|
||||
when: dynamic_inventory is defined
|
||||
|
||||
- name: Add discovered hosts to in-memory inventory
|
||||
ansible.builtin.add_host:
|
||||
name: "{{ item.key }}"
|
||||
groups: discovered_linodes
|
||||
ansible_host: "{{ item.value.ansible_host }}"
|
||||
linode_id: "{{ item.value.linode_id }}"
|
||||
linode_region: "{{ item.value.linode_region }}"
|
||||
linode_type: "{{ item.value.linode_type }}"
|
||||
linode_status: "{{ item.value.linode_status }}"
|
||||
linode_tags: "{{ item.value.linode_tags }}"
|
||||
is_debian: "{{ item.value.is_debian }}"
|
||||
is_ubuntu: "{{ item.value.is_ubuntu }}"
|
||||
is_k3s: "{{ item.value.is_k3s }}"
|
||||
is_control_plane: "{{ item.value.is_control_plane }}"
|
||||
is_worker_node: "{{ item.value.is_worker_node }}"
|
||||
tag_string: "{{ item.value.tag_string }}"
|
||||
loop: "{{ dynamic_inventory._meta.hostvars | dict2items }}"
|
||||
when:
|
||||
- dynamic_inventory is defined
|
||||
- item.value.linode_status == "running"
|
||||
|
||||
- name: Display added hosts with tag information
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Added {{ groups['discovered_linodes'] | default([]) | length }} running Linode hosts to inventory
|
||||
|
||||
Host details:
|
||||
{% for host in groups['discovered_linodes'] | default([]) %}
|
||||
- {{ host }} ({{ hostvars[host]['ansible_host'] }})
|
||||
Tags: {{ hostvars[host]['linode_tags'] | join(', ') }}
|
||||
K3s: {{ hostvars[host]['is_k3s'] }}
|
||||
Control Plane: {{ hostvars[host]['is_control_plane'] }}
|
||||
Worker: {{ hostvars[host]['is_worker_node'] }}
|
||||
{% endfor %}
|
||||
|
||||
- name: Test connection to discovered Linode hosts
|
||||
hosts: discovered_linodes
|
||||
gather_facts: false
|
||||
vars:
|
||||
ansible_user: phlux
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=10'
|
||||
tasks:
|
||||
- name: Test connectivity
|
||||
- name: Test connectivity to Linode hosts
|
||||
ansible.builtin.ping:
|
||||
register: ping_result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Display connectivity status with tag info
|
||||
- name: Display connectivity results
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
{{ inventory_hostname }} ({{ ansible_host }}): {{ 'REACHABLE' if ping_result is succeeded else 'UNREACHABLE' }}
|
||||
{{ inventory_hostname }}: {{ 'REACHABLE' if ping_result is succeeded else 'UNREACHABLE' }}
|
||||
IP: {{ ansible_host }}
|
||||
Tags: {{ linode_tags | join(', ') }}
|
||||
Role: {{ 'Control Plane' if is_control_plane else 'Worker Node' if is_worker_node else 'Other' }}
|
||||
|
||||
# Example: Run tasks only on k3s control plane nodes
|
||||
- name: Example - Control Plane specific tasks
|
||||
hosts: discovered_linodes
|
||||
gather_facts: false
|
||||
vars:
|
||||
ansible_user: phlux
|
||||
tasks:
|
||||
- name: Control plane specific task
|
||||
ansible.builtin.debug:
|
||||
msg: "This would run control plane specific commands on {{ inventory_hostname }}"
|
||||
when: is_control_plane | bool
|
||||
|
||||
# Example: Run tasks only on k3s worker nodes
|
||||
- name: Example - Worker Node specific tasks
|
||||
hosts: discovered_linodes
|
||||
gather_facts: false
|
||||
vars:
|
||||
ansible_user: phlux
|
||||
tasks:
|
||||
- name: Worker node specific task
|
||||
ansible.builtin.debug:
|
||||
msg: "This would run worker node specific commands on {{ inventory_hostname }}"
|
||||
when: is_worker_node | bool
|
||||
|
||||
# Example: Run tasks on all k3s nodes (control plane + workers)
|
||||
- name: Example - All K3s nodes
|
||||
hosts: discovered_linodes
|
||||
gather_facts: false
|
||||
vars:
|
||||
ansible_user: phlux
|
||||
tasks:
|
||||
- name: K3s cluster task
|
||||
ansible.builtin.debug:
|
||||
msg: "This would run on all k3s nodes: {{ inventory_hostname }}"
|
||||
when: is_k3s | bool
|
||||
|
46
playbooks/tests/check_plugins.yml
Normal file
46
playbooks/tests/check_plugins.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
- name: Check Available Inventory Plugins
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: List all available inventory plugins
|
||||
ansible.builtin.shell: |
|
||||
echo "=== All inventory plugins ==="
|
||||
ansible-doc -t inventory -l 2>/dev/null || echo "ansible-doc failed"
|
||||
echo ""
|
||||
echo "=== Check linode specific plugins ==="
|
||||
ansible-doc -t inventory -l 2>/dev/null | grep -i linode || echo "No linode plugins in doc list"
|
||||
echo ""
|
||||
echo "=== Check plugin files directly ==="
|
||||
find /runner/requirements_collections -name "*.py" -path "*/inventory/*" | grep -i linode || echo "No linode inventory plugins found in files"
|
||||
echo ""
|
||||
echo "=== Collection structure ==="
|
||||
ls -la /runner/requirements_collections/ansible_collections/linode/cloud/plugins/inventory/ 2>/dev/null || echo "Linode inventory directory not found"
|
||||
register: plugin_check
|
||||
ignore_errors: true
|
||||
|
||||
- name: Display plugin check results
|
||||
ansible.builtin.debug:
|
||||
var: plugin_check.stdout_lines
|
||||
|
||||
- name: Test inventory plugin directly
|
||||
ansible.builtin.shell: |
|
||||
echo "=== Test plugin path ==="
|
||||
export ANSIBLE_COLLECTIONS_PATH="/runner/requirements_collections:/usr/share/ansible/collections"
|
||||
ansible-inventory --list -i /dev/stdin << 'EOF'
|
||||
plugin: linode.cloud.inventory
|
||||
access_token: "fake_token_for_test"
|
||||
EOF
|
||||
register: plugin_test
|
||||
ignore_errors: true
|
||||
environment:
|
||||
LINODE_API_TOKEN: "test"
|
||||
|
||||
- name: Display plugin test results
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Plugin test output:
|
||||
{{ plugin_test.stdout }}
|
||||
|
||||
Plugin test error:
|
||||
{{ plugin_test.stderr }}
|
19
playbooks/tests/debug_host_vars.yml
Normal file
19
playbooks/tests/debug_host_vars.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
- name: Ensure correct SSH user for all Linode hosts
|
||||
hosts: debian_hosts
|
||||
gather_facts: false
|
||||
vars:
|
||||
ansible_user: phlux
|
||||
ansible_ssh_user: phlux
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
||||
tasks:
|
||||
- name: Test connectivity with correct user
|
||||
ansible.builtin.ping:
|
||||
register: ping_result
|
||||
|
||||
- name: Display connection results
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
{{ inventory_hostname }}: {{ 'SUCCESS' if ping_result is succeeded else 'FAILED' }}
|
||||
Connected as: {{ ansible_user }}
|
||||
IP: {{ ansible_host }}
|
20
playbooks/tests/inv_formats.yml
Normal file
20
playbooks/tests/inv_formats.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
plugin: ansible_collections.linode.cloud.plugins.inventory.inventory
|
||||
access_token: "{{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||
compose:
|
||||
ansible_host: ipv4[0]
|
||||
ansible_user: phlux
|
||||
|
||||
---
|
||||
plugin: linode.cloud.inventory
|
||||
access_token: "{{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||
compose:
|
||||
ansible_host: ipv4[0]
|
||||
ansible_user: phlux
|
||||
|
||||
---
|
||||
plugin: community.general.linode
|
||||
api_token: "{{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||
compose:
|
||||
ansible_host: ipv4[0]
|
||||
ansible_user: phlux
|
37
playbooks/tests/test_inv_with_collection_path.yml
Normal file
37
playbooks/tests/test_inv_with_collection_path.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
- name: Test Inventory with Collection Paths
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Test inventory with explicit collection path
|
||||
ansible.builtin.shell: |
|
||||
export ANSIBLE_COLLECTIONS_PATH="/runner/requirements_collections:/usr/share/ansible/collections"
|
||||
export LINODE_API_TOKEN="${LINODE_API_TOKEN}"
|
||||
|
||||
# Create temp inventory file
|
||||
cat > /tmp/test_inventory.yml << 'EOF'
|
||||
plugin: linode.cloud.inventory
|
||||
access_token: "{{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||
compose:
|
||||
ansible_host: ipv4[0]
|
||||
ansible_user: phlux
|
||||
EOF
|
||||
|
||||
echo "=== Testing inventory ==="
|
||||
ansible-inventory -i /tmp/test_inventory.yml --list
|
||||
register: inventory_test
|
||||
ignore_errors: true
|
||||
environment:
|
||||
ANSIBLE_COLLECTIONS_PATH: "/runner/requirements_collections:/usr/share/ansible/collections"
|
||||
LINODE_API_TOKEN: "{{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||
|
||||
- name: Display inventory test results
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Return code: {{ inventory_test.rc }}
|
||||
|
||||
Stdout:
|
||||
{{ inventory_test.stdout }}
|
||||
|
||||
Stderr:
|
||||
{{ inventory_test.stderr }}
|
@@ -1,26 +1,17 @@
|
||||
---
|
||||
# Default variables for linode_inventory role
|
||||
|
||||
# Linode API settings
|
||||
linode_api_url: "https://api.linode.com/v4"
|
||||
linode_inventory_output_dir: "/tmp"
|
||||
linode_inventory_output_file: "linode_inventory.json"
|
||||
|
||||
# Inventory grouping options
|
||||
create_region_groups: true
|
||||
create_type_groups: true
|
||||
create_status_groups: true
|
||||
create_tag_groups: true
|
||||
|
||||
# Default groups to create
|
||||
default_groups:
|
||||
- all
|
||||
- ungrouped
|
||||
|
||||
# Filter options
|
||||
include_only_running: false
|
||||
specific_regions: []
|
||||
specific_tags: []
|
||||
include_only_running: true
|
||||
specific_regions: [] # e.g., ['us-east', 'us-west']
|
||||
specific_tags: [] # e.g., ['production', 'web']
|
||||
|
||||
# Output format
|
||||
inventory_format: "json" # json or ini
|
||||
# Default SSH settings for discovered hosts
|
||||
default_ansible_user: "phlux"
|
||||
default_ssh_args: "-o StrictHostKeyChecking=no"
|
||||
|
||||
# Group naming options
|
||||
group_prefix_region: "region_"
|
||||
group_prefix_type: "type_"
|
||||
group_prefix_tag: "tag_"
|
||||
group_prefix_status: "status_"
|
||||
|
@@ -1,208 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from argparse import ArgumentParser
|
||||
|
||||
class LinodeInventory:
|
||||
def __init__(self):
|
||||
self.api_token = os.environ.get('LINODE_API_TOKEN')
|
||||
if not self.api_token:
|
||||
raise ValueError("LINODE_API_TOKEN environment variable is required")
|
||||
|
||||
self.base_url = "https://api.linode.com/v4"
|
||||
|
||||
def make_request(self, endpoint):
|
||||
"""Make HTTP request to Linode API using urllib"""
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('Authorization', f'Bearer {self.api_token}')
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
data = response.read().decode('utf-8')
|
||||
return json.loads(data)
|
||||
except urllib.error.HTTPError as e:
|
||||
error_msg = e.read().decode('utf-8') if e.fp else str(e)
|
||||
raise Exception(f"HTTP Error {e.code}: {error_msg}")
|
||||
except urllib.error.URLError as e:
|
||||
raise Exception(f"URL Error: {e}")
|
||||
except Exception as e:
|
||||
raise Exception(f"Request failed: {e}")
|
||||
|
||||
def get_instances(self):
|
||||
"""Fetch all Linode instances"""
|
||||
try:
|
||||
# Get first page to check pagination
|
||||
response = self.make_request("/linode/instances?page=1")
|
||||
instances = response['data']
|
||||
|
||||
# Handle pagination if there are more pages
|
||||
if response.get('pages', 1) > 1:
|
||||
for page in range(2, response['pages'] + 1):
|
||||
page_response = self.make_request(f"/linode/instances?page={page}")
|
||||
instances.extend(page_response['data'])
|
||||
|
||||
return instances
|
||||
except Exception as e:
|
||||
print(f"Error fetching instances: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
def generate_inventory(self):
|
||||
"""Generate Ansible inventory from Linode instances"""
|
||||
inventory = {
|
||||
'_meta': {
|
||||
'hostvars': {}
|
||||
},
|
||||
'all': {
|
||||
'children': ['ungrouped']
|
||||
},
|
||||
'ungrouped': {
|
||||
'hosts': []
|
||||
}
|
||||
}
|
||||
|
||||
# Group definitions
|
||||
regions = {}
|
||||
types = {}
|
||||
statuses = {}
|
||||
|
||||
try:
|
||||
instances = self.get_instances()
|
||||
|
||||
if not instances:
|
||||
print("No instances found or API request failed", file=sys.stderr)
|
||||
return inventory
|
||||
|
||||
for instance in instances:
|
||||
# Use Linode label as hostname
|
||||
hostname = instance['label']
|
||||
|
||||
# Get primary IPv4 address
|
||||
ipv4_addresses = instance.get('ipv4', [])
|
||||
primary_ip = ipv4_addresses[0] if ipv4_addresses else None
|
||||
|
||||
if not primary_ip:
|
||||
print(f"Warning: No IPv4 address found for {hostname}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
# Add to ungrouped hosts
|
||||
inventory['ungrouped']['hosts'].append(hostname)
|
||||
|
||||
# Host variables
|
||||
linode_tags = instance.get('tags', [])
|
||||
inventory['_meta']['hostvars'][hostname] = {
|
||||
'ansible_host': primary_ip,
|
||||
'ansible_user': 'phlux', # Set default SSH user
|
||||
'linode_id': instance['id'],
|
||||
'linode_label': instance['label'],
|
||||
'linode_region': instance['region'],
|
||||
'linode_type': instance['type'],
|
||||
'linode_status': instance['status'],
|
||||
'linode_ipv4': instance.get('ipv4', []),
|
||||
'linode_ipv6': instance.get('ipv6'),
|
||||
'linode_tags': linode_tags,
|
||||
'linode_specs': instance.get('specs', {}),
|
||||
'linode_hypervisor': instance.get('hypervisor'),
|
||||
'linode_created': instance.get('created'),
|
||||
'linode_updated': instance.get('updated'),
|
||||
'linode_group': instance.get('group', ''),
|
||||
'linode_image': instance.get('image', {}).get('id', '') if isinstance(instance.get('image'), dict) else '',
|
||||
'linode_backups': instance.get('backups', {}).get('enabled', False) if isinstance(instance.get('backups'), dict) else False,
|
||||
# Add individual tag variables for easy access
|
||||
'is_debian': 'Debian' in linode_tags,
|
||||
'is_ubuntu': 'Ubuntu' in linode_tags,
|
||||
'is_k3s': 'k3s' in linode_tags,
|
||||
'is_control_plane': 'control-plane' in linode_tags,
|
||||
'is_worker_node': 'worker-node' in linode_tags,
|
||||
# Tag string for easy filtering
|
||||
'tag_string': ','.join(linode_tags).lower(),
|
||||
}
|
||||
|
||||
# Group by region
|
||||
region_group = f"region_{instance['region'].replace('-', '_')}"
|
||||
if region_group not in regions:
|
||||
regions[region_group] = {'hosts': []}
|
||||
regions[region_group]['hosts'].append(hostname)
|
||||
|
||||
# Group by instance type
|
||||
type_group = f"type_{instance['type'].replace('-', '_').replace('.', '_')}"
|
||||
if type_group not in types:
|
||||
types[type_group] = {'hosts': []}
|
||||
types[type_group]['hosts'].append(hostname)
|
||||
|
||||
# Group by status
|
||||
status_group = f"status_{instance['status']}"
|
||||
if status_group not in statuses:
|
||||
statuses[status_group] = {'hosts': []}
|
||||
statuses[status_group]['hosts'].append(hostname)
|
||||
|
||||
# Group by tags
|
||||
for tag in instance.get('tags', []):
|
||||
tag_group = f"tag_{tag.replace('-', '_').replace(' ', '_').replace('.', '_')}"
|
||||
if tag_group not in inventory:
|
||||
inventory[tag_group] = {'hosts': []}
|
||||
inventory[tag_group]['hosts'].append(hostname)
|
||||
|
||||
# Group by Linode group (if set)
|
||||
if instance.get('group'):
|
||||
group_name = f"group_{instance['group'].replace('-', '_').replace(' ', '_')}"
|
||||
if group_name not in inventory:
|
||||
inventory[group_name] = {'hosts': []}
|
||||
inventory[group_name]['hosts'].append(hostname)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error generating inventory: {e}", file=sys.stderr)
|
||||
return inventory
|
||||
|
||||
# Add all groups to inventory
|
||||
inventory.update(regions)
|
||||
inventory.update(types)
|
||||
inventory.update(statuses)
|
||||
|
||||
# Add group children to 'all'
|
||||
all_groups = list(regions.keys()) + list(types.keys()) + list(statuses.keys())
|
||||
tag_groups = [k for k in inventory.keys() if k.startswith('tag_')]
|
||||
group_groups = [k for k in inventory.keys() if k.startswith('group_')]
|
||||
all_groups.extend(tag_groups)
|
||||
all_groups.extend(group_groups)
|
||||
|
||||
if all_groups:
|
||||
inventory['all']['children'].extend(all_groups)
|
||||
|
||||
return inventory
|
||||
|
||||
def get_host_vars(self, hostname):
|
||||
"""Get variables for a specific host"""
|
||||
inventory = self.generate_inventory()
|
||||
return inventory['_meta']['hostvars'].get(hostname, {})
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(description='Linode Dynamic Inventory')
|
||||
parser.add_argument('--list', action='store_true', help='List all hosts')
|
||||
parser.add_argument('--host', help='Get variables for specific host')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
inventory = LinodeInventory()
|
||||
|
||||
if args.list:
|
||||
result = inventory.generate_inventory()
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.host:
|
||||
result = inventory.get_host_vars(args.host)
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -1,30 +0,0 @@
|
||||
---
|
||||
galaxy_info:
|
||||
author: Kevin M Thompson
|
||||
description: Ansible role for Linode dynamic inventory management
|
||||
company: Ewnix
|
||||
license: MIT
|
||||
min_ansible_version: 2.9
|
||||
platforms:
|
||||
- name: EL
|
||||
versions:
|
||||
- 7
|
||||
- 8
|
||||
- 9
|
||||
- name: Ubuntu
|
||||
versions:
|
||||
- 18.04
|
||||
- 20.04
|
||||
- 22.04
|
||||
- name: Debian
|
||||
versions:
|
||||
- 10
|
||||
- 11
|
||||
galaxy_tags:
|
||||
- linode
|
||||
- inventory
|
||||
- dynamic
|
||||
- cloud
|
||||
- automation
|
||||
|
||||
dependencies: []
|
@@ -1,214 +1,83 @@
|
||||
---
|
||||
# Main tasks for linode_inventory role
|
||||
|
||||
- name: Set API token (AWX credential injection takes precedence)
|
||||
ansible.builtin.set_fact:
|
||||
linode_api_token: "{{ linode_api_token | default(lookup('env', 'LINODE_API_TOKEN')) | default('') }}"
|
||||
- name: Validate Linode API token
|
||||
ansible.builtin.fail:
|
||||
msg: "LINODE_API_TOKEN environment variable must be set"
|
||||
when: lookup('env', 'LINODE_API_TOKEN') == ""
|
||||
|
||||
- name: Validate required variables
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- linode_api_token is defined
|
||||
- linode_api_token | length > 0
|
||||
fail_msg: |
|
||||
Linode API token not found.
|
||||
For AWX: Attach a Linode API Token credential to your job template
|
||||
For local: Set LINODE_API_TOKEN environment variable or pass linode_api_token variable
|
||||
quiet: true
|
||||
- name: Fetch Linode instances
|
||||
ansible.builtin.uri:
|
||||
url: "https://api.linode.com/v4/linode/instances"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "Bearer {{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||
Content-Type: "application/json"
|
||||
return_content: yes
|
||||
status_code: 200
|
||||
register: linode_response
|
||||
|
||||
- name: Ensure output directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ linode_inventory_output_dir }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Copy Linode inventory script
|
||||
ansible.builtin.copy:
|
||||
src: linode_inventory.py
|
||||
dest: "{{ linode_inventory_output_dir }}/linode_inventory.py"
|
||||
mode: '0755'
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Execute Linode inventory script
|
||||
ansible.builtin.command:
|
||||
cmd: python3 {{ linode_inventory_output_dir }}/linode_inventory.py --list
|
||||
environment:
|
||||
LINODE_API_TOKEN: "{{ linode_api_token }}"
|
||||
register: linode_inventory_result
|
||||
delegate_to: localhost
|
||||
changed_when: true
|
||||
|
||||
- name: Parse inventory JSON (only if stdout exists)
|
||||
ansible.builtin.set_fact:
|
||||
linode_inventory_data: "{{ linode_inventory_result.stdout | from_json }}"
|
||||
when:
|
||||
- linode_inventory_result.stdout | length > 0
|
||||
- linode_inventory_result.rc == 0
|
||||
|
||||
- name: Set empty inventory if script failed
|
||||
ansible.builtin.set_fact:
|
||||
linode_inventory_data:
|
||||
_meta:
|
||||
hostvars: {}
|
||||
all:
|
||||
children: ['ungrouped']
|
||||
ungrouped:
|
||||
hosts: []
|
||||
when: linode_inventory_data is not defined
|
||||
|
||||
- name: Display inventory summary
|
||||
- name: Display API response summary
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Linode Dynamic Inventory Summary:
|
||||
Total hosts discovered: {{ linode_inventory_data._meta.hostvars | length }}
|
||||
Groups created: {{ linode_inventory_data.keys() | reject('equalto', '_meta') | list | length }}
|
||||
Found {{ linode_response.json.data | length }} Linode instances
|
||||
Running instances: {{ linode_response.json.data | selectattr('status', 'equalto', 'running') | list | length }}
|
||||
|
||||
- name: Show discovered hosts
|
||||
ansible.builtin.debug:
|
||||
msg: "Host: {{ item.key }} ({{ item.value.ansible_host }}) - Region: {{ item.value.linode_region }} - Status: {{ item.value.linode_status }}"
|
||||
loop: "{{ linode_inventory_data._meta.hostvars | dict2items }}"
|
||||
- name: Add Linode hosts to in-memory inventory
|
||||
ansible.builtin.add_host:
|
||||
name: "{{ item.label }}"
|
||||
groups:
|
||||
- linode_all
|
||||
- "region_{{ item.region | replace('-', '_') }}"
|
||||
- "type_{{ item.type | replace('-', '_') | replace('.', '_') }}"
|
||||
- "status_{{ item.status }}"
|
||||
- "{% for tag in item.tags %}tag_{{ tag | replace('-', '_') | replace(' ', '_') | replace('.', '_') }}{% if not loop.last %},{% endif %}{% endfor %}"
|
||||
ansible_host: "{{ item.ipv4[0] }}"
|
||||
ansible_user: "phlux"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||
linode_id: "{{ item.id }}"
|
||||
linode_label: "{{ item.label }}"
|
||||
linode_region: "{{ item.region }}"
|
||||
linode_type: "{{ item.type }}"
|
||||
linode_status: "{{ item.status }}"
|
||||
linode_tags: "{{ item.tags }}"
|
||||
linode_ipv4: "{{ item.ipv4 }}"
|
||||
linode_ipv6: "{{ item.ipv6 | default('') }}"
|
||||
# Convenience boolean flags
|
||||
is_k3s: "{{ 'k3s' in item.tags }}"
|
||||
is_control_plane: "{{ 'control-plane' in item.tags }}"
|
||||
is_worker_node: "{{ 'worker-node' in item.tags }}"
|
||||
is_debian: "{{ 'Debian' in item.tags }}"
|
||||
is_ubuntu: "{{ 'Ubuntu' in item.tags }}"
|
||||
loop: "{{ linode_response.json.data }}"
|
||||
when:
|
||||
- item.status == "running"
|
||||
- item.ipv4 | length > 0
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
when: linode_inventory_data._meta.hostvars | length > 0
|
||||
label: "{{ item.label }}"
|
||||
|
||||
- name: Debug current directory and Git status
|
||||
ansible.builtin.shell: |
|
||||
echo "Current directory: $(pwd)" &&
|
||||
echo "Directory contents:" &&
|
||||
ls -la &&
|
||||
echo "Git status:" &&
|
||||
git status 2>&1 || echo "Not a git repository" &&
|
||||
echo "Git remote:" &&
|
||||
git remote -v 2>&1 || echo "No git remotes"
|
||||
register: debug_git_info
|
||||
delegate_to: localhost
|
||||
ignore_errors: true
|
||||
|
||||
- name: Initialize Git if needed and configure
|
||||
ansible.builtin.shell: |
|
||||
if [ ! -d ".git" ]; then
|
||||
echo "Not in a git repository, checking for git in parent directories"
|
||||
git_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
|
||||
if [ -n "$git_root" ]; then
|
||||
cd "$git_root"
|
||||
echo "Found git repository at: $git_root"
|
||||
else
|
||||
echo "No git repository found"
|
||||
exit 0
|
||||
fi
|
||||
fi &&
|
||||
git config user.email "awx@ewnix.net" &&
|
||||
git config user.name "AWX Automation" &&
|
||||
git remote set-url origin git@git.ewnix.net:phlux/ewnix-automation.git &&
|
||||
echo "Git configured successfully"
|
||||
register: git_config_result
|
||||
delegate_to: localhost
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check SSH configuration and keys
|
||||
ansible.builtin.shell: |
|
||||
echo "=== SSH configuration ==="
|
||||
echo "SSH_AUTH_SOCK: ${SSH_AUTH_SOCK:-not set}"
|
||||
echo "=== SSH keys available ==="
|
||||
ssh-add -l 2>/dev/null || echo "No SSH agent or no keys loaded"
|
||||
echo "=== SSH config ==="
|
||||
ls -la ~/.ssh/ 2>/dev/null || echo "No .ssh directory"
|
||||
echo "=== Test SSH to git.ewnix.net ==="
|
||||
timeout 10 ssh -T git@git.ewnix.net -o ConnectTimeout=5 -o StrictHostKeyChecking=no 2>&1 || echo "SSH test completed"
|
||||
register: ssh_debug
|
||||
delegate_to: localhost
|
||||
ignore_errors: true
|
||||
|
||||
- name: Create inventory directory
|
||||
ansible.builtin.file:
|
||||
path: "inventory"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create JSON inventory file
|
||||
ansible.builtin.copy:
|
||||
content: "{{ linode_inventory_data | to_nice_json }}"
|
||||
dest: "inventory/linode_hosts.json"
|
||||
mode: '0644'
|
||||
delegate_to: localhost
|
||||
when: linode_inventory_data is defined
|
||||
|
||||
- name: Check if inventory file was created
|
||||
ansible.builtin.stat:
|
||||
path: "inventory/linode_hosts.json"
|
||||
register: inventory_file_check
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Attempt to commit and push inventory
|
||||
ansible.builtin.shell: |
|
||||
git_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
|
||||
if [ -n "$git_root" ]; then
|
||||
cd "$git_root" &&
|
||||
echo "Working in git repository: $git_root" &&
|
||||
git add inventory/linode_hosts.json &&
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git commit -m "Update Linode inventory - $(date '+%Y-%m-%d %H:%M:%S') [AWX]" &&
|
||||
echo "Committed successfully, attempting push..." &&
|
||||
timeout 30 git push origin HEAD &&
|
||||
echo "Push successful!"
|
||||
fi
|
||||
else
|
||||
echo "No git repository found - inventory file created but not committed"
|
||||
fi
|
||||
register: git_commit_result
|
||||
delegate_to: localhost
|
||||
ignore_errors: true
|
||||
when:
|
||||
- linode_inventory_data is defined
|
||||
- push_to_git | default(true)
|
||||
- inventory_file_check.stat.exists
|
||||
|
||||
- name: Display comprehensive debug information
|
||||
- name: Display discovered hosts summary
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
=== COMPREHENSIVE DEBUG RESULTS ===
|
||||
🎉 Successfully added {{ groups['linode_all'] | default([]) | length }} Linode hosts to inventory!
|
||||
|
||||
Current Directory Info:
|
||||
{% if debug_git_info is defined and debug_git_info.stdout is defined %}
|
||||
{{ debug_git_info.stdout }}
|
||||
{% else %}
|
||||
Debug info not available
|
||||
{% endif %}
|
||||
📋 Hosts discovered:
|
||||
{% for host in groups['linode_all'] | default([]) %}
|
||||
- {{ host }} ({{ hostvars[host]['ansible_host'] }})
|
||||
Region: {{ hostvars[host]['linode_region'] }}
|
||||
Type: {{ hostvars[host]['linode_type'] }}
|
||||
Tags: {{ hostvars[host]['linode_tags'] | join(', ') }}
|
||||
{% endfor %}
|
||||
|
||||
SSH Configuration:
|
||||
{% if ssh_debug is defined and ssh_debug.stdout is defined %}
|
||||
{{ ssh_debug.stdout }}
|
||||
{% else %}
|
||||
SSH debug not available
|
||||
{% endif %}
|
||||
📂 Groups available for targeting:
|
||||
{% for group in groups.keys() | sort if group.startswith(('tag_', 'region_', 'type_', 'status_')) %}
|
||||
- {{ group }}: {{ groups[group] | length }} hosts
|
||||
{% endfor %}
|
||||
|
||||
Git Configuration:
|
||||
{% if git_config_result is defined and git_config_result.stdout is defined %}
|
||||
{{ git_config_result.stdout }}
|
||||
{% else %}
|
||||
Git config not available
|
||||
{% endif %}
|
||||
💡 Use these groups in your job templates:
|
||||
- tag_k3s: All k3s cluster nodes
|
||||
- tag_control_plane: Control plane nodes only
|
||||
- tag_worker_node: Worker nodes only
|
||||
- region_us_southeast: All hosts in us-southeast
|
||||
|
||||
Git Commit/Push Results:
|
||||
{% if git_commit_result is defined and git_commit_result.stdout is defined %}
|
||||
{{ git_commit_result.stdout }}
|
||||
{% else %}
|
||||
Git commit result not available
|
||||
{% endif %}
|
||||
|
||||
Inventory File Status:
|
||||
- Exists: {{ inventory_file_check.stat.exists if inventory_file_check is defined else 'Unknown' }}
|
||||
{% if inventory_file_check is defined and inventory_file_check.stat.exists %}
|
||||
- Size: {{ inventory_file_check.stat.size }} bytes
|
||||
{% endif %}
|
||||
|
||||
- name: Clean up temporary script
|
||||
ansible.builtin.file:
|
||||
path: "{{ linode_inventory_output_dir }}/linode_inventory.py"
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
when: cleanup_temp_files | default(true)
|
||||
✅ Hosts are now available for subsequent job templates in this workflow!
|
||||
|
@@ -1,107 +0,0 @@
|
||||
#!/bin/bash
|
||||
# AWX Inventory Update Script
|
||||
# Generated on: {{ ansible_date_time.iso8601 }}
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
LINODE_INVENTORY_SCRIPT="{{ linode_inventory_output_dir }}/linode_inventory.py"
|
||||
INVENTORY_OUTPUT="{{ linode_inventory_output_dir }}/{{ linode_inventory_output_file }}"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites() {
|
||||
log "Checking prerequisites..."
|
||||
|
||||
if [[ -z "${LINODE_API_TOKEN:-}" ]]; then
|
||||
error "LINODE_API_TOKEN environment variable is not set"
|
||||
fi
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
error "python3 is not installed"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$LINODE_INVENTORY_SCRIPT" ]]; then
|
||||
error "Inventory script not found: $LINODE_INVENTORY_SCRIPT"
|
||||
fi
|
||||
|
||||
log "Prerequisites check passed"
|
||||
}
|
||||
|
||||
# Update inventory
|
||||
update_inventory() {
|
||||
log "Updating Linode inventory..."
|
||||
|
||||
if ! python3 "$LINODE_INVENTORY_SCRIPT" --list > "$INVENTORY_OUTPUT.tmp"; then
|
||||
error "Failed to generate inventory"
|
||||
fi
|
||||
|
||||
# Validate JSON
|
||||
if ! python3 -m json.tool "$INVENTORY_OUTPUT.tmp" >/dev/null 2>&1; then
|
||||
error "Generated inventory is not valid JSON"
|
||||
fi
|
||||
|
||||
# Move to final location
|
||||
mv "$INVENTORY_OUTPUT.tmp" "$INVENTORY_OUTPUT"
|
||||
|
||||
log "Inventory updated successfully: $INVENTORY_OUTPUT"
|
||||
}
|
||||
|
||||
# Display summary
|
||||
display_summary() {
|
||||
if [[ -f "$INVENTORY_OUTPUT" ]]; then
|
||||
local host_count
|
||||
host_count=$(python3 -c "import json; data=json.load(open('$INVENTORY_OUTPUT')); print(len(data.get('_meta', {}).get('hostvars', {})))")
|
||||
|
||||
local group_count
|
||||
group_count=$(python3 -c "import json; data=json.load(open('$INVENTORY_OUTPUT')); print(len([k for k in data.keys() if k != '_meta']))")
|
||||
|
||||
log "Inventory Summary:"
|
||||
log " Hosts discovered: $host_count"
|
||||
log " Groups created: $group_count"
|
||||
log " Output file: $INVENTORY_OUTPUT"
|
||||
|
||||
# List hosts
|
||||
log "Discovered hosts:"
|
||||
python3 -c "
|
||||
import json
|
||||
data = json.load(open('$INVENTORY_OUTPUT'))
|
||||
for hostname, vars in data.get('_meta', {}).get('hostvars', {}).items():
|
||||
print(f' - {hostname} ({vars.get(\"ansible_host\", \"no-ip\")}) - {vars.get(\"linode_region\", \"unknown\")} - {vars.get(\"linode_status\", \"unknown\")}')
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log "Starting AWX Linode inventory update"
|
||||
|
||||
check_prerequisites
|
||||
update_inventory
|
||||
display_summary
|
||||
|
||||
log "Inventory update completed successfully"
|
||||
}
|
||||
|
||||
# Run if executed directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
@@ -1,22 +0,0 @@
|
||||
# Generated Linode Inventory
|
||||
# Generated on: {{ ansible_date_time.iso8601 }}
|
||||
|
||||
{% for host_name, host_vars in linode_inventory_data._meta.hostvars.items() %}
|
||||
{{ host_name }} ansible_host={{ host_vars.ansible_host }} linode_id={{ host_vars.linode_id }} linode_region={{ host_vars.linode_region }} linode_type={{ host_vars.linode_type }} linode_status={{ host_vars.linode_status }}
|
||||
{% endfor %}
|
||||
|
||||
{% for group_name, group_data in linode_inventory_data.items() %}
|
||||
{% if group_name != '_meta' and group_name != 'all' %}
|
||||
[{{ group_name }}]
|
||||
{% if group_data.hosts is defined %}
|
||||
{% for host in group_data.hosts %}
|
||||
{{ host }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
[all:vars]
|
||||
ansible_user=phlux
|
||||
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
{% for host, vars in linode_inventory_data._meta.hostvars.items() %}
|
||||
"{{ host }}": {
|
||||
"ansible_host": "{{ vars.ansible_host }}",
|
||||
"ansible_user": "phlux",
|
||||
"linode_id": {{ vars.linode_id }},
|
||||
"linode_label": "{{ vars.linode_label }}",
|
||||
"linode_region": "{{ vars.linode_region }}",
|
||||
"linode_type": "{{ vars.linode_type }}",
|
||||
"linode_status": "{{ vars.linode_status }}",
|
||||
"linode_ipv4": {{ vars.linode_ipv4 | to_json }},
|
||||
"linode_ipv6": "{{ vars.linode_ipv6 | default('') }}",
|
||||
"linode_tags": {{ vars.linode_tags | to_json }},
|
||||
"is_debian": {{ vars.is_debian | lower }},
|
||||
"is_ubuntu": {{ vars.is_ubuntu | lower }},
|
||||
"is_k3s": {{ vars.is_k3s | lower }},
|
||||
"is_control_plane": {{ vars.is_control_plane | lower }},
|
||||
"is_worker_node": {{ vars.is_worker_node | lower }},
|
||||
"tag_string": "{{ vars.tag_string }}"
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
},
|
||||
"all": {
|
||||
"children": {{ linode_inventory_data.all.children | to_json }}
|
||||
},
|
||||
"ungrouped": {
|
||||
"hosts": {{ linode_inventory_data.ungrouped.hosts | to_json }}
|
||||
}{% for group_name, group_data in linode_inventory_data.items() %}{% if group_name not in ['_meta', 'all', 'ungrouped'] %},
|
||||
"{{ group_name }}": {
|
||||
"hosts": {{ group_data.hosts | to_json }}
|
||||
}{% endif %}{% endfor %}
|
||||
}
|
@@ -1,14 +1,13 @@
|
||||
---
|
||||
# Internal variables for linode_inventory role
|
||||
|
||||
linode_inventory_script_path: "{{ role_path }}/files/linode_inventory.py"
|
||||
temp_inventory_path: "{{ linode_inventory_output_dir }}/{{ linode_inventory_output_file }}"
|
||||
# Linode API endpoint
|
||||
linode_api_url: "https://api.linode.com/v4/linode/instances"
|
||||
|
||||
# Required environment variables
|
||||
required_env_vars:
|
||||
- LINODE_API_TOKEN
|
||||
|
||||
# Python packages required
|
||||
python_requirements:
|
||||
- requests
|
||||
- json
|
||||
# Default groups that will be created
|
||||
default_groups:
|
||||
- linode_all
|
||||
|
Reference in New Issue
Block a user