91 lines
3.9 KiB
YAML

---
# roles/disk_grow/tasks/main.yml
#
# Idempotently grows the root partition and filesystem to fill the allocated
# virtual disk. Safe to re-run on already-expanded nodes (no-op).
#
# Prerequisites:
# The Proxmox virtual disk must already be resized via `qm resize` before
# this role runs. This role only handles the in-guest partition/fs layer.
#
# Idempotency contract:
# - growpart: exits 0 + prints CHANGED when it grew; exits 1 + prints NOCHANGE
# when already at disk boundary. failed_when excludes NOCHANGE.
# - resize2fs: prints "Nothing to do!" when filesystem already fills partition.
# Always exits 0. changed_when detects that message.
# --------------------------------------------------
# STEP 1: Install growpart (ships in cloud-guest-utils)
# WHY check first: on a 100%-full root disk, apt-get update writes to
# /var/cache/apt and will fail before installing anything. If growpart
# is already present (cloud images ship it), skip the apt step entirely.
# This avoids a chicken-and-egg failure where we need disk space to
# install the tool that creates disk space.
# --------------------------------------------------
- name: Check if growpart is already present
ansible.builtin.command: which growpart
register: disk_grow_growpart_check
changed_when: false
failed_when: false
tags: [disk_grow, packages]
- name: Install cloud-guest-utils if growpart is missing
ansible.builtin.apt:
name: cloud-guest-utils
state: present
when: disk_grow_growpart_check.rc != 0
tags: [disk_grow, packages]
# --------------------------------------------------
# STEP 2: Grow partition to fill allocated disk
# WHY growpart not parted module: growpart is the canonical safe tool for
# live root partition expansion without unmounting.
# --------------------------------------------------
- name: Grow root partition to fill disk boundary
ansible.builtin.command: >
growpart {{ disk_grow_device }} {{ disk_grow_partition_number }}
register: disk_grow_growpart_result
changed_when: "'CHANGED' in disk_grow_growpart_result.stdout"
failed_when: >
disk_grow_growpart_result.rc != 0
and 'NOCHANGE' not in disk_grow_growpart_result.stdout
tags: [disk_grow, partition]
# --------------------------------------------------
# STEP 3: Grow filesystem to fill the now-expanded partition
# WHY always run: resize2fs is safe on a live ext4 root filesystem and
# does nothing ("Nothing to do!") when already at full size.
# --------------------------------------------------
- name: Grow ext4 filesystem to fill partition
ansible.builtin.command: resize2fs {{ disk_grow_filesystem }}
register: disk_grow_resize2fs_result
changed_when: "'Nothing to do' not in disk_grow_resize2fs_result.stderr"
tags: [disk_grow, filesystem]
# --------------------------------------------------
# STEP 4: Assert result meets minimum size threshold
# WHY assert not debug: a still-small root means qm resize was skipped;
# hard fail prevents downstream tasks from hitting disk-full errors.
# --------------------------------------------------
- name: Get root filesystem total size
ansible.builtin.command: df -BG {{ disk_grow_mount_point }} --output=size
register: disk_grow_df_result
changed_when: false
tags: [disk_grow, verify]
- name: Assert root filesystem meets minimum size ({{ disk_grow_min_gb }}G)
ansible.builtin.assert:
that:
- disk_grow_df_result.stdout_lines[1] | regex_replace('[^0-9]', '') | int >= disk_grow_min_gb
fail_msg: >-
Root filesystem at {{ disk_grow_mount_point }} is
{{ disk_grow_df_result.stdout_lines[1] | trim }} — below the minimum {{ disk_grow_min_gb }}G.
Run 'qm resize <VMID> scsi0 32G' on the Proxmox host first, then re-run this playbook.
success_msg: >-
Root filesystem: {{ disk_grow_df_result.stdout_lines[1] | trim }} ✓
tags: [disk_grow, verify]