diff --git a/README.md b/README.md index a1b3ffc..8e3c2dc 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,32 @@ ansible-galaxy collection install patrickj.infrastructure ## Quick Start +### Proxmox API Configuration + +Set up your Proxmox API connection variables in group vars: + +```yaml +# group_vars/all.yml +proxmox_api_host: "10.0.1.1" +proxmox_api_port: 8006 +proxmox_api_user: "automation@pve" +proxmox_api_token_id: "mytoken" +proxmox_api_token_secret: "{{ vault_proxmox_token }}" +proxmox_api_validate_certs: false +proxmox_node: "pve01" +``` + ### Basic Proxmox LXC Container Setup + ```yaml - name: Create and start an LXC container hosts: localhost connection: local vars: - - container_hostname: new-debian-container - - os_template: "local:vztmpl/debian-12_amd64.tar.zst" - - container_ipv4: "10.0.0.99" + lxc_vmid: 100 + lxc_hostname: new-debian-container + lxc_template: "local:vztmpl/debian-12_amd64.tar.zst" + lxc_ipv4: "10.0.0.99/24" roles: - role: proxmox_lxc_provision @@ -64,7 +81,7 @@ ansible-galaxy collection install patrickj.infrastructure vars: password: "{{ admin_password }}" ssh_pubkey_file: "~/.ssh/id_rsa.pub" - + - include_role: name: patrickj.infrastructure.nvidia_drivers vars: diff --git a/roles/proxmox_lxc_provision/README.md b/roles/proxmox_lxc_provision/README.md index b2ce6d6..aae126b 100755 --- a/roles/proxmox_lxc_provision/README.md +++ b/roles/proxmox_lxc_provision/README.md @@ -14,9 +14,10 @@ It also includes tasks which may be used individually: - `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 +- Ansible 2.12 or higher - Python 3.6 or higher - requests - proxmoxer @@ -24,126 +25,159 @@ It also includes tasks which may be used individually: ## 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 }}" -``` +### Required Proxmox API Variables | Variable | Description | Example | |----------|-------------|---------| +| `proxmox_api_host` | The IP address or hostname of the Proxmox server | `192.168.1.10` | | `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` | +| `proxmox_api_token_secret` | The secret key associated with the API token | `xxx-yyy-zzz` | +| `proxmox_node` | The name of the Proxmox node to target | `pve01` | - -### Optional Variables +### Optional Proxmox API 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` | +| `proxmox_api_port` | The port on which the Proxmox API is listening | `8006` | +| `proxmox_api_validate_certs` | Whether to validate SSL certificates | `false` | + +### Required Container Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `lxc_template` | The OS template to create the LXC from. Mutually exclusive with `lxc_clone_from` | `local:vztmpl/debian-12_amd64.tar.zst` | +| `lxc_clone_from` | The vmid of the container or template to clone. Mutually exclusive with `lxc_template` | `201` | +| `lxc_hostname` | The hostname for the container | `my-container` | +| `lxc_vmid` | The VM ID for the container | `100` | + +### Optional Container Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `lxc_clone_type` | Clone type when using `lxc_clone_from` | `full` | +| `lxc_storage` | Target storage for the container | `local-zfs` | +| `lxc_size` | Disk size in GB | `16` | | `lxc_disk` | The target storage and storage size | `local-zfs:16` | -| `lxc_password` | The password for the root account | `password123` | +| `lxc_password` | The password for the root account | - | | `lxc_cores` | The number of CPU cores | `4` | -| `lxc_memory` | Memory size in MB for container | `2048` | +| `lxc_memory` | Memory size in MB | `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` | +| `lxc_gateway` | The default gateway | `10.0.0.1` | +| `lxc_nameserver` | DNS nameserver | `10.0.0.7` | +| `lxc_pubkey_file` | Path to SSH public key file | `~/.ssh/id_ed25519.pub` | +| `lxc_features` | List of container features | `["nesting=1"]` | +| `lxc_tags` | Tags for the container | `["ansible-managed"]` | +| `lxc_start` | Start container after creation | `true` | ## Example Playbook ### Prerequisites -First, set up your Proxmox API connection in group vars: +Set up your Proxmox API connection variables 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_api_host: "10.0.1.1" +proxmox_api_port: 8006 +proxmox_api_user: "automation@pve" +proxmox_api_token_id: "mytoken" +proxmox_api_token_secret: "{{ vault_proxmox_token }}" +proxmox_api_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_vmid: 100 lxc_hostname: new-debian-container lxc_template: "local:vztmpl/debian-12_amd64.tar.zst" - lxc_ipv4: "10.0.0.99" + lxc_ipv4: "10.0.0.99/24" roles: - role: proxmox_lxc_provision ``` -### Creating a new LXC by cloning an existing container with vmid 200 +### Creating a new LXC by cloning an existing container + ```yaml -- name: Create and start an LXC container +- name: Clone an LXC container hosts: localhost connection: local vars: - lxc_hostname: new-debian-container + lxc_vmid: 101 + lxc_hostname: cloned-container lxc_clone_from: 200 - lxc_ipv4: "10.0.0.99" + lxc_ipv4: "10.0.0.100/24" 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. +The role includes idempotency checking. If a container with the specified `lxc_vmid` already exists, the role will skip provisioning and exit gracefully. +### Using Standalone Tasks + +When using individual task files via `tasks_from`, you must set `module_defaults` at the play level since the tasks bypass the role's main entry point: + +```yaml +- name: Convert container to a template + hosts: localhost + module_defaults: + community.general.proxmox: + 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 }}" + node: "{{ proxmox_node }}" + vars: + lxc_hostname: "{{ lxc_hostname }}" + tasks: + - include_role: + name: proxmox_lxc_provision + tasks_from: convert +``` + +### Creating an LXC Container and Converting it to a Template -#### 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" + lxc_vmid: "{{ lxc_vmid }}" + lxc_hostname: "{{ lxc_hostname }}" + lxc_template: "local:vztmpl/debian-12_amd64.tar.zst" + lxc_ipv4: "10.0.0.99/24" roles: - role: proxmox_lxc_provision -# Run configuration tasks on the container -# ... +# Run configuration tasks on the container... - name: Convert the created container to a template hosts: localhost + module_defaults: + community.general.proxmox: + 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 }}" + node: "{{ proxmox_node }}" vars: - container_hostname: "{{ container_hostname }}" + lxc_hostname: "{{ lxc_hostname }}" tasks: - include_role: name: proxmox_lxc_provision diff --git a/roles/proxmox_lxc_provision/defaults/main.yml b/roles/proxmox_lxc_provision/defaults/main.yml index 3fbb7b9..39ff794 100755 --- a/roles/proxmox_lxc_provision/defaults/main.yml +++ b/roles/proxmox_lxc_provision/defaults/main.yml @@ -1,4 +1,9 @@ --- +# Proxmox API connection defaults (optional) +proxmox_api_port: 8006 +proxmox_api_validate_certs: false + +# LXC defaults lxc_template: "local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst" lxc_cores: 4 lxc_memory: 2048 diff --git a/roles/proxmox_lxc_provision/tasks/check-exists.yml b/roles/proxmox_lxc_provision/tasks/check-exists.yml index 596a7ec..98243fe 100755 --- a/roles/proxmox_lxc_provision/tasks/check-exists.yml +++ b/roles/proxmox_lxc_provision/tasks/check-exists.yml @@ -1,7 +1,6 @@ --- - name: Query Proxmox for existing LXCs - community.proxmox.proxmox_lxc_info: - <<: "{{ proxmox_api_connection }}" + community.proxmox.proxmox_lxc_info: {} register: proxmox_lxcs - name: Check if LXC with hostname already exists diff --git a/roles/proxmox_lxc_provision/tasks/clone.yml b/roles/proxmox_lxc_provision/tasks/clone.yml index 2212f58..00cd1bb 100755 --- a/roles/proxmox_lxc_provision/tasks/clone.yml +++ b/roles/proxmox_lxc_provision/tasks/clone.yml @@ -1,7 +1,6 @@ --- - 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 }}" @@ -23,7 +22,7 @@ become: yes register: resize_result changed_when: resize_result.rc == 0 and 'already at specified size' not in resize_result.stderr - failed_when: + failed_when: - resize_result.rc != 0 - "'already at specified size' not in resize_result.stderr" when: lxc_size is defined diff --git a/roles/proxmox_lxc_provision/tasks/convert.yml b/roles/proxmox_lxc_provision/tasks/convert.yml index 048f916..593c095 100755 --- a/roles/proxmox_lxc_provision/tasks/convert.yml +++ b/roles/proxmox_lxc_provision/tasks/convert.yml @@ -1,8 +1,7 @@ --- -- ansible.builtin.include_tasks: stop.yaml +- ansible.builtin.include_tasks: stop.yml + - name: Convert container to template community.general.proxmox: - <<: "{{ proxmox_api_connection }}" - - state: template hostname: "{{ lxc_hostname }}" + state: template diff --git a/roles/proxmox_lxc_provision/tasks/create.yml b/roles/proxmox_lxc_provision/tasks/create.yml index c12fd8f..7727fba 100755 --- a/roles/proxmox_lxc_provision/tasks/create.yml +++ b/roles/proxmox_lxc_provision/tasks/create.yml @@ -1,7 +1,6 @@ --- - name: Create an LXC container community.general.proxmox: - <<: "{{ proxmox_api_connection }}" vmid: "{{ lxc_vmid | default(omit) }}" hostname: "{{ lxc_hostname }}" password: "{{ lxc_password }}" diff --git a/roles/proxmox_lxc_provision/tasks/delete.yml b/roles/proxmox_lxc_provision/tasks/delete.yml index 2c40c2a..c703c8f 100755 --- a/roles/proxmox_lxc_provision/tasks/delete.yml +++ b/roles/proxmox_lxc_provision/tasks/delete.yml @@ -1,8 +1,8 @@ --- -- ansible.builtin.include_tasks: stop.yaml +- ansible.builtin.include_tasks: stop.yml + - name: Delete a container community.general.proxmox: - <<: "{{ proxmox_api_connection }}" vmid: "{{ lxc_vmid | default(omit) }}" hostname: "{{ lxc_hostname | default(omit) }}" state: absent diff --git a/roles/proxmox_lxc_provision/tasks/main.yml b/roles/proxmox_lxc_provision/tasks/main.yml index eac864a..d37b5e2 100755 --- a/roles/proxmox_lxc_provision/tasks/main.yml +++ b/roles/proxmox_lxc_provision/tasks/main.yml @@ -1,50 +1,66 @@ --- -- 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 +- name: Proxmox LXC provision + module_defaults: + community.general.proxmox: &proxmox_defaults + 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 }}" + node: "{{ proxmox_node }}" + community.proxmox.proxmox_lxc_info: *proxmox_defaults block: - - name: Clone from template - ansible.builtin.include_tasks: clone.yaml - register: clone_result + - name: Check if container exists + community.general.proxmox: + vmid: "{{ lxc_vmid }}" + state: current + register: existing_container + ignore_errors: true - - name: Update container - ansible.builtin.include_tasks: update.yaml + - 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: + file: clone.yml + register: clone_result + + - name: Update container + ansible.builtin.include_tasks: + file: update.yml + vars: + lxc_vmid: "{{ clone_result.vmid }}" + register: lxc_result + + - name: Create the new container + ansible.builtin.include_tasks: + file: create.yml + when: lxc_template is defined and lxc_clone_from is undefined + + - name: Start the created container and wait for ssh vars: - lxc_vmid: "{{ clone_result.vmid }}" - register: lxc_result + lxc_vmid: "{{ lxc_result.vmid }}" + ansible.builtin.include_tasks: + file: "{{ item }}" + loop: + - start.yml + - wait.yml + when: lxc_start -- 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 + - name: Post clone updates + when: lxc_clone_from is defined + delegate_to: "{{ lxc_hostname }}" + block: + - name: Include post-clone tasks + ansible.builtin.include_tasks: + file: post-clone.yml diff --git a/roles/proxmox_lxc_provision/tasks/start.yml b/roles/proxmox_lxc_provision/tasks/start.yml index 492eb0d..088fc24 100755 --- a/roles/proxmox_lxc_provision/tasks/start.yml +++ b/roles/proxmox_lxc_provision/tasks/start.yml @@ -1,7 +1,6 @@ --- - name: Start the LXC container community.general.proxmox: - <<: "{{ proxmox_api_connection }}" vmid: "{{ lxc_result.vmid }}" state: started register: start_result diff --git a/roles/proxmox_lxc_provision/tasks/stop.yml b/roles/proxmox_lxc_provision/tasks/stop.yml index a721f70..06fc385 100755 --- a/roles/proxmox_lxc_provision/tasks/stop.yml +++ b/roles/proxmox_lxc_provision/tasks/stop.yml @@ -1,10 +1,9 @@ --- - 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" + state: stopped register: stop_result failed_when: |- stop_result.failed and diff --git a/roles/proxmox_lxc_provision/tasks/update.yml b/roles/proxmox_lxc_provision/tasks/update.yml index afd5ed4..ad3ecb6 100755 --- a/roles/proxmox_lxc_provision/tasks/update.yml +++ b/roles/proxmox_lxc_provision/tasks/update.yml @@ -1,10 +1,9 @@ --- - 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 + password: "{{ lxc_password | default(omit) }}" cores: "{{ lxc_cores }}" memory: "{{ lxc_memory }}" swap: "{{ lxc_swap }}"