Skip to content

Commit e6fe005

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/npm_and_yarn-8f9a9acd57
2 parents 4d24377 + 0ffff94 commit e6fe005

File tree

12 files changed

+830
-0
lines changed

12 files changed

+830
-0
lines changed

sidebarsUserDocs.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ const sidebars = {
2828
]
2929
}
3030
]
31+
},
32+
{
33+
type: 'category',
34+
label: 'Portability Hints',
35+
link: {
36+
type: 'doc',
37+
id: 'usage-hints/index'
38+
},
39+
items: ['usage-hints/find-image/index']
3140
}
3241
]
3342
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
ignore = E501
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#!/usr/bin/env python3
2+
#
3+
# find_img.py
4+
#
5+
# Searches for an image with distribution and version and purpose
6+
#
7+
# (c) Kurt Garloff <[email protected]>, 7/2025
8+
# SPDX-License-Identifier: MIT
9+
"This module finds the a distribution image with a given purpose"
10+
11+
import os
12+
import sys
13+
import openstack
14+
# import logging
15+
16+
17+
def warn(log, msg):
18+
"warn output"
19+
if log:
20+
log.warn(msg)
21+
else:
22+
print(f"WARN: {msg}", file=sys.stderr)
23+
24+
25+
def debug(log, msg):
26+
"debug output"
27+
if log:
28+
log.debug(msg)
29+
else:
30+
print(f"DEBUG: {msg}", file=sys.stderr)
31+
32+
33+
def img_sort_heuristic(images, distro, version, purpose):
34+
"""Sort list to prefer old names"""
35+
# Do sorting magic (could be omitted)
36+
newlist = []
37+
distro = distro.lower()
38+
version = version.lower()
39+
purpose = purpose.lower()
40+
# 0th: Exact match old SCS naming scheme ("Ubuntu 24.04 Minimal")
41+
for img in images:
42+
newel = (img.id, img.name)
43+
if img.name.lower() == f"{distro} {version} {purpose}":
44+
newlist.append(newel)
45+
elif img.name.lower() == f"{distro} {purpose} {version}":
46+
newlist.append(newel)
47+
# 1st: Exact match old SCS naming scheme ("Ubuntu 24.04")
48+
for img in images:
49+
newel = (img.id, img.name)
50+
if img.name.lower() == f"{distro} {version}":
51+
newlist.append(newel)
52+
# 2nd: Fuzzy match old SCS naming scheme ("Ubuntu 24.04*")
53+
for img in images:
54+
newel = (img.id, img.name)
55+
if img.name.lower().startswith(f"{distro} {version}") and newel not in newlist:
56+
newlist.append(newel)
57+
# 3rd: Even more fuzzy match old SCS naming scheme ("Ubuntu*24.04")
58+
for img in images:
59+
newel = (img.id, img.name)
60+
if img.name.lower().startswith(f"{distro}") and img.name.lower().endswith(f"{version}") \
61+
and newel not in newlist:
62+
newlist.append(newel)
63+
# 4th: Rest
64+
for img in images:
65+
newel = (img.id, img.name)
66+
if newel not in newlist:
67+
newlist.append(newel)
68+
return newlist
69+
70+
71+
def find_image(conn, distro, version, purpose="generic", strict=False, log=None):
72+
"""Return a sorted list of ID,Name pairs that contain the wanted image.
73+
Empty list indicates no image has been found. The list is sorted such
74+
that (on SCS-compliant clouds), it will very likely contain the best
75+
matching, most recent image as first element.
76+
If strict is set, multiple matches are not allowed.
77+
"""
78+
ldistro = distro.lower()
79+
# FIXME: The image.images() method only passes selected filters
80+
purpose_out = purpose
81+
images = [x for x in conn.image.images(os_distro=ldistro, os_version=version,
82+
sort="name:desc,created_at:desc", visibility="public")
83+
if x.properties.get("os_purpose") == purpose]
84+
if len(images) == 0:
85+
warn(log, f"No image found with os_distro={ldistro} os_version={version} os_purpose={purpose}")
86+
purpose_out = ""
87+
# images = list(conn.image.images(os_distro=ldistro, os_version=version,
88+
# sort="name:desc,created_at:desc"))
89+
images = [x for x in conn.image.images(os_distro=ldistro, os_version=version,
90+
sort="name:desc,created_at:desc")
91+
if "os_purpose" not in x.properties]
92+
if len(images) == 0:
93+
warn(log, f"No image found with os_distro={ldistro} os_version={version} without os_purpose")
94+
return []
95+
# Now comes sorting magic for best backwards compatibility
96+
if len(images) > 1:
97+
debug(log, f"Several {purpose_out} images found with os_distro={ldistro} os_version={version}")
98+
if strict:
99+
return []
100+
return img_sort_heuristic(images, distro, version, purpose)
101+
return [(img.id, img.name) for img in images]
102+
103+
104+
def usage():
105+
"Usage hints (CLI)"
106+
print("Usage: find-img.py [-s] DISTRO VERSION [PURPOSE]", file=sys.stderr)
107+
print("Returns all images matching, latest first, purpose defaulting to generic", file=sys.stderr)
108+
print("[-s] sets strict mode where only one match is allowed.", file=sys.stderr)
109+
print("You need to have OS_CLOUD set when running this", file=sys.stderr)
110+
sys.exit(1)
111+
112+
113+
def main(argv):
114+
"Main entry for CLI"
115+
if len(argv) < 3:
116+
usage()
117+
try:
118+
conn = openstack.connect(cloud=os.environ["OS_CLOUD"])
119+
except openstack.exceptions.ConfigException:
120+
print(f"No valid entry for cloud {os.environ['OS_CLOUD']}", file=sys.stderr)
121+
usage()
122+
except KeyError:
123+
print("OS_CLOUD environment not configured", file=sys.stderr)
124+
usage()
125+
conn.authorize()
126+
purpose = "generic"
127+
strict = False
128+
if argv[1] == "-s":
129+
argv = argv[1:]
130+
strict = True
131+
if len(argv) > 3:
132+
purpose = argv[3]
133+
images = find_image(conn, argv[1], argv[2], purpose, strict)
134+
for img in images:
135+
print(f"{img[0]} {img[1]}")
136+
return len(images) == 0
137+
138+
139+
if __name__ == "__main__":
140+
sys.exit(main(sys.argv))
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/bin/bash
2+
#
3+
# Find Image by properties
4+
#
5+
# (c) Kurt Garloff <[email protected]>, 7/2025
6+
# SPDX-License-Identifier: MIT
7+
8+
usage()
9+
{
10+
echo "Usage: find-img [-s] distro version [purpose]"
11+
echo "Returns all images matching, latest first, purpose defaults to generic"
12+
echo "If some images have the wanted purpose, only those will be shown"
13+
}
14+
15+
get_images_raw()
16+
{
17+
# global OS_RESP
18+
DIST=$(echo "$1" | tr A-Z a-z)
19+
VERS="$2"
20+
#VERS=$(echo "$2" | tr A-Z a-z)
21+
shift; shift
22+
#echo "DEBUG: openstack image list --property os_distro=$DIST --property os_version=$VERS $@ -f value -c ID -c Name --sort created_at:desc"
23+
OS_RESP=$(openstack image list --property os_distro="$DIST" --property os_version="$VERS" $@ -f value -c ID -c Name --sort name:desc,created_at:desc)
24+
}
25+
26+
27+
img_sort_heuristic()
28+
{
29+
# Acts on global OS_RESP
30+
# FIXME: We could do all sorts of advanced heuristics here, looking at the name etc.
31+
# We only do a few pattern matches here
32+
# distro version purpose
33+
local NEW_RESP0=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $2 $3\$")
34+
# distro version purpose with extras appended
35+
local NEW_RESP1=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $2 $3" | grep -iv "^[0-9a-f\-]* $1 $2 $3\$")
36+
# distro purpose version
37+
local NEW_RESP2=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $3 $2\$")
38+
# distro purpose version with extras appended
39+
local NEW_RESP3=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $3 $2" | grep -iv "^[0-9a-f\-]* $1 $3 $2\$")
40+
# distro version
41+
local NEW_RESP4=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $2\$")
42+
# distro version with extras (but not purpose)
43+
local NEW_RESP5=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $2" | grep -iv "^[0-9a-f\-]* $1 $2\$" | grep -iv "^[0-9a-f\-]* $1 $2 $3")
44+
# distro extra version (but extra != purpose)
45+
local NEW_RESP6=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 .*$2\$" | grep -iv "^[0-9a-f\-]* $1 $3 $2\$" | grep -iv "$1 $2\$")
46+
OS_RESP=$(echo -e "$NEW_RESP0\n$NEW_RESP1\n$NEW_RESP2\n$NEW_RESP3\n$NEW_RESP4\n$NEW_RESP5\n$NEW_RESP6" | sed '/^$/d')
47+
}
48+
49+
get_images()
50+
{
51+
PURPOSE="${3:-generic}"
52+
PURP="$PURPOSE"
53+
get_images_raw "$1" "$2" --property os_purpose=$PURPOSE
54+
if test -z "$OS_RESP"; then
55+
echo "WARN: No image found with os_distro=$1 os_version=$2 os_purpose=$PURPOSE" 1>&2
56+
PURP=""
57+
# We're screwed as we can not filter for the absence of os_purpose with CLI
58+
# We could loop and do an image show and then flter out, but that's very slow
59+
get_images_raw "$1" "$2" # --property os_purpose=
60+
# FIXME: We need to filter out images with os_purpose property set
61+
NEW_RESP=""
62+
while read ID Name; do
63+
PROPS=$(openstack image show $ID -f value -c properties)
64+
if test $? != 0; then continue; fi
65+
if echo "$PROPS" | grep os_purpose >/dev/null 2>&1; then continue; fi
66+
NEW_RESP=$(echo -en "$NEW_RESP\n$ID $Name")
67+
done < <(echo "$OS_RESP")
68+
OS_RESP=$(echo "$NEW_RESP" | sed '/^$/d')
69+
fi
70+
NR_IMG=$(echo "$OS_RESP" | sed '/^$/d' | wc -l)
71+
if test "$NR_IMG" = "0"; then echo "ERROR: No image found with os_distro=$1 os_version=$2" 1>&2; return 1
72+
elif test "$NR_IMG" = "1"; then return 0
73+
else
74+
echo "DEBUG: Several $PURP images matching os_distro=$1 os_version=$2" 1>&2;
75+
if test -n "$STRICT"; then return 1; fi
76+
img_sort_heuristic "$1" "$2" "$PURPOSE"
77+
return 0
78+
fi
79+
}
80+
81+
if test -z "$OS_CLOUD" -a -z "$OS_AUTH_URL"; then
82+
echo "You need to configure clouds.yaml/secure.yaml and set OS_CLOUD" 1>&2
83+
exit 2
84+
fi
85+
if test "$1" = "-s"; then STRICT=1; shift; fi
86+
if test -z "$1"; then usage; exit 1; fi
87+
88+
get_images "$@"
89+
RC=$?
90+
echo "$OS_RESP"
91+
(exit $RC)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env tofu apply -auto-approve
2+
# Pass variables with -var os_purpose=minimal
3+
# (c) Kurt Garloff <[email protected]>
4+
# SPDX-License-Identifier: MIT
5+
6+
variable "os_distro" {
7+
type = string
8+
default = "ubuntu"
9+
}
10+
11+
variable "os_version" {
12+
type = string
13+
default = "24.04"
14+
}
15+
16+
variable "os_purpose" {
17+
type = string
18+
default = "generic"
19+
}
20+
21+
terraform {
22+
required_providers {
23+
openstack = {
24+
source = "terraform-provider-openstack/openstack"
25+
version = "~> 1.54.0"
26+
}
27+
}
28+
}
29+
30+
provider "openstack" {
31+
# Your cloud config (or use environment variable OS_CLOUD)
32+
}
33+
34+
data "openstack_images_image_v2" "my_image" {
35+
most_recent = true
36+
37+
properties = {
38+
os_distro = "${var.os_distro}"
39+
os_version = "${var.os_version}"
40+
os_purpose = "${var.os_purpose}"
41+
}
42+
#sort = "name:desc,created_at:desc"
43+
#sort_key = "name"
44+
#sort_direction = "desc"
45+
}
46+
47+
# Output the results for inspection
48+
output "selected_image_id" {
49+
value = data.openstack_images_image_v2.my_image.id
50+
}
51+
52+
output "selected_image_name" {
53+
value = data.openstack_images_image_v2.my_image.name
54+
}
55+
56+
output "selected_image_created_at" {
57+
value = data.openstack_images_image_v2.my_image.created_at
58+
}
59+
60+
output "selected_image_properties" {
61+
value = {
62+
os_distro = data.openstack_images_image_v2.my_image.properties.os_distro
63+
os_version = data.openstack_images_image_v2.my_image.properties.os_version
64+
os_purpose = data.openstack_images_image_v2.my_image.properties.os_purpose
65+
}
66+
}
67+
68+
output "selected_image_tags" {
69+
value = data.openstack_images_image_v2.my_image.tags
70+
}
71+

0 commit comments

Comments
 (0)