fix: rename roles to use underscores instead of hyphens which was causing the roles not to be found

This commit is contained in:
2026-04-14 15:19:21 -04:00
parent b2379e597e
commit 62f4a3ea73
31 changed files with 31 additions and 31 deletions

View File

@@ -0,0 +1,151 @@
# Ansible Role: proxmox_lxc_provision
## Description
This Ansible role manages the provision of LXC containers and templates on a Proxmox host. By default the role will create and start an LXC container. It allows creating a new container from a [container image](https://pve.proxmox.com/wiki/Linux_Container#pct_container_images) or cloning an existing container or template container.
It also includes tasks which may be used individually:
- `clone.yml`: Creates a new LXC container by cloning another container or template
- `convert.yml`: Converts an LXC container to a template
- `create.yml`: Creates a new LXC container
- `delete.yml`: Deletes an LXC container given its vmid or hostname
- `start.yml`: Starts an LXC container
- `stop.yml`: Stops an LXC container
- `update.yml`: Updates an existing LXC container
- `wait.yml`: Waits for SSH to be available on the container
- `check-exists.yml`: Checks the existence of the LXC with the given hostname
## Requirements
- Ansible 2.9 or higher
- Python 3.6 or higher
- requests
- proxmoxer
- passlib
## Role Variables
### Required Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `container_template` | The OS template to create the LXC from. Mutually exclusive with `clone_from`| `local:vztmpl/debian-12_amd64.tar.zst` |
| `clone_from` | The vmid of the container or template container to clone the LXC from. Mutually exclusive with `container_template` | `201` |
### Required Proxmox API Authentication Variables
**Note:** These should be defined in `group_vars/all.yml` as part of the `proxmox_api_connection` dictionary:
```yaml
# group_vars/all.yml
proxmox_api_connection:
api_host: "{{ proxmox_api_host }}"
api_port: "{{ proxmox_api_port }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: "{{ proxmox_api_validate_certs }}"
```
| Variable | Description | Example |
|----------|-------------|---------|
| `proxmox_api_user` | The username for Proxmox authentication, typically in format `username@realm` | `ansible@pve` |
| `proxmox_api_token_id` | The API token ID used for authentication | `token` |
| `proxmox_api_token_secret` | The secret key associated with the API token | `xxx-yyy-zzz` (should be stored securely) |
| `proxmox_api_host` | The IP address or hostname of the Proxmox server | `192.168.1.10` |
| `proxmox_api_port` | The port on which the Proxmox API is listening | `8006` |
| `proxmox_node` | The name of the Proxmox node to target | `server1` |
| `proxmox_api_validate_certs` | Whether to validate SSL certificates (set to false for self-signed certs) | `false` |
### Optional Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `lxc_clone_type` | Only use with `lxc_clone_from`. Supports `full` and `linked` clones. | `full` |
| `lxc_storage` | Target storage for the container | `local-zfs` |
| `lxc_disk` | The target storage and storage size | `local-zfs:16` |
| `lxc_password` | The password for the root account | `password123` |
| `lxc_cores` | The number of CPU cores | `4` |
| `lxc_memory` | Memory size in MB for container | `2048` |
| `lxc_swap` | Swap memory size in MB | `2048` |
| `lxc_ipv4` | The IPv4 address | `dhcp` |
| `lxc_ipv6` | The IPv6 address | `auto` |
| `lxc_pubkey_file` | The SSH public key for authentication to root user | Creates a temp key in `/tmp` |
| `lxc_features` | List of additional container features | `- nesting=1` |
## Example Playbook
### Prerequisites
First, set up your Proxmox API connection in group vars:
```yaml
# group_vars/all.yml
proxmox_api_connection:
api_host: "10.0.1.1"
api_port: 8006
api_user: "automation@pve"
api_token_id: "mytoken"
api_token_secret: "{{ vault_proxmox_token }}"
validate_certs: false
proxmox_node: "pve01"
```
### Creating a new LXC from template
```yaml
- name: Create and start an LXC container
hosts: localhost
connection: local
vars:
lxc_hostname: new-debian-container
lxc_template: "local:vztmpl/debian-12_amd64.tar.zst"
lxc_ipv4: "10.0.0.99"
roles:
- role: proxmox_lxc_provision
```
### Creating a new LXC by cloning an existing container with vmid 200
```yaml
- name: Create and start an LXC container
hosts: localhost
connection: local
vars:
lxc_hostname: new-debian-container
lxc_clone_from: 200
lxc_ipv4: "10.0.0.99"
roles:
- role: proxmox_lxc_provision
```
### Idempotent Behavior
The role now includes idempotency checking. If a container with the specified `container_vmid` already exists, the role will skip provisioning and exit gracefully.
#### Creating an LXC Container and Converting it to a Template
```yaml
---
- name: Create and start an LXC container
hosts: localhost
connection: local
vars:
- container_hostname: "{{ container_hostname }}"
- os_template: "local:vztmpl/debian-12_amd64.tar.zst"
- container_ipv4: "10.0.0.99"
roles:
- role: proxmox_lxc_provision
# Run configuration tasks on the container
# ...
- name: Convert the created container to a template
hosts: localhost
vars:
container_hostname: "{{ container_hostname }}"
tasks:
- include_role:
name: proxmox_lxc_provision
tasks_from: convert
```

View File

@@ -0,0 +1,20 @@
---
lxc_template: "local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst"
lxc_cores: 4
lxc_memory: 2048
lxc_swap: 2048
lxc_storage: local-zfs
lxc_size: 16
lxc_disk: "{{ lxc_storage }}:{{ lxc_size }}"
lxc_ipv4: dhcp
lxc_ipv6: auto
lxc_nameserver: 10.0.0.7
lxc_gateway: 10.0.0.1
lxc_pubkey_file: "~/.ssh/id_ed25519.pub"
lxc_unprivileged: true
lxc_features:
- nesting=1
lxc_nvidia_gpu_mount: false
lxc_tags: ["ansible-managed"]
lxc_clone_type: full
lxc_start: true

View File

@@ -0,0 +1,15 @@
---
- name: Query Proxmox for existing LXCs
community.proxmox.proxmox_lxc_info:
<<: "{{ proxmox_api_connection }}"
register: proxmox_lxcs
- name: Check if LXC with hostname already exists
ansible.builtin.set_fact:
lxc_exists: >-
{{
proxmox_lxcs.proxmox_lxcs
| selectattr('name', 'equalto', lxc_hostname)
| list
| length > 0
}}

View File

@@ -0,0 +1,29 @@
---
- name: Create a full clone of the container
community.general.proxmox:
<<: "{{ proxmox_api_connection }}"
vmid: "{{ lxc_vmid | default(0) }}"
clone: "{{ lxc_clone_from }}"
clone_type: "{{ lxc_clone_type }}"
hostname: "{{ lxc_hostname }}"
storage: "{{ lxc_storage }}"
register: clone_result
- name: Add bind mounts via pct
become: yes
ansible.builtin.shell: |
pct set {{ clone_result.vmid | default(lxc_vmid) }} {% for key, value in lxc_mounts.items() %}-{{ key }} {{ value }} {% endfor %}
delegate_to: "{{ proxmox_api_host }}"
when: lxc_mounts is defined
- name: Resize rootfs after clone
ansible.builtin.command:
cmd: "pct resize {{ clone_result.vmid }} rootfs {{ lxc_size }}G"
delegate_to: "{{ proxmox_api_host }}"
become: yes
register: resize_result
changed_when: resize_result.rc == 0 and 'already at specified size' not in resize_result.stderr
failed_when:
- resize_result.rc != 0
- "'already at specified size' not in resize_result.stderr"
when: lxc_size is defined

View File

@@ -0,0 +1,8 @@
---
- ansible.builtin.include_tasks: stop.yaml
- name: Convert container to template
community.general.proxmox:
<<: "{{ proxmox_api_connection }}"
state: template
hostname: "{{ lxc_hostname }}"

View File

@@ -0,0 +1,25 @@
---
- name: Create an LXC container
community.general.proxmox:
<<: "{{ proxmox_api_connection }}"
vmid: "{{ lxc_vmid | default(omit) }}"
hostname: "{{ lxc_hostname }}"
password: "{{ lxc_password }}"
ostemplate: "{{ lxc_template }}"
cores: "{{ lxc_cores }}"
memory: "{{ lxc_memory }}"
swap: "{{ lxc_swap }}"
disk: "{{ lxc_disk }}"
mounts: "{{ lxc_mounts | default(omit) }}"
netif: >
{"net0": "name=eth0,gw={{ lxc_gateway }},ip={{ lxc_ipv4 }},ip6={{ lxc_ipv6 | default(omit) }},bridge=vmbr0"}
pubkey: "{{ lookup('file', lxc_pubkey_file) | default(omit) }}"
onboot: "{{ lxc_onboot | default(false) }}"
startup: "{{ lxc_startup | default(omit) }}"
unprivileged: "{{ lxc_unprivileged | default(true) }}"
features: "{{ lxc_features | default(omit) }}"
timezone: "{{ lxc_timezone | default(omit) }}"
nameserver: "{{ lxc_nameserver | default(omit) }}"
state: present
tags: "{{ lxc_tags | default(omit) }}"
register: lxc_result

View File

@@ -0,0 +1,12 @@
---
- ansible.builtin.include_tasks: stop.yaml
- name: Delete a container
community.general.proxmox:
<<: "{{ proxmox_api_connection }}"
vmid: "{{ lxc_vmid | default(omit) }}"
hostname: "{{ lxc_hostname | default(omit) }}"
state: absent
register: delete_result
failed_when: |
delete_result.failed and
('does not exist' not in delete_result.msg)

View File

@@ -0,0 +1,43 @@
---
- name: Remove all existing ID mappings
lineinfile:
path: "/etc/pve/lxc/{{ lxc_vmid }}.conf"
regexp: '^lxc\.idmap:'
state: absent
when: lxc_id_mappings is defined
- name: Add ID mappings
blockinfile:
path: "/etc/pve/lxc/{{ lxc_vmid }}.conf"
block: "{{ lxc_id_mappings }}"
insertafter: EOF
when: lxc_id_mappings is defined
- name: Remove existing GPU configuration
lineinfile:
path: "/etc/pve/lxc/{{ lxc_vmid }}.conf"
regexp: "{{ item }}"
state: absent
loop:
- '^lxc\.cgroup2\.devices\.allow: c {{ gpu_device_id }}:\* rwm'
- '^lxc\.cgroup2\.devices\.allow: c {{ uvm_device_id }}:\* rwm'
- '^lxc\.mount\.entry: /dev/nvidia0'
- '^lxc\.mount\.entry: /dev/nvidiactl'
- '^lxc\.mount\.entry: /dev/nvidia-uvm '
- '^lxc\.mount\.entry: /dev/nvidia-uvm-tools'
when: lxc_nvidia_gpu_mount
- name: Add GPU device for passthrough
blockinfile:
path: /etc/pve/lxc/{{ lxc_vmid }}.conf
block: |
lxc.cgroup2.devices.allow: c {{ gpu_device_id }}:* rwm
lxc.cgroup2.devices.allow: c {{ uvm_device_id }}:* rwm
lxc.mount.entry: /dev/nvidia0 dev/nvidia0 none bind,optional,create=file
lxc.mount.entry: /dev/nvidiactl dev/nvidiactl none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-uvm dev/nvidia-uvm none bind,optional,create=file
lxc.mount.entry: /dev/nvidia-uvm-tools dev/nvidia-uvm-tools none bind,optional,create=file
when: lxc_nvidia_gpu_mount

View File

@@ -0,0 +1,50 @@
---
- name: Check if container exists
community.general.proxmox:
<<: "{{ proxmox_api_connection }}"
vmid: "{{ lxc_vmid }}"
state: current
register: existing_container
ignore_errors: true
- name: Skip if container already exists
meta: end_host
when: existing_container is succeeded
- name: Container source must be defined (lxc_clone_from or lxc_template)
ansible.builtin.fail:
msg: "Neither lxc_clone_from or lxc_template are defined"
when: lxc_clone_from is undefined and lxc_template is undefined
- name: Clone container from another container or template, then update
when: lxc_clone_from is defined
block:
- name: Clone from template
ansible.builtin.include_tasks: clone.yaml
register: clone_result
- name: Update container
ansible.builtin.include_tasks: update.yaml
vars:
lxc_vmid: "{{ clone_result.vmid }}"
register: lxc_result
- name: Create the new container
ansible.builtin.include_tasks: create.yaml
when: lxc_template is defined and lxc_clone_from is undefined
- name: Start the created container and wait for ssh
vars:
lxc_vmid: "{{ lxc_result.vmid }}"
ansible.builtin.include_tasks: "{{ item }}"
loop:
- start.yaml
- wait.yaml
when: lxc_start
- name: Post clone updates
when: lxc_clone_from is defined
delegate_to: "{{ lxc_hostname }}"
block:
- name: Include post-clone tasks
ansible.builtin.include_tasks: post-clone.yaml

View File

@@ -0,0 +1,27 @@
---
- name: Change root password
ansible.builtin.user:
name: root
password: "{{ lxc_password | password_hash('sha512') }}"
update_password: always
when: lxc_password is defined
- name: Change admin password
ansible.builtin.user:
name: admin
password: "{{ password | password_hash('sha512') }}"
update_password: always
when: password is defined
- name: Regenerate SSH host keys
ansible.builtin.include_role:
name: system_setup
tasks_from: ssh
vars:
regenerate_ssh_keys: true
- name: Remove previous entry from known hosts
ansible.builtin.known_hosts:
name: "{{ hostvars[lxc_hostname]['ansible_host'] }}"
state: absent
delegate_to: localhost

View File

@@ -0,0 +1,11 @@
---
- name: Start the LXC container
community.general.proxmox:
<<: "{{ proxmox_api_connection }}"
vmid: "{{ lxc_result.vmid }}"
state: started
register: start_result
retries: 3
delay: 5
until: start_result is success
failed_when: start_result.failed and ('already running' not in start_result.msg)

View File

@@ -0,0 +1,12 @@
---
- name: Stop container if it is running
community.general.proxmox:
<<: "{{ proxmox_api_connection }}"
vmid: "{{ lxc_vmid | default(omit) }}"
hostname: "{{ lxc_hostname | default(omit) }}"
state: "stopped"
register: stop_result
failed_when: |-
stop_result.failed and
('not running' not in stop_result.msg) and
('does not exist' not in stop_result.msg)

View File

@@ -0,0 +1,22 @@
---
- name: Update an LXC container
community.general.proxmox:
<<: "{{ proxmox_api_connection }}"
vmid: "{{ lxc_vmid }}"
hostname: "{{ lxc_hostname }}"
password: "{{ lxc_password | default(omit) }}" # Updating password does not work
cores: "{{ lxc_cores }}"
memory: "{{ lxc_memory }}"
swap: "{{ lxc_swap }}"
disk: "{{ lxc_disk }}"
netif: '{"net0": "name=eth0,gw={{ lxc_gateway }},ip={{ lxc_ipv4 }},ip6={{ lxc_ipv6 | default(omit) }},bridge=vmbr0"}'
pubkey: "{{ lookup('file', lxc_pubkey_file) | default(omit) }}"
onboot: "{{ lxc_onboot | default(false) }}"
startup: "{{ lxc_startup | default(omit) }}"
features: "{{ lxc_features | default(omit) }}"
timezone: "{{ lxc_timezone | default(omit) }}"
nameserver: "{{ lxc_nameserver | default(omit) }}"
state: present
tags: "{{ lxc_tags | default(omit) }}"
update: true
register: lxc_result

View File

@@ -0,0 +1,20 @@
---
- ansible.builtin.meta: refresh_inventory
- name: Wait a moment for container to be available in inventory
ansible.builtin.pause:
seconds: 2
- name: Debug - Check if container is in inventory
ansible.builtin.debug:
msg: "Container {{ lxc_hostname }} found with IP: {{ hostvars[lxc_hostname]['ansible_host'] | default('NOT FOUND') }}"
- name: Wait for SSH to become available
ansible.builtin.wait_for:
host: "{{ hostvars[lxc_hostname]['ansible_host'] }}"
port: 22
delay: 3
state: started
register: ssh_wait_result
until: ssh_wait_result is not failed