Rewrite the linode inventory stuff...this is a pain in the ass.

This commit is contained in:
2025-08-07 21:47:24 -05:00
parent 3942108d2d
commit a0ccc1fafc
9 changed files with 95 additions and 812 deletions

View File

@@ -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

View File

@@ -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_"

View File

@@ -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()

View File

@@ -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: []

View File

@@ -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!

View File

@@ -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

View File

@@ -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'

View File

@@ -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 %}
}

View File

@@ -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