From c9b9fce8a2196dd676642f4dbf4dfe53cdd37eab Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Tue, 8 Sep 2026 14:10:23 +0100 Subject: [PATCH 1/3] Add protected option to image uploads --- roles/os_images/README.md | 2 ++ roles/os_images/tasks/upload.yml | 52 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/roles/os_images/README.md b/roles/os_images/README.md index 1f956f0..316b8d3 100644 --- a/roles/os_images/README.md +++ b/roles/os_images/README.md @@ -85,6 +85,8 @@ mutually exclusive where each contain: * `use_import`: (optional) Whether to use an import workflow instead of direct upload. Useful in conjuction with an [interoperable image import](https://docs.openstack.org/glance/latest/admin/interoperable-image-import.html). Defaults to 'false'. +* `protected`: (optional) Whether the uploaded image should be protected from deletion. + Existing protected images are automatically unprotected before a forced rebuild. `os_images_common`: A set of elements to include in every image listed. Defaults to `cloud-init enable-serial-console stable-interface-names`. diff --git a/roles/os_images/tasks/upload.yml b/roles/os_images/tasks/upload.yml index b8a0e56..4351007 100644 --- a/roles/os_images/tasks/upload.yml +++ b/roles/os_images/tasks/upload.yml @@ -1,4 +1,27 @@ --- +- name: Gather existing cloud tenant images + openstack.cloud.image_info: + auth_type: "{{ os_images_auth_type }}" + auth: "{{ os_images_auth }}" + cacert: "{{ os_images_cacert | default(omit) }}" + interface: "{{ os_images_interface | default(omit, true) }}" + region_name: "{{ os_images_region | default(omit) }}" + register: existing_images + +- name: Unprotect existing cloud tenant kernel + ansible.builtin.command: >- + {{ os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack' }} + image set --unprotected {{ item.name ~ '-kernel' }} + with_items: "{{ os_images_list | list }}" + loop_control: + label: "{{ item.name }}" + when: + - item.elements is defined + - '"baremetal" in item.elements' + - item.force_rebuild | default(os_images_force_rebuild) | bool + - item.name ~ '-kernel' in existing_images.images | map(attribute='name') | list + changed_when: true + - name: Ensure existing cloud tenant kernel does not exist openstack.cloud.image: auth_type: "{{ os_images_auth_type }}" @@ -30,6 +53,7 @@ container_format: aki disk_format: aki filename: "{{ os_images_cache }}/{{ item.name }}/{{ item.name }}.vmlinuz" + protected: "{{ item.protected | default(omit) }}" with_items: "{{ os_images_list | list }}" vars: visibility: "{{ item.visibility | default(item.is_public | ternary('public', 'private') if item.is_public is defined else os_images_visibility) }}" @@ -40,6 +64,20 @@ - '"baremetal" in item.elements' register: kernel_result +- name: Unprotect existing cloud tenant ramdisk + ansible.builtin.command: >- + {{ os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack' }} + image set --unprotected {{ item.name ~ '-ramdisk' }} + with_items: "{{ os_images_list | list }}" + loop_control: + label: "{{ item.name }}" + when: + - item.elements is defined + - '"baremetal" in item.elements' + - item.force_rebuild | default(os_images_force_rebuild) | bool + - item.name ~ '-ramdisk' in existing_images.images | map(attribute='name') | list + changed_when: true + - name: Ensure existing cloud tenant ramdisk does not exist openstack.cloud.image: auth_type: "{{ os_images_auth_type }}" @@ -71,6 +109,7 @@ container_format: ari disk_format: ari filename: "{{ os_images_cache }}/{{ item.name }}/{{ item.name }}.initrd" + protected: "{{ item.protected | default(omit) }}" with_items: "{{ os_images_list | list }}" vars: visibility: "{{ item.visibility | default(item.is_public | ternary('public', 'private') if item.is_public is defined else os_images_visibility) }}" @@ -81,6 +120,18 @@ - '"baremetal" in item.elements' register: ramdisk_result +- name: Unprotect existing cloud tenant image + ansible.builtin.command: >- + {{ os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack' }} + image set --unprotected {{ item.name }} + with_items: "{{ os_images_list | list }}" + loop_control: + label: "{{ item.name }}" + when: + - item.force_rebuild | default(os_images_force_rebuild) | bool + - item.name in existing_images.images | map(attribute='name') | list + changed_when: true + - name: Ensure existing cloud tenant image does not exist openstack.cloud.image: auth_type: "{{ os_images_auth_type }}" @@ -114,6 +165,7 @@ kernel: "{{ item.1.id if is_baremetal else omit }}" ramdisk: "{{ item.2.id if is_baremetal else omit }}" use_import: "{{ item.0.use_import | default(omit) }}" + protected: "{{ item.0.protected | default(omit) }}" vars: # NOTE(m-anson): When architecture isn't defined for an # image, assume that we should set cpu_arch: x86_64 as From 43a247e2c1f3a6a82d8978e8ec169865441e1542 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Thu, 24 Sep 2026 16:38:31 +0000 Subject: [PATCH 2/3] Use argv to prevent word splitting --- roles/os_images/tasks/upload.yml | 30 +++++++++++++++++++++--------- roles/os_images/vars/main.yml | 15 +++++++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 roles/os_images/vars/main.yml diff --git a/roles/os_images/tasks/upload.yml b/roles/os_images/tasks/upload.yml index 4351007..4852f02 100644 --- a/roles/os_images/tasks/upload.yml +++ b/roles/os_images/tasks/upload.yml @@ -9,9 +9,13 @@ register: existing_images - name: Unprotect existing cloud tenant kernel - ansible.builtin.command: >- - {{ os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack' }} - image set --unprotected {{ item.name ~ '-kernel' }} + ansible.builtin.command: + argv: >- + {{ + [os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack', + 'image', 'set', '--unprotected', item.name ~ '-kernel'] + }} + environment: "{{ os_images_connection_env }}" with_items: "{{ os_images_list | list }}" loop_control: label: "{{ item.name }}" @@ -65,9 +69,13 @@ register: kernel_result - name: Unprotect existing cloud tenant ramdisk - ansible.builtin.command: >- - {{ os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack' }} - image set --unprotected {{ item.name ~ '-ramdisk' }} + ansible.builtin.command: + argv: >- + {{ + [os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack', + 'image', 'set', '--unprotected', item.name ~ '-ramdisk'] + }} + environment: "{{ os_images_connection_env }}" with_items: "{{ os_images_list | list }}" loop_control: label: "{{ item.name }}" @@ -121,9 +129,13 @@ register: ramdisk_result - name: Unprotect existing cloud tenant image - ansible.builtin.command: >- - {{ os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack' }} - image set --unprotected {{ item.name }} + ansible.builtin.command: + argv: >- + {{ + [os_images_venv ~ '/bin/openstack' if os_images_venv else 'openstack', + 'image', 'set', '--unprotected', item.name] + }} + environment: "{{ os_images_connection_env }}" with_items: "{{ os_images_list | list }}" loop_control: label: "{{ item.name }}" diff --git a/roles/os_images/vars/main.yml b/roles/os_images/vars/main.yml new file mode 100644 index 0000000..753ab85 --- /dev/null +++ b/roles/os_images/vars/main.yml @@ -0,0 +1,15 @@ +--- +os_images_connection_env: >- + {{ + dict( + (os_images_auth | default({}, true)).keys() | list + | map('regex_replace', '^', 'OS_') + | map('upper') + | list + | zip((os_images_auth | default({}, true)).values() | list) + ) + | combine({'OS_AUTH_TYPE': os_images_auth_type} if os_images_auth_type | default('', true) else {}) + | combine({'OS_CACERT': os_images_cacert} if os_images_cacert | default('', true) else {}) + | combine({'OS_INTERFACE': os_images_interface} if os_images_interface | default('', true) else {}) + | combine({'OS_REGION_NAME': os_images_region} if os_images_region | default('', true) else {}) + }} From c7bb5be32a23d460a34420ff76a482e23d28c934 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Fri, 25 Sep 2026 13:33:17 +0000 Subject: [PATCH 3/3] Add tags --- roles/os_images/tasks/upload.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/roles/os_images/tasks/upload.yml b/roles/os_images/tasks/upload.yml index 4351007..b5fdc83 100644 --- a/roles/os_images/tasks/upload.yml +++ b/roles/os_images/tasks/upload.yml @@ -7,6 +7,7 @@ interface: "{{ os_images_interface | default(omit, true) }}" region_name: "{{ os_images_region | default(omit) }}" register: existing_images + tags: always - name: Unprotect existing cloud tenant kernel ansible.builtin.command: >- @@ -21,6 +22,7 @@ - item.force_rebuild | default(os_images_force_rebuild) | bool - item.name ~ '-kernel' in existing_images.images | map(attribute='name') | list changed_when: true + tags: clean - name: Ensure existing cloud tenant kernel does not exist openstack.cloud.image: @@ -77,6 +79,7 @@ - item.force_rebuild | default(os_images_force_rebuild) | bool - item.name ~ '-ramdisk' in existing_images.images | map(attribute='name') | list changed_when: true + tags: clean - name: Ensure existing cloud tenant ramdisk does not exist openstack.cloud.image: @@ -131,6 +134,7 @@ - item.force_rebuild | default(os_images_force_rebuild) | bool - item.name in existing_images.images | map(attribute='name') | list changed_when: true + tags: clean - name: Ensure existing cloud tenant image does not exist openstack.cloud.image: