diff --git a/playbooks/inventory/linode.yaml b/playbooks/inventory/linode.yaml index 6bb830d..92915cc 100644 --- a/playbooks/inventory/linode.yaml +++ b/playbooks/inventory/linode.yaml @@ -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 diff --git a/roles/inventory/linode/defaults/main.yml b/roles/inventory/linode/defaults/main.yml index ea1fac0..c585e0f 100644 --- a/roles/inventory/linode/defaults/main.yml +++ b/roles/inventory/linode/defaults/main.yml @@ -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_" diff --git a/roles/inventory/linode/files/linode_inventory.py b/roles/inventory/linode/files/linode_inventory.py deleted file mode 100644 index a924cf5..0000000 --- a/roles/inventory/linode/files/linode_inventory.py +++ /dev/null @@ -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() diff --git a/roles/inventory/linode/meta/main.yml b/roles/inventory/linode/meta/main.yml deleted file mode 100644 index d83c1a7..0000000 --- a/roles/inventory/linode/meta/main.yml +++ /dev/null @@ -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: [] diff --git a/roles/inventory/linode/tasks/main.yml b/roles/inventory/linode/tasks/main.yml index 68355aa..1a24b5d 100644 --- a/roles/inventory/linode/tasks/main.yml +++ b/roles/inventory/linode/tasks/main.yml @@ -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! diff --git a/roles/inventory/linode/templates/awx_inventory_update.sh.j2 b/roles/inventory/linode/templates/awx_inventory_update.sh.j2 deleted file mode 100644 index 53aefec..0000000 --- a/roles/inventory/linode/templates/awx_inventory_update.sh.j2 +++ /dev/null @@ -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 diff --git a/roles/inventory/linode/templates/inventory.ini.j2 b/roles/inventory/linode/templates/inventory.ini.j2 deleted file mode 100644 index 824c057..0000000 --- a/roles/inventory/linode/templates/inventory.ini.j2 +++ /dev/null @@ -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' diff --git a/roles/inventory/linode/templates/linode_hosts.json.j2 b/roles/inventory/linode/templates/linode_hosts.json.j2 deleted file mode 100644 index 16525ef..0000000 --- a/roles/inventory/linode/templates/linode_hosts.json.j2 +++ /dev/null @@ -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 %} -} diff --git a/roles/inventory/linode/vars/main.yml b/roles/inventory/linode/vars/main.yml index 1ff6470..8432aa1 100644 --- a/roles/inventory/linode/vars/main.yml +++ b/roles/inventory/linode/vars/main.yml @@ -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 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