Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions sidebarsUserDocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ const sidebars = {
]
}
]
},
{
type: 'category',
label: 'Portability Hints',
link: {
type: 'doc',
id: 'usage-hints/index'
},
items: ['usage-hints/find-image/index']
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions user-docs/usage-hints/find-image/.flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
ignore = E501
140 changes: 140 additions & 0 deletions user-docs/usage-hints/find-image/find_img.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
#
# find_img.py
#
# Searches for an image with distribution and version and purpose
#
# (c) Kurt Garloff <s7n@garloff.de>, 7/2025
# SPDX-License-Identifier: MIT
"This module finds the a distribution image with a given purpose"

import os
import sys
import openstack
# import logging


def warn(log, msg):
"warn output"
if log:
log.warn(msg)
else:
print(f"WARN: {msg}", file=sys.stderr)


def debug(log, msg):
"debug output"
if log:
log.debug(msg)
else:
print(f"DEBUG: {msg}", file=sys.stderr)


def img_sort_heuristic(images, distro, version, purpose):
"""Sort list to prefer old names"""
# Do sorting magic (could be omitted)
newlist = []
distro = distro.lower()
version = version.lower()
purpose = purpose.lower()
# 0th: Exact match old SCS naming scheme ("Ubuntu 24.04 Minimal")
for img in images:
newel = (img.id, img.name)
if img.name.lower() == f"{distro} {version} {purpose}":
newlist.append(newel)
elif img.name.lower() == f"{distro} {purpose} {version}":
newlist.append(newel)
# 1st: Exact match old SCS naming scheme ("Ubuntu 24.04")
for img in images:
newel = (img.id, img.name)
if img.name.lower() == f"{distro} {version}":
newlist.append(newel)
# 2nd: Fuzzy match old SCS naming scheme ("Ubuntu 24.04*")
for img in images:
newel = (img.id, img.name)
if img.name.lower().startswith(f"{distro} {version}") and newel not in newlist:
newlist.append(newel)
# 3rd: Even more fuzzy match old SCS naming scheme ("Ubuntu*24.04")
for img in images:
newel = (img.id, img.name)
if img.name.lower().startswith(f"{distro}") and img.name.lower().endswith(f"{version}") \
and newel not in newlist:
newlist.append(newel)
# 4th: Rest
for img in images:
newel = (img.id, img.name)
if newel not in newlist:
newlist.append(newel)
return newlist


def find_image(conn, distro, version, purpose="generic", strict=False, log=None):
"""Return a sorted list of ID,Name pairs that contain the wanted image.
Empty list indicates no image has been found. The list is sorted such
that (on SCS-compliant clouds), it will very likely contain the best
matching, most recent image as first element.
If strict is set, multiple matches are not allowed.
"""
ldistro = distro.lower()
# FIXME: The image.images() method only passes selected filters
purpose_out = purpose
images = [x for x in conn.image.images(os_distro=ldistro, os_version=version,
sort="name:desc,created_at:desc", visibility="public")
if x.properties.get("os_purpose") == purpose]
if len(images) == 0:
warn(log, f"No image found with os_distro={ldistro} os_version={version} os_purpose={purpose}")
purpose_out = ""
# images = list(conn.image.images(os_distro=ldistro, os_version=version,
# sort="name:desc,created_at:desc"))
images = [x for x in conn.image.images(os_distro=ldistro, os_version=version,
sort="name:desc,created_at:desc")
if "os_purpose" not in x.properties]
if len(images) == 0:
warn(log, f"No image found with os_distro={ldistro} os_version={version} without os_purpose")
return []
# Now comes sorting magic for best backwards compatibility
if len(images) > 1:
debug(log, f"Several {purpose_out} images found with os_distro={ldistro} os_version={version}")
if strict:
return []
return img_sort_heuristic(images, distro, version, purpose)
return [(img.id, img.name) for img in images]


def usage():
"Usage hints (CLI)"
print("Usage: find-img.py [-s] DISTRO VERSION [PURPOSE]", file=sys.stderr)
print("Returns all images matching, latest first, purpose defaulting to generic", file=sys.stderr)
print("[-s] sets strict mode where only one match is allowed.", file=sys.stderr)
print("You need to have OS_CLOUD set when running this", file=sys.stderr)
sys.exit(1)


def main(argv):
"Main entry for CLI"
if len(argv) < 3:
usage()
try:
conn = openstack.connect(cloud=os.environ["OS_CLOUD"])
except openstack.exceptions.ConfigException:
print(f"No valid entry for cloud {os.environ['OS_CLOUD']}", file=sys.stderr)
usage()
except KeyError:
print("OS_CLOUD environment not configured", file=sys.stderr)
usage()
conn.authorize()
purpose = "generic"
strict = False
if argv[1] == "-s":
argv = argv[1:]
strict = True
if len(argv) > 3:
purpose = argv[3]
images = find_image(conn, argv[1], argv[2], purpose, strict)
for img in images:
print(f"{img[0]} {img[1]}")
return len(images) == 0


if __name__ == "__main__":
sys.exit(main(sys.argv))
91 changes: 91 additions & 0 deletions user-docs/usage-hints/find-image/find_img.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/bin/bash
#
# Find Image by properties
#
# (c) Kurt Garloff <s7n@garloff.de>, 7/2025
# SPDX-License-Identifier: MIT

usage()
{
echo "Usage: find-img [-s] distro version [purpose]"
echo "Returns all images matching, latest first, purpose defaults to generic"
echo "If some images have the wanted purpose, only those will be shown"
}

get_images_raw()
{
# global OS_RESP
DIST=$(echo "$1" | tr A-Z a-z)
VERS="$2"
#VERS=$(echo "$2" | tr A-Z a-z)
shift; shift
#echo "DEBUG: openstack image list --property os_distro=$DIST --property os_version=$VERS $@ -f value -c ID -c Name --sort created_at:desc"
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)
}


img_sort_heuristic()
{
# Acts on global OS_RESP
# FIXME: We could do all sorts of advanced heuristics here, looking at the name etc.
# We only do a few pattern matches here
# distro version purpose
local NEW_RESP0=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $2 $3\$")
# distro version purpose with extras appended
local NEW_RESP1=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $2 $3" | grep -iv "^[0-9a-f\-]* $1 $2 $3\$")
# distro purpose version
local NEW_RESP2=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $3 $2\$")
# distro purpose version with extras appended
local NEW_RESP3=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $3 $2" | grep -iv "^[0-9a-f\-]* $1 $3 $2\$")
# distro version
local NEW_RESP4=$(echo "$OS_RESP" | grep -i "^[0-9a-f\-]* $1 $2\$")
# distro version with extras (but not purpose)
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")
# distro extra version (but extra != purpose)
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\$")
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')
}

get_images()
{
PURPOSE="${3:-generic}"
PURP="$PURPOSE"
get_images_raw "$1" "$2" --property os_purpose=$PURPOSE
if test -z "$OS_RESP"; then
echo "WARN: No image found with os_distro=$1 os_version=$2 os_purpose=$PURPOSE" 1>&2
PURP=""
# We're screwed as we can not filter for the absence of os_purpose with CLI
# We could loop and do an image show and then flter out, but that's very slow
get_images_raw "$1" "$2" # --property os_purpose=
# FIXME: We need to filter out images with os_purpose property set
NEW_RESP=""
while read ID Name; do
PROPS=$(openstack image show $ID -f value -c properties)
if test $? != 0; then continue; fi
if echo "$PROPS" | grep os_purpose >/dev/null 2>&1; then continue; fi
NEW_RESP=$(echo -en "$NEW_RESP\n$ID $Name")
done < <(echo "$OS_RESP")
OS_RESP=$(echo "$NEW_RESP" | sed '/^$/d')
fi
NR_IMG=$(echo "$OS_RESP" | sed '/^$/d' | wc -l)
if test "$NR_IMG" = "0"; then echo "ERROR: No image found with os_distro=$1 os_version=$2" 1>&2; return 1
elif test "$NR_IMG" = "1"; then return 0
else
echo "DEBUG: Several $PURP images matching os_distro=$1 os_version=$2" 1>&2;
if test -n "$STRICT"; then return 1; fi
img_sort_heuristic "$1" "$2" "$PURPOSE"
return 0
fi
}

if test -z "$OS_CLOUD" -a -z "$OS_AUTH_URL"; then
echo "You need to configure clouds.yaml/secure.yaml and set OS_CLOUD" 1>&2
exit 2
fi
if test "$1" = "-s"; then STRICT=1; shift; fi
if test -z "$1"; then usage; exit 1; fi

get_images "$@"
RC=$?
echo "$OS_RESP"
(exit $RC)
71 changes: 71 additions & 0 deletions user-docs/usage-hints/find-image/find_img.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env tofu apply -auto-approve
# Pass variables with -var os_purpose=minimal
# (c) Kurt Garloff <s7n@garloff.de>
# SPDX-License-Identifier: MIT

variable "os_distro" {
type = string
default = "ubuntu"
}

variable "os_version" {
type = string
default = "24.04"
}

variable "os_purpose" {
type = string
default = "generic"
}

terraform {
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 1.54.0"
}
}
}

provider "openstack" {
# Your cloud config (or use environment variable OS_CLOUD)
}

data "openstack_images_image_v2" "my_image" {
most_recent = true

properties = {
os_distro = "${var.os_distro}"
os_version = "${var.os_version}"
os_purpose = "${var.os_purpose}"
}
#sort = "name:desc,created_at:desc"
#sort_key = "name"
#sort_direction = "desc"
}

# Output the results for inspection
output "selected_image_id" {
value = data.openstack_images_image_v2.my_image.id
}

output "selected_image_name" {
value = data.openstack_images_image_v2.my_image.name
}

output "selected_image_created_at" {
value = data.openstack_images_image_v2.my_image.created_at
}

output "selected_image_properties" {
value = {
os_distro = data.openstack_images_image_v2.my_image.properties.os_distro
os_version = data.openstack_images_image_v2.my_image.properties.os_version
os_purpose = data.openstack_images_image_v2.my_image.properties.os_purpose
}
}

output "selected_image_tags" {
value = data.openstack_images_image_v2.my_image.tags
}

Loading