#!/usr/bin/env python3 import json import requests import sys import os 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" self.headers = { "Authorization": f"Bearer {self.api_token}", "Content-Type": "application/json" } def get_instances(self): """Fetch all Linode instances""" url = f"{self.base_url}/linode/instances" response = requests.get(url, headers=self.headers) response.raise_for_status() return response.json()['data'] 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() for instance in instances: # Use Linode label as hostname (this is what you wanted!) hostname = instance['label'] # Get primary IPv4 address ipv4_addresses = instance.get('ipv4', []) primary_ip = ipv4_addresses[0] if ipv4_addresses else None # Add to ungrouped hosts inventory['ungrouped']['hosts'].append(hostname) # Host variables inventory['_meta']['hostvars'][hostname] = { 'ansible_host': primary_ip, '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': instance.get('tags', []), 'linode_specs': instance.get('specs', {}), 'linode_hypervisor': instance.get('hypervisor'), 'linode_created': instance.get('created'), 'linode_updated': instance.get('updated') } # 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(' ', '_')}" if tag_group not in inventory: inventory[tag_group] = {'hosts': []} inventory[tag_group]['hosts'].append(hostname) except requests.exceptions.RequestException as e: print(f"Error fetching Linode instances: {e}", file=sys.stderr) return {} # 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_')] all_groups.extend(tag_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: print(json.dumps(inventory.generate_inventory(), indent=2)) elif args.host: print(json.dumps(inventory.get_host_vars(args.host), indent=2)) else: parser.print_help() except Exception as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1) if __name__ == '__main__': main()