diff --git a/playbooks/inventory/linode.yaml b/playbooks/inventory/linode.yaml index a3f8f94..f8c5898 100644 --- a/playbooks/inventory/linode.yaml +++ b/playbooks/inventory/linode.yaml @@ -32,7 +32,7 @@ Include only running: {{ include_only_running }} roles: - - role: roles/inventory/linode + - role: linode_inventory vars: linode_api_token: "{{ linode_api_token }}" @@ -130,7 +130,7 @@ hosts: discovered_linodes gather_facts: false vars: - ansible_user: root + ansible_user: phlux ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=10' tasks: - name: Test connectivity @@ -150,7 +150,7 @@ hosts: discovered_linodes gather_facts: false vars: - ansible_user: root + ansible_user: phlux tasks: - name: Control plane specific task ansible.builtin.debug: @@ -162,7 +162,7 @@ hosts: discovered_linodes gather_facts: false vars: - ansible_user: root + ansible_user: phlux tasks: - name: Worker node specific task ansible.builtin.debug: @@ -174,7 +174,7 @@ hosts: discovered_linodes gather_facts: false vars: - ansible_user: root + ansible_user: phlux tasks: - name: K3s cluster task ansible.builtin.debug: diff --git a/roles/inventory/linode/tasks/main.yml b/roles/inventory/linode/tasks/main.yml index f8c5898..e1e84b3 100644 --- a/roles/inventory/linode/tasks/main.yml +++ b/roles/inventory/linode/tasks/main.yml @@ -1,182 +1,139 @@ --- -- name: Update Linode Dynamic Inventory - hosts: localhost - gather_facts: true - 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 - - # 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 == "" +# Main tasks for linode_inventory role - - 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 }} +- 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('') }}" - roles: - - role: linode_inventory - vars: - linode_api_token: "{{ 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 - post_tasks: - - name: Display next steps - ansible.builtin.debug: - msg: | - Inventory update complete! - - Next steps for AWX integration: - 1. Copy the inventory script to your SCM repository - 2. Create a custom inventory source in AWX - 3. Point it to the linode_inventory.py script - 4. Set up the Linode API credential - - Files created: - - JSON inventory: {{ linode_inventory_output_dir }}/{{ linode_inventory_output_file }} - {% if inventory_format == "ini" %} - - INI inventory: {{ linode_inventory_output_dir }}/linode_static_inventory.ini - {% endif %} +- name: Ensure output directory exists + ansible.builtin.file: + path: "{{ linode_inventory_output_dir }}" + state: directory + mode: '0755' + delegate_to: localhost -# Optional: Run against discovered Linode hosts -- name: Debug and use discovered Linode hosts - hosts: localhost - 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: 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: 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: 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: 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: Show script execution details + ansible.builtin.debug: + msg: | + Script execution results: + Return code: {{ linode_inventory_result.rc }} + Stdout length: {{ linode_inventory_result.stdout | length }} + Stderr length: {{ linode_inventory_result.stderr | length }} + +- name: Show stderr if present + ansible.builtin.debug: + msg: "Script stderr: {{ linode_inventory_result.stderr }}" + when: linode_inventory_result.stderr | length > 0 - - name: Parse inventory JSON - ansible.builtin.set_fact: - dynamic_inventory: "{{ inventory_content.content | b64decode | from_json }}" - when: inventory_file_stat.stat.exists +- name: Show stdout if present + ansible.builtin.debug: + msg: "Script stdout: {{ linode_inventory_result.stdout }}" + when: linode_inventory_result.stdout | length > 0 - - 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: Test API token directly + ansible.builtin.uri: + url: "https://api.linode.com/v4/linode/instances" + method: GET + headers: + Authorization: "Bearer {{ linode_api_token }}" + Content-Type: "application/json" + return_content: yes + status_code: [200, 401, 403] + register: direct_api_test + delegate_to: localhost - - 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 direct API test results + ansible.builtin.debug: + msg: | + Direct API test results: + Status: {{ direct_api_test.status }} + Response: {{ direct_api_test.json | default('No JSON response') }} + +- 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: 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: 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: 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 - ansible.builtin.ping: - register: ping_result - ignore_errors: true +- name: Save inventory to file + ansible.builtin.copy: + content: "{{ linode_inventory_data | to_nice_json }}" + dest: "{{ temp_inventory_path }}" + mode: '0644' + delegate_to: localhost - - name: Display connectivity status with tag info - ansible.builtin.debug: - msg: | - {{ inventory_hostname }} ({{ ansible_host }}): {{ 'REACHABLE' if ping_result is succeeded else 'UNREACHABLE' }} - Tags: {{ linode_tags | join(', ') }} - Role: {{ 'Control Plane' if is_control_plane else 'Worker Node' if is_worker_node else 'Other' }} +- name: Display inventory 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 }} + Inventory saved to: {{ temp_inventory_path }} + API Token status: {{ 'Set (' + (linode_api_token[:8] + '...' if linode_api_token | length > 8 else linode_api_token) + ')' if linode_api_token is defined else 'NOT SET' }} -# 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 +- name: Show raw script output for debugging + ansible.builtin.debug: + var: linode_inventory_result.stdout + when: linode_inventory_result.stdout | length > 0 -# 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 +- 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 }}" + loop_control: + label: "{{ item.key }}" + when: linode_inventory_data._meta.hostvars | length > 0 -# 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 +- name: Create static inventory file (optional) + ansible.builtin.template: + src: inventory.ini.j2 + dest: "{{ linode_inventory_output_dir }}/linode_static_inventory.ini" + mode: '0644' + when: inventory_format == "ini" + delegate_to: localhost + +- 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)