Skip to content

A powerful and flexible Ansible dynamic inventory script for LXD that supports multiple endpoints, advanced filtering, and hostname customization.

License

Notifications You must be signed in to change notification settings

vosdev/ansible-lxd-inventory

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LXD Ansible Dynamic Inventory Script

A powerful and flexible Ansible dynamic inventory script for LXD that supports multiple endpoints, advanced filtering, and hostname customization.

This script was created after running into too many limitations of community.general.lxd inventory. Using many projects on multiple clusters, I ended up with over 20 inventory files. This has now been reduced to 1.

Should also work fine with Incus!

Feel free to request additional features.

Features

  • Multi-Endpoint Support: Connect to multiple LXD servers in a single inventory run
  • Tag-based Filtering: Filter instances using user.* configuration keys
  • Flexible Hostname Formatting: Customize how hostnames appear in your inventory ({name}.{project}.{endpoint}.example.com)
  • Custom Group Formatting: Customize Ansible group names with templates
  • User-Defined Groups: Create custom groups using user.ansible_groups configuration
  • Project and Profile Filtering: Filter by LXD projects and profiles
  • Network Interface Control: Configure IP address selection with interface filtering and IPv4/6 preference
  • Status and Type Filtering: Include/exclude based on instance status and type
  • Regex-based Name Filtering: Exclude instances based on names with support for regex
  • SSL/TLS Support: Full certificate management for secure connections
  • Unix Socket Support: Local LXD daemon connections
  • Debug Mode: Detailed logging for troubleshooting

Installation

Requirements

pip install PyYAML requests urllib3

For Unix socket connections (local LXD):

pip install requests-unixsocket

Download

git clone https://github.com/vosdev/ansible-lxd-inventory
chmod +x ansible-lxd-inventory/lxd_inventory.py

Quick Start

1. Basic Configuration

Create lxd_inventory.yml:

global_defaults:
  verify_ssl: false
  filters:
    status: [running, stopped]
    type: [container, virtual-machine]
    projects: [all]

lxd_endpoints:
  local:
    endpoint: "unix:///var/lib/lxd/unix.socket"
  
  remote:
    endpoint: "https://lxd.example.com:8443"
    cert_path: "/path/to/client.crt"
    key_path: "/path/to/client.key"

2. Test the Inventory

# List all instances
./lxd_inventory.py --list

# Get specific instance details
./lxd_inventory.py --instance mycontainer

# Debug mode
./lxd_inventory.py --list --debug

3. Use with Ansible

# Run playbook using dynamic inventory
ansible-playbook -i lxd_inventory.py playbook.yml

# List hosts in inventory
ansible-inventory -i lxd_inventory.py --list

Configuration

Configuration File Locations

To allow for multiple instances of the script in restricted environments (e.g. SemaphoreUI, AWX or Tower), the script searches for configuration files in this order based on it's file name. e.g., lxd_inventory_dev.py -> lxd_inventory_dev.yml in the following locations:

  1. /etc/{base_name}.yml
  2. /etc/{base_name}.yaml
  3. ~/.config/{base_name}.yml
  4. ~/.config/{base_name}.yaml
  5. ./{base_name}.yml
  6. ./{base_name}.yaml

If those are not found, it will fallback to the default config filename in the following locations:

  1. --config /path/to/config.yml (CLI argument)
  2. ./lxd_inventory.yml
  3. ./lxd_inventory.yaml
  4. ~/.config/lxd_inventory.yml
  5. ~/.config/lxd_inventory.yaml
  6. /etc/lxd_inventory.yml
  7. /etc/lxd_inventory.yaml

Configuration Structure

global_defaults:
  # SSL/TLS settings
  verify_ssl: false
  cert_path: "/path/to/client.crt"
  key_path: "/path/to/client.key"
  ca_cert_path: "/path/to/ca.crt"
  
  # Hostname formatting template
  hostname_format: "{name}"
  
  # Group formatting templates
  group_formats:
    project: "lxd_project_{project}"     # Default format
    profile: "lxd_profile_{profile}"
    endpoint: "lxd_endpoint_{endpoint}"
    status: "lxd_{status}"
    type: "lxd_{type}"
  
  # User-defined groups
  user_groups:
    enabled: true                        # Enable user groups (default)
    key: "user.ansible_groups"           # Configuration key to check
  
  # Default filters
  filters:
    status: [running, stopped, frozen, error]
    type: [container, virtual-machine]
    projects: [all]
    exclude_projects: []
    profiles: []
    tags: {}
    ignore_interfaces: [lo, docker0, lxdbr0]
    prefer_ipv6: false
    exclude_names: []

lxd_endpoints:
  endpoint_name:
    endpoint: "https://lxd.example.com:8443"
    # Override any global_defaults here
    filters:
      # Override specific filters for this endpoint

Filtering

Status Filtering

# CLI
./lxd_inventory.py --list --status running
./lxd_inventory.py --list --status running,stopped

# Config
filters:
  status: [running, stopped]

Type Filtering

# CLI
./lxd_inventory.py --list --type vm
./lxd_inventory.py --list --type vm,lxc

# Config  
filters:
  type: [container, virtual-machine]

Project Filtering

Filter instances based on their project.

CLI Examples

# CLI - specific projects
./lxd_inventory.py --list --project production,development

# CLI - all projects
./lxd_inventory.py --list --all-projects

Configuration Examples

filters:
  # only list instances from production and development project
  projects: [production, development]

  # list instances from all projects (default value)
  projects: [all]

  # list instances from all projects with some exceptions
  projects: [all]
  exclude_projects:
    - backup
    - 'regex:^temp.*'
    - 'regex:^test[1-9]'

Tag Filtering

Filter instances based on user.* configuration keys. Tags can be set directly on instances or inherited from profiles.

CLI Examples

# Only include instances with user.ansible=true
./lxd_inventory.py --list --tag "user.ansible=true"

# Multiple requirements
./lxd_inventory.py --list --tag "user.ansible=true,user.env=production"

# Exclude instances (negation)
./lxd_inventory.py --list --tag "user.ansible!=false"

# Check for key existence
./lxd_inventory.py --list --tag "user.managed"

Configuration Examples

filters:
  tags:
    # Include only instances with user.ansible=true
    user.ansible: "true"
    
    # Exclude instances with user.ansible=false
    "user.ansible!=": "false"
    
    # Multiple requirements
    user.environment: "production"
    "user.backup!=": "true"

Setting Tags on Instances and Profiles

# Direct instance configuration
lxc config set myinstance user.ansible true
lxc config set myinstance user.environment production

# Profile-based tags (applies to all instances using the profile)
lxc profile create ansible-managed
lxc profile set ansible-managed user.ansible true
lxc profile add myinstance ansible-managed

Profile Filtering

# CLI
./lxd_inventory.py --list --profile web,database

# Config
filters:
  profiles: [web, database]

Instance name filtering

filters:
  exclude_names:
    - 'vm1'                     # excludes vm1 from any project
    - 'project/vm1'             # excludes vm1 only from specified project
    - 'regex:^vm.*'             # excludes instances matching regex pattern
    - 'regex:project/^vm[1-3]'  # excludes instances matching regex in specific project

Hostname Formatting

Customize how instance names appear in your Ansible inventory using template variables:

Available Variables

  • {name} - Instance name
  • {project} - LXD project name
  • {endpoint} - Endpoint name from config
  • {type} - Instance type (container/virtual-machine)
  • {status} - Instance status (running/stopped/etc)

Examples

# Global default
global_defaults:
  hostname_format: "{name}"  # Output: "myinstance"

# Include project
global_defaults:
  hostname_format: "{name}.{project}"  # Output: "myinstance.web"

# FQDN-style (My personal preference)
global_defaults:
  hostname_format: "{name}.{project}.{endpoint}.example.com"
  # Output: "myinstance.web.prod.example.com"

# Per-endpoint override
lxd_endpoints:
  production:
    hostname_format: "{name}.prod.example.com"
  development:
    hostname_format: "{name}.dev.example.com"

Group Formatting

Customize how Ansible groups are named using template variables:

Available Group Types

  • project - Groups by LXD project
  • profile - Groups by LXD profile
  • endpoint - Groups by LXD endpoint
  • status - Groups by instance status
  • type - Groups by instance type

Default Group Formats

global_defaults:
  group_formats:
    project: "lxd_project_{project}"     # Default: lxd_project_web
    profile: "lxd_profile_{profile}"     # Default: lxd_profile_nginx
    endpoint: "lxd_endpoint_{endpoint}"  # Default: lxd_endpoint_production
    status: "lxd_{status}"               # Default: lxd_running
    type: "lxd_{type}"                   # Default: lxd_containers

Custom Group Format Examples

# Simple naming without lxd_ prefix
global_defaults:
  group_formats:
    project: "{project}"                 # Output: "web"
    profile: "{profile}"                 # Output: "nginx"
    endpoint: "{endpoint}"               # Output: "production"
    status: "{status}"                   # Output: "running"
    type: "{type}"                       # Output: "containers"

# Hierarchical naming
global_defaults:
  group_formats:
    project: "{endpoint}_{project}"      # Output: "production_web"
    profile: "{project}_{profile}"       # Output: "web_nginx"
    status: "{endpoint}_{status}"        # Output: "production_running"

# Per-endpoint customization
lxd_endpoints:
  production:
    group_formats:
      project: "prod_{project}"          # Output: "prod_web"
      profile: "prod_{profile}"          # Output: "prod_nginx"
  development:
    group_formats:
      project: "dev_{project}"           # Output: "dev_web"
      profile: "dev_{profile}"           # Output: "dev_nginx"

User-Defined Groups

Create custom groups by setting the user.ansible_groups (or a key to your personal preference) configuration key on instances or profiles:

Setting User Groups

# Direct instance configuration
lxc config set myinstance user.ansible_groups "webservers,production,frontend"

# Profile-based groups (applies to all instances using the profile)
lxc profile set nginx-profile user.ansible_groups "webservers,nginx"
lxc profile add myinstance nginx-profile

Configuration

global_defaults:
  user_groups:
    enabled: true                        # Enable user-defined groups (default: true)
    key: "user.ansible_groups"           # Configuration key to check (default)

# Per-endpoint override
lxd_endpoints:
  production:
    user_groups:
      enabled: true
      key: "user.prod_groups"            # Use different key for this endpoint

Examples

# Web server instances
lxc config set web1 user.ansible_groups "webservers,nginx,production"
lxc config set web2 user.ansible_groups "webservers,nginx,production"

# Database instances  
lxc config set db1 user.ansible_groups "databases,mysql,production"
lxc config set db2 user.ansible_groups "databases,mysql,production"

# Load balancer
lxc config set lb1 user.ansible_groups "loadbalancers,haproxy,production,frontend"

This creates Ansible groups: webservers, nginx, production, databases, mysql, loadbalancers, haproxy, frontend

Network Configuration

IP Address Selection

filters:
  # Interfaces to ignore when finding IP addresses
  ignore_interfaces: [lo, docker0, cilium_host, lxdbr0]
  
  # Prefer IPv6 over IPv4
  prefer_ipv6: true
# CLI
./lxd_inventory.py --list --ignore-interface "lo,docker0" --prefer-ipv6

Multi-Endpoint Usage

Select Specific Endpoints

# Single endpoint
./lxd_inventory.py --list --endpoint production

# Multiple endpoints  
./lxd_inventory.py --list --endpoint production,development

# All endpoints (default)
./lxd_inventory.py --list

Endpoint-Specific Configuration

lxd_endpoints:
  production:
    endpoint: "https://prod-lxd:8443"
    verify_ssl: true
    filters:
      projects: [production]
      tags:
        user.ansible: "true"
        user.environment: "production"
  
  development:
    endpoint: "https://dev-lxd:8443" 
    verify_ssl: false
    filters:
      projects: [development, testing]
      tags:
        "user.ansible!=": "false"

Inventory Output

Ansible Groups

The script automatically creates these groups:

Legacy groups (always available for backward compatibility):

  • all - All instances
  • lxd_containers - Container instances
  • lxd_vms - Virtual machine instances
  • lxd_running - Running instances
  • lxd_stopped - Stopped instances
  • lxd_frozen - Frozen instances
  • lxd_error - Error state instances

Configurable groups (customizable via group_formats):

  • Project groups - Instances from specific projects
  • Profile groups - Instances with specific profiles
  • Endpoint groups - Instances from specific endpoints
  • Status groups - Instances by status (customizable names)
  • Type groups - Instances by type (customizable names)

User-defined groups:

  • Custom groups from user.ansible_groups configuration

Host Variables

Each instance includes these variables:

lxd_name: "original-instance-name"
lxd_hostname: "formatted-hostname"  
lxd_type: "container"
lxd_status: "running"
lxd_architecture: "x86_64"
lxd_profiles: ["default", "web"]
lxd_project: "production"
lxd_endpoint: "production" 
lxd_endpoint_url: "https://prod-lxd:8443"
lxd_ip: ["10.0.1.100","2001:db8::100"]
lxd_config: {...}
lxd_expanded_config: {...}
ansible_host: "10.0.1.100"

Advanced Examples

Production Setup with Tag-based Management

global_defaults:
  verify_ssl: true
  cert_path: "/etc/ssl/lxd-client.crt"
  key_path: "/etc/ssl/lxd-client.key"
  hostname_format: "{name}.{project}.{endpoint}.example.com"
  
  filters:
    # Only include ansible-managed instances
    tags:
      user.ansible: "true"
      "user.maintenance!=": "true"

lxd_endpoints:
  production:
    endpoint: "https://prod-lxd.example.com:8443"
    filters:
      projects: [production]
      tags:
        user.environment: "production"
        
  staging:
    endpoint: "https://staging-lxd.example.com:8443" 
    filters:
      projects: [staging]
      tags:
        user.environment: "staging"

With instances configured like:

# Web servers
lxc config set web1 user.ansible "true"
lxc config set web1 user.environment "production"

# Database
lxc config set db1 user.ansible "true"
lxc config set db1 user.environment "production"

Production Setup with Custom Group Naming

global_defaults:
  verify_ssl: true
  cert_path: "/etc/ssl/lxd-client.crt"
  key_path: "/etc/ssl/lxd-client.key"
  hostname_format: "{name}.{project}.{endpoint}.example.com"

  # Custom group naming
  group_formats:
    project: "{endpoint}_{project}"      # production_web instead of lxd_project_web
    profile: "{project}_{profile}"       # web_nginx instead of lxd_profile_nginx
    endpoint: "{endpoint}"               # production instead of lxd_endpoint_production
    status: "{status}"                   # running instead of lxd_running
    type: "{type}"                       # containers instead of lxd_containers
  
  # Enable user-defined groups
  user_groups:
    enabled: true
    key: "user.ansible_groups"

With instances configured like:

# Web servers
lxc config set web1 user.ansible_groups "webservers,nginx,frontend"

# Database
lxc config set db1 user.ansible_groups "databases,mysql,backend"

Development Environment

lxd_endpoints:
  local:
    endpoint: "unix:///var/lib/lxd/unix.socket"
    hostname_format: "{name}.local"
    filters:
      projects: [all]
      status: [running]
      tags:
        "user.ansible!=": "false"

Multi-Environment with Tag Exclusions

lxd_endpoints:
  multi_env:
    endpoint: "https://multi-lxd.example.com:8443"
    filters:
      projects: [all]
      tags:
        user.managed: "true"           # Must be managed
        "user.backup!=": "true"        # Exclude backup instances
        "user.template!=": "true"      # Exclude templates
        "user.ansible!=": "false"      # Exclude explicitly disabled

Production-Environment with Name, Project and Regex-based Exclusions

lxd_endpoints:
  prod:
    endpoint: "https://lxd-prod.example.com:8443"
    hostname_format: "{name}.{project}.{endpoint}.example.com"    # nginx2.webshop.prod.example.com
    filters:
      projects: [all]
      exclude_names:
        - webshop/nginx1                  # Exclude nginx1 in the webshop project
        - sophos                          # Exclude all instances named sophos
        - 'regex:^talos.*'                # Exclude all instances starting with talos
        - 'regex:security/^opnsense[1-2]' # Exclude opnsense1 and opnsense2 in the security project
      exclude_projects:
        - backup                          # Exclude backup project
        - 'regex:^temp.*'                 # Exclude all projects starting with temp

Troubleshooting

Debug Mode

./lxd_inventory.py --list --debug

This shows:

  • Configuration file loading
  • Endpoint connections
  • Instance filtering decisions
  • IP address selection
  • Tag matching results
  • Group creation and naming

Common Issues

No instances found:

  • Check endpoint connectivity with --debug
  • Verify project names and permissions
  • Check tag filters - they may be too restrictive

SSL Certificate errors:

  • Use verify_ssl: false for testing
  • Check certificate paths and permissions
  • Ensure certificates are valid and not expired

Permission denied:

  • Verify LXD client certificates have proper permissions
  • Check that user is in lxd group for Unix socket access

IP addresses not detected:

  • Check ignore_interfaces configuration
  • Verify instances have network interfaces configured
  • Use --debug to see network interface processing

Group names not as expected:

  • Check group_formats configuration
  • Use --debug to see group name generation
  • Verify template variables are correct

User groups not appearing:

  • Verify user_groups.enabled is true
  • Check the user_groups.key configuration
  • Use --debug to see user group parsing
  • Ensure instances have the correct configuration key set

Testing Configuration

# Test specific endpoint
./lxd_inventory.py --list --endpoint production --debug

# Test tag filtering
./lxd_inventory.py --list --tag "user.ansible=true" --debug

# Validate instance details  
./lxd_inventory.py --instance mycontainer

# Test group formatting
./lxd_inventory.py --list --debug | grep -E "(Debug:|\"hosts\")"

Contributing

Bug reports and feature requests are welcome. When reporting issues, please include:

  • Configuration file (redacted)
  • Command used
  • Debug output (--debug)
  • LXD version and OS details

License

This script is provided as-is for managing LXD infrastructure with Ansible.

About

A powerful and flexible Ansible dynamic inventory script for LXD that supports multiple endpoints, advanced filtering, and hostname customization.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages