Rewrite the linode inventory stuff...this is a pain in the ass.
This commit is contained in:
@@ -1,200 +1,26 @@
|
|||||||
---
|
---
|
||||||
- name: Update Linode Dynamic Inventory
|
- name: Sync Linode Hosts to AWX Inventory
|
||||||
hosts: localhost
|
hosts: localhost
|
||||||
gather_facts: true
|
gather_facts: false
|
||||||
connection: local
|
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:
|
roles:
|
||||||
- role: roles/inventory/linode
|
- roles/inventory/linode
|
||||||
vars:
|
|
||||||
linode_api_token: "{{ linode_api_token }}"
|
|
||||||
|
|
||||||
post_tasks:
|
# Optional: Example of using the discovered hosts
|
||||||
- name: Display next steps
|
- name: Test connectivity to discovered Linode hosts
|
||||||
ansible.builtin.debug:
|
hosts: linode_all
|
||||||
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
|
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
tasks:
|
tasks:
|
||||||
- name: Check if inventory file exists
|
- name: Test connectivity to Linode hosts
|
||||||
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
|
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
register: ping_result
|
register: ping_result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Display connectivity status with tag info
|
- name: Display connectivity results
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: |
|
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(', ') }}
|
Tags: {{ linode_tags | join(', ') }}
|
||||||
Role: {{ 'Control Plane' if is_control_plane else 'Worker Node' if is_worker_node else 'Other' }}
|
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
|
|
||||||
|
@@ -1,26 +1,17 @@
|
|||||||
---
|
---
|
||||||
# Default variables for linode_inventory role
|
# 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
|
# Filter options
|
||||||
include_only_running: false
|
include_only_running: true
|
||||||
specific_regions: []
|
specific_regions: [] # e.g., ['us-east', 'us-west']
|
||||||
specific_tags: []
|
specific_tags: [] # e.g., ['production', 'web']
|
||||||
|
|
||||||
# Output format
|
# Default SSH settings for discovered hosts
|
||||||
inventory_format: "json" # json or ini
|
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_"
|
||||||
|
@@ -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()
|
|
@@ -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: []
|
|
@@ -1,214 +1,83 @@
|
|||||||
---
|
---
|
||||||
# Main tasks for linode_inventory role
|
# Main tasks for linode_inventory role
|
||||||
|
|
||||||
- name: Set API token (AWX credential injection takes precedence)
|
- name: Validate Linode API token
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.fail:
|
||||||
linode_api_token: "{{ linode_api_token | default(lookup('env', 'LINODE_API_TOKEN')) | default('') }}"
|
msg: "LINODE_API_TOKEN environment variable must be set"
|
||||||
|
when: lookup('env', 'LINODE_API_TOKEN') == ""
|
||||||
|
|
||||||
- name: Validate required variables
|
- name: Fetch Linode instances
|
||||||
ansible.builtin.assert:
|
ansible.builtin.uri:
|
||||||
that:
|
url: "https://api.linode.com/v4/linode/instances"
|
||||||
- linode_api_token is defined
|
method: GET
|
||||||
- linode_api_token | length > 0
|
headers:
|
||||||
fail_msg: |
|
Authorization: "Bearer {{ lookup('env', 'LINODE_API_TOKEN') }}"
|
||||||
Linode API token not found.
|
Content-Type: "application/json"
|
||||||
For AWX: Attach a Linode API Token credential to your job template
|
return_content: yes
|
||||||
For local: Set LINODE_API_TOKEN environment variable or pass linode_api_token variable
|
status_code: 200
|
||||||
quiet: true
|
register: linode_response
|
||||||
|
|
||||||
- name: Ensure output directory exists
|
- name: Display API response summary
|
||||||
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
|
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: |
|
msg: |
|
||||||
Linode Dynamic Inventory Summary:
|
Found {{ linode_response.json.data | length }} Linode instances
|
||||||
Total hosts discovered: {{ linode_inventory_data._meta.hostvars | length }}
|
Running instances: {{ linode_response.json.data | selectattr('status', 'equalto', 'running') | list | length }}
|
||||||
Groups created: {{ linode_inventory_data.keys() | reject('equalto', '_meta') | list | length }}
|
|
||||||
|
|
||||||
- name: Show discovered hosts
|
- name: Add Linode hosts to in-memory inventory
|
||||||
ansible.builtin.debug:
|
ansible.builtin.add_host:
|
||||||
msg: "Host: {{ item.key }} ({{ item.value.ansible_host }}) - Region: {{ item.value.linode_region }} - Status: {{ item.value.linode_status }}"
|
name: "{{ item.label }}"
|
||||||
loop: "{{ linode_inventory_data._meta.hostvars | dict2items }}"
|
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:
|
loop_control:
|
||||||
label: "{{ item.key }}"
|
label: "{{ item.label }}"
|
||||||
when: linode_inventory_data._meta.hostvars | length > 0
|
|
||||||
|
|
||||||
- name: Debug current directory and Git status
|
- name: Display discovered hosts summary
|
||||||
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
|
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: |
|
msg: |
|
||||||
=== COMPREHENSIVE DEBUG RESULTS ===
|
🎉 Successfully added {{ groups['linode_all'] | default([]) | length }} Linode hosts to inventory!
|
||||||
|
|
||||||
Current Directory Info:
|
📋 Hosts discovered:
|
||||||
{% if debug_git_info is defined and debug_git_info.stdout is defined %}
|
{% for host in groups['linode_all'] | default([]) %}
|
||||||
{{ debug_git_info.stdout }}
|
- {{ host }} ({{ hostvars[host]['ansible_host'] }})
|
||||||
{% else %}
|
Region: {{ hostvars[host]['linode_region'] }}
|
||||||
Debug info not available
|
Type: {{ hostvars[host]['linode_type'] }}
|
||||||
{% endif %}
|
Tags: {{ hostvars[host]['linode_tags'] | join(', ') }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
SSH Configuration:
|
📂 Groups available for targeting:
|
||||||
{% if ssh_debug is defined and ssh_debug.stdout is defined %}
|
{% for group in groups.keys() | sort if group.startswith(('tag_', 'region_', 'type_', 'status_')) %}
|
||||||
{{ ssh_debug.stdout }}
|
- {{ group }}: {{ groups[group] | length }} hosts
|
||||||
{% else %}
|
{% endfor %}
|
||||||
SSH debug not available
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
Git Configuration:
|
💡 Use these groups in your job templates:
|
||||||
{% if git_config_result is defined and git_config_result.stdout is defined %}
|
- tag_k3s: All k3s cluster nodes
|
||||||
{{ git_config_result.stdout }}
|
- tag_control_plane: Control plane nodes only
|
||||||
{% else %}
|
- tag_worker_node: Worker nodes only
|
||||||
Git config not available
|
- region_us_southeast: All hosts in us-southeast
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
Git Commit/Push Results:
|
✅ Hosts are now available for subsequent job templates in this workflow!
|
||||||
{% 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)
|
|
||||||
|
@@ -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
|
|
@@ -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'
|
|
@@ -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 %}
|
|
||||||
}
|
|
@@ -1,14 +1,13 @@
|
|||||||
---
|
---
|
||||||
# Internal variables for linode_inventory role
|
# Internal variables for linode_inventory role
|
||||||
|
|
||||||
linode_inventory_script_path: "{{ role_path }}/files/linode_inventory.py"
|
# Linode API endpoint
|
||||||
temp_inventory_path: "{{ linode_inventory_output_dir }}/{{ linode_inventory_output_file }}"
|
linode_api_url: "https://api.linode.com/v4/linode/instances"
|
||||||
|
|
||||||
# Required environment variables
|
# Required environment variables
|
||||||
required_env_vars:
|
required_env_vars:
|
||||||
- LINODE_API_TOKEN
|
- LINODE_API_TOKEN
|
||||||
|
|
||||||
# Python packages required
|
# Default groups that will be created
|
||||||
python_requirements:
|
default_groups:
|
||||||
- requests
|
- linode_all
|
||||||
- json
|
|
||||||
|
Reference in New Issue
Block a user