--- # ============================================================================ # expand-root-lv / main # ---------------------------------------------------------------------------- # 0. Ensure growpart is available (cloud-guest-utils provides the growpart binary) # 1. Grow the partition (growpart) if a PV partition device is defined # 2. Resize the physical volume (pvresize) to pick up the new partition size # 3. Confirm the target VG exists (skip role cleanly on non-LVM hosts). # 4. Read free physical-extent count for the VG. # 5. Extend the LV to +100%FREE only when free_pe > 0. # 6. Grow the filesystem on the mountpoint (ext4 -> resize2fs, xfs -> xfs_growfs). # Each step is idempotent and skips when there's nothing to do. # ============================================================================ # --------------------------------------------------------------------------- # Step 0: Ensure growpart is available # --------------------------------------------------------------------------- - name: Ensure cloud-guest-utils (growpart) is installed ansible.builtin.package: name: cloud-guest-utils state: present when: expand_root_lv_pv_partition is defined tags: [growpart, always] # --------------------------------------------------------------------------- # Step 1: Grow the partition that backs the PV # --------------------------------------------------------------------------- # growpart expects: growpart # e.g. growpart /dev/sda 3 — NOT growpart /dev/sda3 # We split expand_root_lv_pv_partition (e.g. /dev/sda3) into device and part_no. - name: Derive device and partition number from PV partition path ansible.builtin.set_fact: expand_root_lv_pv_device: "{{ expand_root_lv_pv_partition | regex_replace('p?(\\d+)$', '') }}" expand_root_lv_pv_part_no: "{{ expand_root_lv_pv_partition | regex_replace('.*p?(\\d+)$', '\\1') }}" when: expand_root_lv_pv_partition is defined tags: [growpart, always] - name: Grow partition to fill disk (growpart) ansible.builtin.command: cmd: "growpart {{ expand_root_lv_pv_device }} {{ expand_root_lv_pv_part_no }}" register: growpart_result when: expand_root_lv_pv_partition is defined changed_when: growpart_result.rc == 0 and "NO CHANGE" not in growpart_result.stdout tags: [growpart, always] # --------------------------------------------------------------------------- # Step 2: Resize the physical volume to claim the new partition space # --------------------------------------------------------------------------- - name: Resize physical volume (pvresize) ansible.builtin.command: cmd: "pvresize {{ expand_root_lv_pv_partition }}" register: pvresize_result when: expand_root_lv_pv_partition is defined changed_when: pvresize_result.rc == 0 tags: [pvresize, always] # --------------------------------------------------------------------------- # Step 3: Confirm the target VG exists (skip role cleanly on non-LVM hosts). # --------------------------------------------------------------------------- - name: Gather LVM facts ansible.builtin.command: cmd: "vgs --noheadings --nosuffix --units b -o vg_name,vg_free_count {{ expand_root_lv_vg_name }}" register: vg_info changed_when: false failed_when: false tags: [lvm, always] - name: Skip role when target VG is absent ansible.builtin.meta: end_play when: vg_info.rc != 0 tags: [lvm, always] # --------------------------------------------------------------------------- # Step 4: Parse free PE count for the VG # --------------------------------------------------------------------------- - name: Parse free PE count ansible.builtin.set_fact: expand_root_lv_free_pe: "{{ (vg_info.stdout.split() | last | int) if vg_info.stdout | length > 0 else 0 }}" tags: [lvm, always] # --------------------------------------------------------------------------- # Step 5: Extend the LV to +100%FREE only when free_pe > 0 # --------------------------------------------------------------------------- - name: Extend LV to fill VG (only if free PE > 0) ansible.builtin.command: cmd: "lvextend -l +100%FREE /dev/{{ expand_root_lv_vg_name }}/{{ expand_root_lv_lv_name }}" register: lvextend_result when: expand_root_lv_free_pe | int > 0 changed_when: lvextend_result.rc == 0 tags: [lvm, always] # --------------------------------------------------------------------------- # Step 6: Detect filesystem type at mountpoint and grow # --------------------------------------------------------------------------- - name: Detect filesystem type at mountpoint ansible.builtin.command: cmd: "findmnt {{ expand_root_lv_mountpoint }} -no FSTYPE" register: fstype_result changed_when: false tags: [filesystem, always] - name: Set filesystem type fact ansible.builtin.set_fact: expand_root_lv_fstype: "{{ fstype_result.stdout | trim }}" tags: [filesystem, always] - name: Grow ext4 filesystem ansible.builtin.command: cmd: "resize2fs /dev/{{ expand_root_lv_vg_name }}/{{ expand_root_lv_lv_name }}" register: resize_result when: - expand_root_lv_fstype == "ext4" - (growpart_result is defined and growpart_result.changed) or (pvresize_result is defined and pvresize_result.changed) or (lvextend_result is defined and lvextend_result.changed) or (growpart_result is not defined) changed_when: resize_result.rc == 0 tags: [filesystem, always] - name: Grow xfs filesystem ansible.builtin.command: cmd: "xfs_growfs {{ expand_root_lv_mountpoint }}" register: xfs_result when: - expand_root_lv_fstype == "xfs" - (growpart_result is defined and growpart_result.changed) or (pvresize_result is defined and pvresize_result.changed) or (lvextend_result is defined and lvextend_result.changed) or (growpart_result is not defined) changed_when: xfs_result.rc == 0 tags: [filesystem, always] # --------------------------------------------------------------------------- # Step 7: Report current root size # --------------------------------------------------------------------------- - name: Report current root size ansible.builtin.command: cmd: "df -h {{ expand_root_lv_mountpoint }}" register: df_result changed_when: false tags: [always] - name: Show post-resize disk usage ansible.builtin.debug: msg: "{{ df_result.stdout_lines }}" tags: [always]