diff --git a/custom_scripts/linode_inventory.py b/custom_scripts/linode_inventory.py deleted file mode 100644 index a924cf5..0000000 --- a/custom_scripts/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/tasks/main.yml b/roles/inventory/linode/tasks/main.yml index e1e84b3..7a65541 100644 --- a/roles/inventory/linode/tasks/main.yml +++ b/roles/inventory/linode/tasks/main.yml @@ -123,13 +123,13 @@ label: "{{ item.key }}" when: linode_inventory_data._meta.hostvars | length > 0 -- name: Create static inventory file (optional) +- name: Create JSON inventory file for AWX ansible.builtin.template: - src: inventory.ini.j2 - dest: "{{ linode_inventory_output_dir }}/linode_static_inventory.ini" + src: linode_hosts.json.j2 + dest: "{{ linode_inventory_output_dir }}/linode_hosts.json" mode: '0644' - when: inventory_format == "ini" delegate_to: localhost + when: linode_inventory_data is defined - name: Clean up temporary script ansible.builtin.file: diff --git a/roles/inventory/linode/templates/linode_hosts.json.j2 b/roles/inventory/linode/templates/linode_hosts.json.j2 new file mode 100644 index 0000000..16525ef --- /dev/null +++ b/roles/inventory/linode/templates/linode_hosts.json.j2 @@ -0,0 +1,35 @@ +{ + "_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 %} +}