Skip to content

Commit f65f027

Browse files
committed
OK, make opentofu work with external helper.
This was more complicated than I hoped for. We could have done some HCL magic IF the openstack provider would have allowed to query image properties by IDs which it does not seem to. So the external python3 program is easiest and we reuse what we already have. Also comment on heat. Signed-off-by: Kurt Garloff <[email protected]>
1 parent 404ffc0 commit f65f027

File tree

5 files changed

+152
-12
lines changed

5 files changed

+152
-12
lines changed

user-docs/usage-hints/find-image/find_img.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#
77
# (c) Kurt Garloff <[email protected]>, 7/2025
88
# SPDX-License-Identifier: MIT
9-
"This module finds the a vanilla distribution image"
9+
"This module finds the a distribution image with a given purpose"
1010

1111
import os
1212
import sys

user-docs/usage-hints/find-image/find_img.tf

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env tofu apply -auto-approve
22
# Pass variables with -var os_purpose=minimal
3+
# (c) Kurt Garloff <[email protected]>
4+
# SPDX-License-Identifier: MIT
35

46
variable "os_distro" {
57
type = string
@@ -26,17 +28,20 @@ terraform {
2628
}
2729

2830
provider "openstack" {
29-
# Your cloud config (or use environment variables OS_CLOUD)
31+
# Your cloud config (or use environment variable OS_CLOUD)
3032
}
3133

3234
data "openstack_images_image_v2" "my_image" {
3335
most_recent = true
34-
36+
3537
properties = {
36-
os_distro = "${var.os_distro}"
37-
os_version = "${var.os_version}"
38-
os_purpose = "${var.os_purpose}"
38+
os_distro = "${var.os_distro}"
39+
os_version = "${var.os_version}"
40+
os_purpose = "${var.os_purpose}"
3941
}
42+
#sort = "name:desc,created_at:desc"
43+
#sort_key = "name"
44+
#sort_direction = "desc"
4045
}
4146

4247
# Output the results for inspection
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python3
2+
#
3+
# find_img2.py
4+
#
5+
# Find images for opentofu/terraform
6+
#
7+
# (c) Kurt Garloff <[email protected]>, 11/2025
8+
# SPDX-License-Identifier: MIT
9+
"This module finds the a distribution image with a given purpose"
10+
11+
import sys
12+
import json
13+
import find_img
14+
15+
try:
16+
in_data = json.loads(sys.stdin.read())
17+
conn = find_img.openstack.connect() # cloud=os.environ["OS_CLOUD"])
18+
images = find_img.find_image(conn, in_data["os_distro"], in_data["os_version"], in_data["os_purpose"])
19+
for img in images:
20+
print(f"DEBUG: {img[0]} {img[1]}", file=sys.stderr)
21+
if not (len(images)):
22+
print(f"No image found for {in_data}", file=sys.stderr)
23+
sys.exit(1)
24+
output = { "image_id": images[0][0],
25+
"image_name": images[0][1] }
26+
print(json.dumps(output))
27+
28+
except Exception as e:
29+
print(f"Error: {str(e)}", file=sys.stderr)
30+
sys.exit(2)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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_distro2" {
7+
type = string
8+
default = "ubuntu"
9+
}
10+
11+
variable "os_version2" {
12+
type = string
13+
default = "24.04"
14+
}
15+
16+
variable "os_purpose2" {
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 variables OS_CLOUD)
32+
#}
33+
34+
# Call python find_img.py to find best image
35+
data "external" "my_image2" {
36+
program = ["python3", "${path.module}/find_img2.py"]
37+
38+
query = {
39+
os_distro = "${var.os_distro2}"
40+
os_version = "${var.os_version2}"
41+
os_purpose = "${var.os_purpose2}"
42+
}
43+
}
44+
45+
# Output the results for inspection
46+
output "selected_image2" {
47+
value = {
48+
id = data.external.my_image2.result.image_id
49+
name = data.external.my_image2.result.image_name
50+
}
51+
}
52+

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

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,9 @@ data "openstack_images_image_v2" "my_image" {
125125
os_version = "24.04"
126126
os_purpose = "generic"
127127
}
128-
# sort_key = "name"
129-
# sort_direction = "desc"
128+
# sort = "name:desc,created_at:desc"
129+
# sort_key = "name"
130+
# sort_direction = "desc"
130131
}
131132
132133
# Use the selected image
@@ -138,11 +139,63 @@ resource "openstack_compute_instance_v2" "instance" {
138139

139140
This will find the most recent image wtih the `os_` variables set to `ubuntu`, `24.04`, `generic`.
140141
Note that unlike the python and shell examples, we can not easily sort for name and creation
141-
date at the same time; the name sorting is thus commented out here. To use name sorting you can
142-
enable it — using it and then selecting the latest if the name is identical requires some
143-
HCL magic using `locals` and `reverse(sort(...))` calls or calling an external program.
142+
date at the same time; the only option to deal with several matches is to tell opentofu's
143+
openstack provider to return the latest (the one with the newest `created_at` date).
144144

145145
An example can be found in [find_img.tf](find_img.tf). Call it with `tofu apply -auto-approve`
146146
(after you ran `tofu init` in this directory once).
147147

148-
The fallback to name matching is harder.
148+
The fallback to name matching for clouds that don't have `os_purpose` yet is harder.
149+
150+
We use an external program, the python script from before to select the right image and just create
151+
a little wrapper around it: [find_img2.py](find_img2.py). The HCL then looks like this:
152+
153+
```hcl
154+
# Call python find_img.py to find best image
155+
data "external" "my_image2" {
156+
program = ["python3", "${path.module}/find_img2.py"]
157+
158+
query = {
159+
os_distro = "${var.os_distro2}"
160+
os_version = "${var.os_version2}"
161+
os_purpose = "${var.os_purpose2}"
162+
}
163+
}
164+
165+
# Output the results for inspection
166+
output "selected_image2" {
167+
value = {
168+
id = data.external.my_image2.result.image_id
169+
name = data.external.my_image2.result.image_name
170+
}
171+
}
172+
```
173+
174+
Note that I have appended a `2` to the variable names, so they don't clash in case you have the
175+
original example in the same directory.
176+
177+
## heat
178+
179+
I did not find a good way to select an image based on its properties in heat.
180+
Obviously, you can use the python (or shell) script above and pass the image name
181+
or ID as a parameter when invoking heat.
182+
183+
```yaml
184+
heat_template_version: 2018-08-31
185+
186+
parameters:
187+
image:
188+
type: string
189+
description: Image ID or name
190+
constraints:
191+
- custom_constraint: glance.image
192+
193+
resources:
194+
my_instance:
195+
type: OS::Nova::Server
196+
properties:
197+
image: { get_param: image }
198+
# ... other properties
199+
```
200+
201+
and call `openstack stack create --parameter image=$ID $TEMPLATE $STACKNAME`.

0 commit comments

Comments
 (0)