From ec7ff790f29e83301466b3435b1a5735e63a043e Mon Sep 17 00:00:00 2001 From: sfulmer Date: Wed, 15 Apr 2026 10:55:18 -0400 Subject: [PATCH 01/10] feat(vm-storage-labeling): add storage volume labeling role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move from openshift_virtualization_migration repo — this is day 2 ops functionality for labeling PVCs/DataVolumes on existing VMs. Co-Authored-By: Claude Opus 4.6 --- playbooks/vm_storage_labeling.yml | 15 ++++ roles/vm_storage_labeling/defaults/main.yml | 32 ++++++++ roles/vm_storage_labeling/meta/main.yml | 10 +++ .../tasks/_apply_metadata.yml | 75 +++++++++++++++++ .../tasks/_process_request.yml | 82 +++++++++++++++++++ roles/vm_storage_labeling/tasks/main.yml | 40 +++++++++ roles/vm_storage_labeling/tests/inventory | 1 + roles/vm_storage_labeling/tests/test.yml | 7 ++ 8 files changed, 262 insertions(+) create mode 100644 playbooks/vm_storage_labeling.yml create mode 100644 roles/vm_storage_labeling/defaults/main.yml create mode 100644 roles/vm_storage_labeling/meta/main.yml create mode 100644 roles/vm_storage_labeling/tasks/_apply_metadata.yml create mode 100644 roles/vm_storage_labeling/tasks/_process_request.yml create mode 100644 roles/vm_storage_labeling/tasks/main.yml create mode 100644 roles/vm_storage_labeling/tests/inventory create mode 100644 roles/vm_storage_labeling/tests/test.yml diff --git a/playbooks/vm_storage_labeling.yml b/playbooks/vm_storage_labeling.yml new file mode 100644 index 0000000..add6b40 --- /dev/null +++ b/playbooks/vm_storage_labeling.yml @@ -0,0 +1,15 @@ +--- + +- name: VM Storage Volume Labeling + hosts: localhost + connection: local + gather_facts: false + tasks: + - name: Invoke VM Storage Volume Labeling + ansible.builtin.include_role: + name: infra.openshift_virtualization_ops.vm_storage_labeling + vars: + openshift_host: "{{ lookup('ansible.builtin.env', 'K8S_AUTH_HOST', default=Undefined) | default('', True) }}" + openshift_api_key: "{{ lookup('ansible.builtin.env', 'K8S_AUTH_API_KEY', default=Undefined) | default('', True) }}" # noqa: yaml[line-length] + openshift_verify_ssl: "{{ lookup('ansible.builtin.env', 'K8S_AUTH_VERIFY_SSL', default='') | default(false) | bool }}" # noqa: yaml[line-length] +... diff --git a/roles/vm_storage_labeling/defaults/main.yml b/roles/vm_storage_labeling/defaults/main.yml new file mode 100644 index 0000000..a5195cf --- /dev/null +++ b/roles/vm_storage_labeling/defaults/main.yml @@ -0,0 +1,32 @@ +--- +# defaults file for vm_storage_labeling + +# title: Storage Volume Labeling Request +# required: True +# description: List of Storage Volume Labeling Requests +vm_storage_labeling_request: [] +# - namespace: # Namespace containing storage volumes to label. +# names: # List of PVC/DataVolume names within a namespace. \ +# Optional when using label_selectors. +# label_selectors: # Label selectors to match volumes. \ +# Cannot be used with list of volume names. +# - = +# labels: # Labels to apply to matched volumes. +# : +# annotations: # Annotations to apply to matched volumes. +# : + +# title: OpenShift Host +# required: True +# description: OpenShift Host +vm_storage_labeling_openshift_host: "{{ openshift_host }}" +# title: OpenShift API Key +# required: True +# description: OpenShift API Key +vm_storage_labeling_api_key: "{{ openshift_api_key }}" +# title: Verify SSL Certificate +# required: True +# description: Verify SSL Certificate +vm_storage_labeling_openshift_verify_ssl: "{{ openshift_verify_ssl }}" + +... diff --git a/roles/vm_storage_labeling/meta/main.yml b/roles/vm_storage_labeling/meta/main.yml new file mode 100644 index 0000000..1ff34cd --- /dev/null +++ b/roles/vm_storage_labeling/meta/main.yml @@ -0,0 +1,10 @@ +--- +galaxy_info: + author: "" + description: Add labels, annotations, and descriptive names to storage volumes (PVCs and DataVolumes). + company: Red Hat + license: GPL-3.0-only + min_ansible_version: 2.15.0 + galaxy_tags: [] +dependencies: [] +... diff --git a/roles/vm_storage_labeling/tasks/_apply_metadata.yml b/roles/vm_storage_labeling/tasks/_apply_metadata.yml new file mode 100644 index 0000000..1f78771 --- /dev/null +++ b/roles/vm_storage_labeling/tasks/_apply_metadata.yml @@ -0,0 +1,75 @@ +--- + +- name: _apply_metadata | Initialize Patch Operations + ansible.builtin.set_fact: + vm_storage_labeling_patch: [] + +- name: _apply_metadata | Build Label Patch Operations + when: vm_storage_labeling_current_request.labels | default({}, true) | length > 0 + ansible.builtin.set_fact: + vm_storage_labeling_patch: >- + {{ vm_storage_labeling_patch + [ + { + 'op': ('replace' + if (vm_storage_labeling_volume.metadata.labels | default({}) | dict2items + | selectattr('key', 'equalto', vm_storage_labeling_label.key) | list | length > 0) + else 'add'), + 'path': '/metadata/labels/' + (vm_storage_labeling_label.key | regex_replace('/', '~1')), + 'value': vm_storage_labeling_label.value + } + ] }} + loop: "{{ vm_storage_labeling_current_request.labels | dict2items }}" + loop_control: + loop_var: vm_storage_labeling_label + label: "{{ vm_storage_labeling_label.key }}" + +- name: _apply_metadata | Build Annotation Patch Operations + when: vm_storage_labeling_current_request.annotations | default({}, true) | length > 0 + ansible.builtin.set_fact: + vm_storage_labeling_patch: >- + {{ vm_storage_labeling_patch + [ + { + 'op': ('replace' + if (vm_storage_labeling_volume.metadata.annotations | default({}) | dict2items + | selectattr('key', 'equalto', vm_storage_labeling_annotation.key) | list | length > 0) + else 'add'), + 'path': '/metadata/annotations/' + (vm_storage_labeling_annotation.key | regex_replace('/', '~1')), + 'value': vm_storage_labeling_annotation.value + } + ] }} + loop: "{{ vm_storage_labeling_current_request.annotations | dict2items }}" + loop_control: + loop_var: vm_storage_labeling_annotation + label: "{{ vm_storage_labeling_annotation.key }}" + +- name: _apply_metadata | Apply Patch to PersistentVolumeClaim + when: vm_storage_labeling_patch | length > 0 + kubernetes.core.k8s_json_patch: + api_key: "{{ vm_storage_labeling_api_key }}" + host: "{{ vm_storage_labeling_openshift_host }}" + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ vm_storage_labeling_volume.metadata.namespace }}" + name: "{{ vm_storage_labeling_volume.metadata.name }}" + validate_certs: "{{ vm_storage_labeling_openshift_verify_ssl }}" + patch: "{{ vm_storage_labeling_patch }}" + register: vm_storage_labeling_patch_result + +- name: _apply_metadata | Apply Patch to DataVolume + when: + - vm_storage_labeling_patch | length > 0 + - vm_storage_labeling_volume.metadata.ownerReferences | default([]) + | selectattr('kind', 'equalto', 'DataVolume') | list | length > 0 + kubernetes.core.k8s_json_patch: + api_key: "{{ vm_storage_labeling_api_key }}" + host: "{{ vm_storage_labeling_openshift_host }}" + api_version: cdi.kubevirt.io/v1beta1 + kind: DataVolume + namespace: "{{ vm_storage_labeling_volume.metadata.namespace }}" + name: "{{ vm_storage_labeling_volume.metadata.name }}" + validate_certs: "{{ vm_storage_labeling_openshift_verify_ssl }}" + patch: "{{ vm_storage_labeling_patch }}" + register: vm_storage_labeling_dv_patch_result + failed_when: false + +... diff --git a/roles/vm_storage_labeling/tasks/_process_request.yml b/roles/vm_storage_labeling/tasks/_process_request.yml new file mode 100644 index 0000000..054081a --- /dev/null +++ b/roles/vm_storage_labeling/tasks/_process_request.yml @@ -0,0 +1,82 @@ +--- + +- name: _process_request | Query PVCs by Name + when: vm_storage_labeling_current_request.names | default([], true) | length > 0 + kubernetes.core.k8s_info: + api_key: "{{ vm_storage_labeling_api_key }}" + host: "{{ vm_storage_labeling_openshift_host }}" + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ vm_storage_labeling_current_request.namespace }}" + name: "{{ vm_storage_labeling_pvc_name }}" + validate_certs: "{{ vm_storage_labeling_openshift_verify_ssl }}" + register: vm_storage_labeling_pvc_by_name + loop: "{{ vm_storage_labeling_current_request.names }}" + loop_control: + loop_var: vm_storage_labeling_pvc_name + label: "{{ vm_storage_labeling_pvc_name }}" + +- name: _process_request | Query PVCs by Label Selector + when: vm_storage_labeling_current_request.label_selectors | default([], true) | length > 0 + kubernetes.core.k8s_info: + api_key: "{{ vm_storage_labeling_api_key }}" + host: "{{ vm_storage_labeling_openshift_host }}" + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ vm_storage_labeling_current_request.namespace }}" + label_selectors: "{{ vm_storage_labeling_current_request.label_selectors }}" + validate_certs: "{{ vm_storage_labeling_openshift_verify_ssl }}" + register: vm_storage_labeling_pvc_by_selector + +- name: _process_request | Query All PVCs in Namespace + when: + - vm_storage_labeling_current_request.names | default([], true) | length == 0 + - vm_storage_labeling_current_request.label_selectors | default([], true) | length == 0 + kubernetes.core.k8s_info: + api_key: "{{ vm_storage_labeling_api_key }}" + host: "{{ vm_storage_labeling_openshift_host }}" + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ vm_storage_labeling_current_request.namespace }}" + validate_certs: "{{ vm_storage_labeling_openshift_verify_ssl }}" + register: vm_storage_labeling_pvc_all + +- name: _process_request | Build Volume List from Named PVCs + when: vm_storage_labeling_pvc_by_name is not skipped + ansible.builtin.set_fact: + vm_storage_labeling_volumes: >- + {{ vm_storage_labeling_pvc_by_name.results + | selectattr('resources', 'defined') + | map(attribute='resources') + | flatten }} + +- name: _process_request | Build Volume List from Selector PVCs + when: vm_storage_labeling_pvc_by_selector is not skipped + ansible.builtin.set_fact: + vm_storage_labeling_volumes: "{{ vm_storage_labeling_pvc_by_selector.resources | default([]) }}" + +- name: _process_request | Build Volume List from All PVCs + when: vm_storage_labeling_pvc_all is not skipped + ansible.builtin.set_fact: + vm_storage_labeling_volumes: "{{ vm_storage_labeling_pvc_all.resources | default([]) }}" + +- name: _process_request | Verify Volumes Found + ansible.builtin.assert: + that: + - vm_storage_labeling_volumes | default([], true) | length > 0 + fail_msg: >- + No PersistentVolumeClaims found in namespace '{{ vm_storage_labeling_current_request.namespace }}' + matching the provided criteria + quiet: true + +- name: _process_request | Apply Labels and Annotations to Volumes + ansible.builtin.include_tasks: + file: _apply_metadata.yml + loop: "{{ vm_storage_labeling_volumes }}" + loop_control: + loop_var: vm_storage_labeling_volume + label: >- + Namespace: {{ vm_storage_labeling_volume.metadata.namespace }} + - Name: {{ vm_storage_labeling_volume.metadata.name }} + +... diff --git a/roles/vm_storage_labeling/tasks/main.yml b/roles/vm_storage_labeling/tasks/main.yml new file mode 100644 index 0000000..8746a40 --- /dev/null +++ b/roles/vm_storage_labeling/tasks/main.yml @@ -0,0 +1,40 @@ +--- + +- name: Verify vm_storage_labeling_request Variable Provided + ansible.builtin.assert: + that: + - vm_storage_labeling_request | default("", true) | length > 0 + fail_msg: "'vm_storage_labeling_request' Variable Not Provided" + quiet: true + +- name: Verify Required Properties Provided + ansible.builtin.assert: + that: + - vm_storage_labeling_request | selectattr('namespace', 'undefined') | list | length == 0 + fail_msg: "Required property 'namespace' in 'vm_storage_labeling_request' Variable Not Provided" + quiet: true + +- name: Verify Labels or Annotations Provided + ansible.builtin.assert: + that: + - >- + (vm_storage_labeling_item.labels | default({}, true) | length > 0) or + (vm_storage_labeling_item.annotations | default({}, true) | length > 0) + fail_msg: >- + Either 'labels' or 'annotations' must be provided for + namespace '{{ vm_storage_labeling_item.namespace }}' + quiet: true + loop: "{{ vm_storage_labeling_request }}" + loop_control: + loop_var: vm_storage_labeling_item + label: "Namespace: {{ vm_storage_labeling_item.namespace }}" + +- name: Process Storage Volume Labeling Request + ansible.builtin.include_tasks: + file: _process_request.yml + loop: "{{ vm_storage_labeling_request }}" + loop_control: + loop_var: vm_storage_labeling_current_request + label: "Namespace: {{ vm_storage_labeling_current_request.namespace }}" + +... diff --git a/roles/vm_storage_labeling/tests/inventory b/roles/vm_storage_labeling/tests/inventory new file mode 100644 index 0000000..2fbb50c --- /dev/null +++ b/roles/vm_storage_labeling/tests/inventory @@ -0,0 +1 @@ +localhost diff --git a/roles/vm_storage_labeling/tests/test.yml b/roles/vm_storage_labeling/tests/test.yml new file mode 100644 index 0000000..fc12bcc --- /dev/null +++ b/roles/vm_storage_labeling/tests/test.yml @@ -0,0 +1,7 @@ +--- +- name: Test + hosts: localhost + remote_user: root + roles: + - vm_storage_labeling +... From 53b8d8ebf583f6052e9954f3926fe489e52770b9 Mon Sep 17 00:00:00 2001 From: sfulmer Date: Sat, 2 May 2026 09:51:58 -0400 Subject: [PATCH 02/10] fix: add missing allowlist_externals for tox-ansible environments --- tox-ansible.ini | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tox-ansible.ini b/tox-ansible.ini index 3e1b6d9..27d44ea 100644 --- a/tox-ansible.ini +++ b/tox-ansible.ini @@ -31,6 +31,14 @@ allowlist_externals = mkdir ln bash + echo + git + ansible-galaxy + ansible-test + ansible-doc + pytest + mkdir + ln commands = mkdir -p "{env:HOME}/.ansible/collections/ansible_collections" ansible-galaxy collection install "{toxinidir}" -p '{env:HOME}/.ansible/collections/ansible_collections' --force @@ -50,6 +58,14 @@ change_dir = {env:HOME}/.ansible/collections/ansible_collections/infra/openshift skip_install = false allowlist_externals = bash + echo + git + ansible-galaxy + ansible-test + ansible-doc + pytest + mkdir + ln commands = bash -c 'git init --initial-branch=main .' bash -c 'ansible-test integration' From beebafa42ba7a0f6db2f751377b2afb03ce8f734 Mon Sep 17 00:00:00 2001 From: sfulmer Date: Sat, 2 May 2026 10:00:31 -0400 Subject: [PATCH 03/10] fix: let tox-ansible manage test commands instead of overriding --- tox-ansible.ini | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tox-ansible.ini b/tox-ansible.ini index 27d44ea..5c79a79 100644 --- a/tox-ansible.ini +++ b/tox-ansible.ini @@ -46,15 +46,13 @@ commands = [testenv] set_env = - COLLECTIONS_PATH = "{env:HOME}/.ansible/collections/ansible_collections" + COLLECTIONS_PATH = {env:HOME}/.ansible/collections/ansible_collections FORCE_COLOR = 1 passenv = HOME ANSIBLE_COLLECTIONS_PATH + ANSIBLE_GALAXY_* no_package = true -deps = - -r tests/integration/requirements.txt -change_dir = {env:HOME}/.ansible/collections/ansible_collections/infra/openshift_virtualization_ops skip_install = false allowlist_externals = bash @@ -66,6 +64,5 @@ allowlist_externals = pytest mkdir ln -commands = - bash -c 'git init --initial-branch=main .' - bash -c 'ansible-test integration' + sh + ade From 9d2e4ee4418c1f259b09f46c632c0e5df8a5c559 Mon Sep 17 00:00:00 2001 From: sfulmer Date: Sat, 2 May 2026 10:11:16 -0400 Subject: [PATCH 04/10] fix: add distlib to integration requirements for manifest support --- tests/integration/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/requirements.txt b/tests/integration/requirements.txt index 8b3ac1e..04708bc 100644 --- a/tests/integration/requirements.txt +++ b/tests/integration/requirements.txt @@ -1 +1,2 @@ # Add python packages that are required for integration testing +distlib From 190b6f67b0811ed2dcf5d451b00d50ea1eb3a666 Mon Sep 17 00:00:00 2001 From: sfulmer Date: Sat, 2 May 2026 10:22:53 -0400 Subject: [PATCH 05/10] fix: add distlib to testenv deps for galaxy/sanity manifest support --- tox-ansible.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox-ansible.ini b/tox-ansible.ini index 5c79a79..d8b5c88 100644 --- a/tox-ansible.ini +++ b/tox-ansible.ini @@ -54,6 +54,8 @@ passenv = ANSIBLE_GALAXY_* no_package = true skip_install = false +deps = + distlib allowlist_externals = bash echo From aea07315bf66bbb21e1b12f34b349b62e820e717 Mon Sep 17 00:00:00 2001 From: sfulmer Date: Sat, 2 May 2026 10:26:49 -0400 Subject: [PATCH 06/10] =?UTF-8?q?fix:=20remove=20testenv=20overrides=20?= =?UTF-8?q?=E2=80=94=20only=20set=20allowlist=5Fexternals,=20let=20tox-ans?= =?UTF-8?q?ible=20manage=20deps/commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tox-ansible.ini | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tox-ansible.ini b/tox-ansible.ini index d8b5c88..5e0966b 100644 --- a/tox-ansible.ini +++ b/tox-ansible.ini @@ -31,31 +31,12 @@ allowlist_externals = mkdir ln bash - echo - git - ansible-galaxy - ansible-test - ansible-doc - pytest - mkdir - ln commands = mkdir -p "{env:HOME}/.ansible/collections/ansible_collections" ansible-galaxy collection install "{toxinidir}" -p '{env:HOME}/.ansible/collections/ansible_collections' --force ansible-galaxy collection install -r "{toxinidir}/requirements-dev.yml" [testenv] -set_env = - COLLECTIONS_PATH = {env:HOME}/.ansible/collections/ansible_collections - FORCE_COLOR = 1 -passenv = - HOME - ANSIBLE_COLLECTIONS_PATH - ANSIBLE_GALAXY_* -no_package = true -skip_install = false -deps = - distlib allowlist_externals = bash echo From 90c604d6496ca12e1e97053313741c398a376b3a Mon Sep 17 00:00:00 2001 From: sfulmer Date: Sat, 2 May 2026 10:38:05 -0400 Subject: [PATCH 07/10] fix: add distlib to galaxy tox env for manifest support --- tox-ansible.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tox-ansible.ini b/tox-ansible.ini index 5e0966b..9348a75 100644 --- a/tox-ansible.ini +++ b/tox-ansible.ini @@ -49,3 +49,8 @@ allowlist_externals = ln sh ade + +[testenv:galaxy] +deps = + galaxy-importer>=0.4.31 + distlib From 0b865dd09d2b50489aa186ec3a02356266f7c155 Mon Sep 17 00:00:00 2001 From: sfulmer Date: Mon, 4 May 2026 08:21:44 -0400 Subject: [PATCH 08/10] fix: add missing README for vm_storage_labeling role --- roles/vm_storage_labeling/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 roles/vm_storage_labeling/README.md diff --git a/roles/vm_storage_labeling/README.md b/roles/vm_storage_labeling/README.md new file mode 100644 index 0000000..b8ea4cc --- /dev/null +++ b/roles/vm_storage_labeling/README.md @@ -0,0 +1,17 @@ +# vm_storage_labeling + +Add labels, annotations, and descriptive names to storage volumes (PVCs and DataVolumes) in OpenShift Virtualization. + +## Requirements + +- `redhat.openshift_virtualization` collection +- `kubernetes.core` collection +- OpenShift cluster with Virtualization operator installed + +## Role Variables + +See `defaults/main.yml` for available variables. + +## License + +Apache-2.0 From 1bd00c9f6296115b9ff6e78d441b7309a9743c18 Mon Sep 17 00:00:00 2001 From: sfulmer Date: Mon, 4 May 2026 08:23:36 -0400 Subject: [PATCH 09/10] fix: restore original testenv config, only add allowlist_externals --- tox-ansible.ini | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tox-ansible.ini b/tox-ansible.ini index 9348a75..89248d9 100644 --- a/tox-ansible.ini +++ b/tox-ansible.ini @@ -37,20 +37,29 @@ commands = ansible-galaxy collection install -r "{toxinidir}/requirements-dev.yml" [testenv] +set_env = + COLLECTIONS_PATH = "{env:HOME}/.ansible/collections/ansible_collections" + FORCE_COLOR = 1 +passenv = + HOME + ANSIBLE_COLLECTIONS_PATH +no_package = true +deps = + -r tests/integration/requirements.txt +change_dir = {env:HOME}/.ansible/collections/ansible_collections/infra/openshift_virtualization_ops +skip_install = false allowlist_externals = bash echo git + mkdir + sh + ade ansible-galaxy ansible-test ansible-doc pytest - mkdir ln - sh - ade - -[testenv:galaxy] -deps = - galaxy-importer>=0.4.31 - distlib +commands = + bash -c 'git init --initial-branch=main .' + bash -c 'ansible-test integration' From bef3a512c64656667f88395adf6709accf76c079 Mon Sep 17 00:00:00 2001 From: sfulmer Date: Mon, 4 May 2026 08:36:39 -0400 Subject: [PATCH 10/10] fix: pin tox-ansible<26.2.2 to match main (26.3.0 requires ade) --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 9d3d4f9..0061feb 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,7 @@ pytest-cov coverage molecule tox -tox-ansible +tox-ansible<26.2.2 black jmespath ansible-core