Skip to content

Commit cb227fd

Browse files
committed
Add ansible approach.
Signed-off-by: Kurt Garloff <[email protected]>
1 parent f65f027 commit cb227fd

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
---
2+
# Logic to identify image with given os_distro, os_version, os_purpose
3+
# Simply find the image for new SCS clouds that have os_purpose set.
4+
# Fall back to name matching otherwise.
5+
# (c) Kurt Garloff <[email protected]>, 11/2025
6+
# SPDX-License-Identifier: MIT
7+
# Created with help from Claude AI
8+
9+
- name: Select Image with purpose and fall back to name matching
10+
hosts: localhost
11+
gather_facts: false
12+
vars:
13+
# Primary selection criteria
14+
os_version: '24.04'
15+
os_distro: 'ubuntu'
16+
os_purpose: 'generic'
17+
cloud_name: "{{ lookup('env', 'OS_CLOUD'} | default('openstack') }}"
18+
19+
tasks:
20+
- name: Get available images matching os_distro and os_version
21+
openstack.cloud.image_info:
22+
cloud: '{{ cloud_name }}'
23+
properties:
24+
os_distro: '{{ os_distro }}'
25+
os_version: '{{ os_version }}'
26+
register: _distro_images
27+
28+
- name: 'First choice: Match os_purpose'
29+
set_fact:
30+
_primary_images: >-
31+
{{
32+
_distro_images.images
33+
| selectattr('properties.os_purpose', 'defined')
34+
| selectattr('properties.os_purpose', 'equalto', os_purpose)
35+
| list
36+
| sort(attribute='created_at', reverse=true)
37+
| sort(attribute='name', reverse=true)
38+
}}
39+
40+
- name: 'Select primary image if found'
41+
set_fact:
42+
selected_image_id: '{{ _primary_images[0].id }}'
43+
selected_image_name: '{{ _primary_images[0].name }}'
44+
selected_image: '{{ _primary_images[0] }}'
45+
match_type: 'primary'
46+
when: _primary_images | length > 0
47+
48+
# Fallback logic - only executed if no primary match
49+
- name: "Fallback 1: Filter images without os_purpose, matching name pattern '{{ os_distro }} {{ os_version }} {{ os_purpose }}'"
50+
set_fact:
51+
_fallback1_images: >-
52+
{{
53+
_distro_images.images
54+
| rejectattr('properties.os_purpose', 'defined')
55+
| selectattr('name', 'match', '(?i)^.*' + os_distro | regex_escape + '\\s+' + os_version | regex_escape + '\\s+' + os_purpose | regex_escape + '.*$')
56+
| list
57+
| sort(attribute='created_at', reverse=true)
58+
| sort(attribute='name', reverse=true)
59+
}}
60+
when: _primary_images | length == 0
61+
62+
- name: Select fallback 1 match if found
63+
set_fact:
64+
selected_image_id: '{{ _fallback1_matches[0].id }}'
65+
selected_image_name: '{{ _fallback1_matches[0].name }}'
66+
selected_image: '{{ _fallback1_matches[0] }}'
67+
match_type: 'fallback_pattern1'
68+
when:
69+
- _primary_images | length == 0
70+
- _fallback1_images | length > 0
71+
72+
- name: "Fallback 2: Filter images without os_purpose, matching name pattern '{{ os_distro }} {{ os_purpose }} {{ os_version }}'"
73+
set_fact:
74+
_fallback2_images: >-
75+
{{
76+
_distro_images.images
77+
| rejectattr('properties.os_purpose', 'defined')
78+
| selectattr('name', 'match', '(?i)^.*' + os_distro | regex_escape + '\\s+' + os_purpose | regex_escape + '\\s+' + os_version | regex_escape + '.*$')
79+
| list
80+
| sort(attribute='created_at', reverse=true)
81+
| sort(attribute='name', reverse=true)
82+
}}
83+
when:
84+
- _primary_images | length == 0
85+
- _fallback1_images | default([]) | length == 0
86+
87+
- name: Select fallback 2 match if found
88+
set_fact:
89+
selected_image_id: '{{ _fallback2_matches[0].id }}'
90+
selected_image_name: '{{ _fallback2_matches[0].name }}'
91+
selected_image: '{{ _fallback2_matches[0] }}'
92+
match_type: 'fallback_pattern2'
93+
when:
94+
- _primary_images | length == 0
95+
- _fallback1_images | default([]) | length == 0
96+
- _fallback2_images | length > 0
97+
98+
- name: "Fallback 3: Filter images without os_purpose, matching name pattern '{{ os_distro }} {{ os_version }}'"
99+
set_fact:
100+
_fallback3_images: >-
101+
{{
102+
_distro_images.images
103+
| rejectattr('properties.os_purpose', 'defined')
104+
| selectattr('name', 'match', '(?i)^.*' + os_distro | regex_escape + '\\s+' + os_version | regex_escape + '.*$')
105+
| list
106+
| sort(attribute='created_at', reverse=true)
107+
| sort(attribute='name', reverse=true)
108+
}}
109+
when:
110+
- _primary_images | length == 0
111+
- _fallback1_images | default([]) | length == 0
112+
- _fallback2_images | default([]) | length == 0
113+
114+
- name: Select fallback 3 match if found
115+
set_fact:
116+
selected_image_id: '{{ _fallback3_matches[0].id }}'
117+
selected_image_name: '{{ _fallback3_matches[0].name }}'
118+
selected_image: '{{ _fallback3_matches[0] }}'
119+
match_type: 'fallback_pattern3'
120+
when:
121+
- _primary_images | length == 0
122+
- _fallback1_images | default([]) | length == 0
123+
- _fallback2_images | default([]) | length == 0
124+
- _fallback3_images | length > 0
125+
126+
- name: Display selected image
127+
debug:
128+
msg:
129+
- 'Match Type: {{ match_type }}'
130+
- 'Image Name: {{ selected_image_name }}'
131+
- 'Image ID: {{ selected_image_id }}'
132+
- 'Created: {{ selected_image.created_at }}'
133+
- 'Properties: {{ selected_image.properties }}'
134+
when: selected_image is defined
135+
136+
- name: Fail if no suitable image found
137+
fail:
138+
msg: |
139+
No suitable image found matching criteria:
140+
- os_distro: {{ os_distro }}
141+
- os_version: {{ os_version }}
142+
- os_purpose: {{ os_purpose }}
143+
144+
Tried:
145+
1. Images with os_purpose property = '{{ os_purpose }}'
146+
2. Images matching name pattern '{{ os_distro }} {{ os_version }} {{ os_purpose }}'
147+
3. Images matching name pattern '{{ os_distro }} {{ os_purpose }} {{ os_version }}'
148+
4. Images matching name pattern '{{ os_distro }} {{ os_version }}'
149+
when: selected_image is not defined

user-docs/usage-hints/find-image/index.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,37 @@ resources:
199199
```
200200

201201
and call `openstack stack create --parameter image=$ID $TEMPLATE $STACKNAME`.
202+
203+
## ansible
204+
205+
Finding the right image in ansible can be done with a task that matches the properties
206+
in a straight forward way.
207+
208+
<!--TODO: The ansible stuff needs testing -->
209+
210+
```yaml
211+
tasks:
212+
- name: Get available images matching os_distro and os_version
213+
openstack.cloud.image_info:
214+
cloud: '{{ cloud_name }}'
215+
properties:
216+
os_distro: '{{ os_distro }}'
217+
os_version: '{{ os_version }}'
218+
os_purpose: '{{ os_purpose }}'
219+
register: _distro_purpose_images
220+
- name: Select image with proper multi-key sort (single task)
221+
set_fact:
222+
selected_image: >-
223+
{{
224+
(_distro_purpose_images.images
225+
| list
226+
| sort(attribute='created_at', reverse=true)
227+
| sort(attribute='name', reverse=true))[0]
228+
| first
229+
}}
230+
```
231+
232+
The fallback to name matching (for older clouds not yet complying to scs-0102-v2)
233+
can be done in ansible, but gets a bit complex. Find the ansible tasks in
234+
[find_img.yaml](find_img.yaml).
235+
Full disclosure: This has been produced with the help of Claude AI.

0 commit comments

Comments
 (0)