openstack-image-manager 0.20240403.0__py3-none-any.whl → 0.20240411.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openstack-image-manager
3
- Version: 0.20240403.0
3
+ Version: 0.20240411.0
4
4
  Summary: OpenStack image manager
5
5
  Author-email: OSISM community <info@osism.tech>
6
6
  License: Apache License
@@ -232,7 +232,7 @@ Requires-Dist: patool ==2.2.0
232
232
  Requires-Dist: requests ==2.31.0
233
233
  Requires-Dist: ruamel.yaml ==0.18.6
234
234
  Requires-Dist: tabulate ==0.9.0
235
- Requires-Dist: typer[all] ==0.12.0
235
+ Requires-Dist: typer[all] ==0.12.3
236
236
  Requires-Dist: yamale ==5.1.0
237
237
 
238
238
  # openstack-image-manager
@@ -0,0 +1,8 @@
1
+ openstack_image_manager/__init__.py,sha256=z6lQHDMfCV8IkUz5pM1QYfQ37O2Rdy82jYovN8N9DIU,240
2
+ openstack_image_manager/main.py,sha256=gh8Cf5XAKTOCoKL8jMe-Q6V_d3pQryLc2b457OQLZPI,46764
3
+ openstack_image_manager-0.20240411.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
4
+ openstack_image_manager-0.20240411.0.dist-info/METADATA,sha256=UkQZX6SI8bbZtJS5LfGOQzCee1DufFyR4sNzGRR5vtY,14871
5
+ openstack_image_manager-0.20240411.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ openstack_image_manager-0.20240411.0.dist-info/entry_points.txt,sha256=IrOGjHJbCa6-jmCQiqiKFqHeVvjGa1p25s2mLkt0da8,78
7
+ openstack_image_manager-0.20240411.0.dist-info/top_level.txt,sha256=iLfREddId51T97Dr9IGRQtJXKJgVy1PB6uHCaQk1j44,24
8
+ openstack_image_manager-0.20240411.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ openstack-image-manager = openstack_image_manager.main:main
@@ -1,144 +0,0 @@
1
- # SPDX-License-Identifier: Apache-2.0
2
-
3
- import os
4
- import patoolib
5
- import requests
6
- import shutil
7
- import sys
8
- import typer
9
- import yaml
10
-
11
- from loguru import logger
12
- from minio import Minio
13
- from minio.error import S3Error
14
- from os import listdir
15
- from os.path import isfile, join
16
- from urllib.parse import urlparse
17
-
18
-
19
- app = typer.Typer(add_completion=False)
20
-
21
-
22
- @app.command()
23
- def main(
24
- debug: bool = typer.Option(False, "--debug", help="Enable debug logging"),
25
- dry_run: bool = typer.Option(False, "--dry-run", help="Do not perform any changes"),
26
- images: str = typer.Option(
27
- "etc/images/", help="Path to the directory containing all image files"
28
- ),
29
- minio_access_key: str = typer.Option(
30
- None, help="Minio access key", envvar="MINIO_ACCESS_KEY"
31
- ),
32
- minio_secret_key: str = typer.Option(
33
- None, help="Minio secret key", envvar="MINIO_SECRET_KEY"
34
- ),
35
- minio_server: str = typer.Option(
36
- "swift.services.a.regiocloud.tech", help="Minio server"
37
- ),
38
- minio_bucket: str = typer.Option("openstack-images", help="Minio bucket"),
39
- ):
40
- if debug:
41
- level = "DEBUG"
42
- else:
43
- level = "INFO"
44
-
45
- log_fmt = (
46
- "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | "
47
- "<level>{message}</level>"
48
- )
49
-
50
- logger.remove()
51
- logger.add(sys.stderr, format=log_fmt, level=level, colorize=True)
52
-
53
- client = Minio(
54
- minio_server,
55
- access_key=minio_access_key,
56
- secret_key=minio_secret_key,
57
- )
58
-
59
- result = client.bucket_exists(minio_bucket)
60
- if not result:
61
- logger.error(f"Create bucket '{minio_bucket}' first")
62
- if not dry_run:
63
- sys.exit(1)
64
-
65
- onlyfiles = []
66
- for f in listdir(images):
67
- if isfile(join(images, f)):
68
- logger.debug(f"Adding {f} to the list of files")
69
- onlyfiles.append(f)
70
-
71
- all_images = []
72
- for file in [x for x in onlyfiles if x.endswith(".yml")]:
73
- logger.info(f"Processing file {file}")
74
- with open(join(images, file)) as fp:
75
- data = yaml.load(fp, Loader=yaml.SafeLoader)
76
- for image in data.get("images"):
77
- logger.debug(f"Adding {image['name']} to the list of images")
78
- all_images.append(image)
79
-
80
- for image in all_images:
81
- logger.info(f"Processing image {image['name']}")
82
-
83
- if "versions" not in image:
84
- continue
85
-
86
- for version in image["versions"]:
87
- if "source" not in version:
88
- continue
89
- else:
90
- source = version["source"]
91
-
92
- logger.debug(f"source: {source}")
93
-
94
- path = urlparse(source)
95
- url = urlparse(version["url"])
96
-
97
- dirname = f"{image['shortname']}/{version['version']}"
98
- filename, fileextension = os.path.splitext(os.path.basename(path.path))
99
- _, fileextension2 = os.path.splitext(filename)
100
-
101
- if fileextension not in [".bz2", ".zip", ".xz", ".gz"]:
102
- filename += fileextension
103
-
104
- if fileextension2 == ".tar":
105
- filename = os.path.basename(url.path)
106
-
107
- logger.debug(f"dirname: {dirname}")
108
- logger.debug(f"filename: {filename}")
109
-
110
- try:
111
- client.stat_object(minio_bucket, os.path.join(dirname, filename))
112
- logger.info(f"File {filename} available in bucket {dirname}")
113
- except S3Error:
114
- logger.info(f"File {filename} not yet available in bucket {dirname}")
115
-
116
- if not isfile(os.path.basename(path.path)):
117
- logger.info(f"Downloading {version['source']}")
118
- response = requests.get(
119
- version["source"], stream=True, allow_redirects=True
120
- )
121
- with open(os.path.basename(path.path), "wb") as fp:
122
- shutil.copyfileobj(response.raw, fp)
123
- del response
124
-
125
- if fileextension in [".bz2", ".zip", ".xz", ".gz"]:
126
- logger.info(f"Decompressing {os.path.basename(path.path)}")
127
- patoolib.extract_archive(os.path.basename(path.path), outdir=".")
128
- os.remove(os.path.basename(path.path))
129
-
130
- if not dry_run:
131
- logger.info(f"Uploading {filename} to bucket {dirname}")
132
- client.fput_object(
133
- minio_bucket, os.path.join(dirname, filename), filename
134
- )
135
- else:
136
- logger.info(
137
- f"Not uploading {filename} to bucket {dirname} (dry-run enabled)"
138
- )
139
-
140
- os.remove(filename)
141
-
142
-
143
- if __name__ == "__main__":
144
- app()
@@ -1,47 +0,0 @@
1
- # SPDX-License-Identifier: Apache-2.0
2
-
3
- import tabulate
4
- import typer
5
- import yaml
6
-
7
- from munch import Munch
8
- from os import listdir
9
- from os.path import isfile, join
10
-
11
-
12
- app = typer.Typer(add_completion=False)
13
-
14
-
15
- @app.command()
16
- def main(
17
- images: str = typer.Option(
18
- "etc/images/", help="Path to the directory containing all image files"
19
- )
20
- ):
21
- CONF = Munch.fromDict(locals())
22
-
23
- onlyfiles = []
24
- for f in listdir(CONF.images):
25
- if isfile(join(CONF.images, f)):
26
- onlyfiles.append(f)
27
-
28
- all_images = []
29
- for file in onlyfiles:
30
- with open(join(CONF.images, file)) as fp:
31
- data = yaml.load(fp, Loader=yaml.SafeLoader)
32
- imgs = data.get("images")
33
- for image in imgs:
34
- all_images.append(image)
35
-
36
- data = []
37
- for image in all_images:
38
- data.append([image["name"], image["login"], image.get("password", "")])
39
-
40
- result = tabulate.tabulate(
41
- sorted(data), headers=["Name", "Login user", "Password"], tablefmt="rst"
42
- )
43
- print(result)
44
-
45
-
46
- if __name__ == "__main__":
47
- app()
@@ -1,341 +0,0 @@
1
- # SPDX-License-Identifier: Apache-2.0
2
-
3
- # source of latest URLs: https://gitlab.com/libosinfo/osinfo-db
4
-
5
- from datetime import datetime
6
- import os
7
- import re
8
- import shutil
9
- import sys
10
- from urllib.parse import urlparse
11
- from urllib.request import urlopen
12
-
13
- from loguru import logger
14
- from minio import Minio
15
- from minio.error import S3Error
16
- from natsort import natsorted
17
- import patoolib
18
- import requests
19
- import ruamel.yaml
20
- import typer
21
-
22
- app = typer.Typer()
23
- DEBUBU_REGEX = r'<a href="([^"]+)/">(?:release-)?([0-9]+)(\-[0-9]+)?/</a>'
24
-
25
-
26
- def get_latest_default(
27
- shortname, latest_checksum_url, latest_url, checksum_type="sha256"
28
- ):
29
- result = requests.get(latest_checksum_url)
30
- result.raise_for_status()
31
-
32
- latest_filename = os.path.basename(urlparse(latest_url).path)
33
- filename_pattern = None
34
- if shortname in ["centos-stream-8", "centos-stream-9", "centos-7"]:
35
- filename_pattern = latest_filename.replace("HEREBE", "")
36
- filename_pattern = filename_pattern.replace("DRAGONS", "")
37
-
38
- checksums = {}
39
- for line in result.text.split("\n"):
40
- cs = re.split(r"\s+", line)
41
- if shortname in ["rocky-8", "rocky-9"]:
42
- if len(cs) == 4 and cs[0] == "SHA256":
43
- checksums[latest_filename] = cs[3]
44
- elif shortname in ["centos-7"]:
45
- if len(cs) == 2 and re.search(filename_pattern, cs[1]):
46
- checksums[cs[1]] = cs[0]
47
- elif shortname in ["centos-stream-8", "centos-stream-9"]:
48
- if (
49
- len(cs) == 4
50
- and cs[0] == "SHA256"
51
- and re.search(filename_pattern, cs[1][1:-1])
52
- ):
53
- checksums[cs[1][1:-1]] = cs[3]
54
- else:
55
- if len(cs) == 2:
56
- checksums[cs[1]] = cs[0]
57
-
58
- if filename_pattern:
59
- new_latest_filename = natsorted(checksums.keys())[-1]
60
- new_latest_url = latest_url.replace(latest_filename, new_latest_filename)
61
-
62
- logger.info(f"Latest URL is now {new_latest_url}")
63
- logger.info(f"Latest filename is now {new_latest_filename}")
64
-
65
- latest_filename = new_latest_filename
66
- latest_url = new_latest_url
67
-
68
- current_checksum = f"{checksum_type}:{checksums[latest_filename]}"
69
- return current_checksum, latest_url, None
70
-
71
-
72
- def resolve_debubu(base_url, rex=re.compile(DEBUBU_REGEX)):
73
- result = requests.get(base_url)
74
- result.raise_for_status()
75
- latest_folder, latest_date, latest_build = sorted(rex.findall(result.text))[-1]
76
- return latest_folder, latest_date, latest_build
77
-
78
-
79
- def get_latest_debubu(shortname, latest_checksum_url, latest_url, checksum_type=None):
80
- base_url, _, filename = latest_url.rsplit("/", 2)
81
- latest_folder, latest_date, latest_build = resolve_debubu(base_url)
82
- current_base_url = f"{base_url}/{latest_folder}"
83
- current_checksum_url = (
84
- f"{current_base_url}/{latest_checksum_url.rsplit('/', 1)[-1]}"
85
- )
86
- result = requests.get(current_checksum_url)
87
- result.raise_for_status()
88
- current_checksum = None
89
- current_filename = filename
90
- if latest_build: # Debian includes date-build in file name
91
- fn_pre, fn_suf = filename.rsplit(".", 1)
92
- current_filename = f"{fn_pre}-{latest_date}{latest_build}.{fn_suf}"
93
- for line in result.text.splitlines():
94
- cs = line.split()
95
- if len(cs) != 2:
96
- continue
97
- if cs[1].startswith("*"): # Ubuntu has the asterisk in front of the name
98
- cs[1] = cs[1][1:]
99
- if cs[1] != current_filename:
100
- continue
101
- if checksum_type is None: # use heuristics to distinguish sha256/sha512
102
- checksum_type = "sha256" if len(cs[0]) == 64 else "sha512"
103
- current_checksum = f"{checksum_type}:{cs[0]}"
104
- break
105
- if current_checksum is None:
106
- raise RuntimeError(
107
- f"{current_checksum_url} does not contain {current_filename}"
108
- )
109
- current_url = f"{current_base_url}/{current_filename}"
110
- return current_checksum, current_url, latest_date
111
-
112
-
113
- IMAGES = {
114
- "almalinux": get_latest_default,
115
- "centos": get_latest_default,
116
- "debian": get_latest_debubu,
117
- "rockylinux": get_latest_default,
118
- "ubuntu": get_latest_debubu,
119
- }
120
-
121
-
122
- def mirror_image(
123
- image, latest_url, minio_server, minio_bucket, minio_access_key, minio_secret_key
124
- ):
125
- client = Minio(
126
- minio_server,
127
- access_key=minio_access_key,
128
- secret_key=minio_secret_key,
129
- )
130
-
131
- result = client.bucket_exists(minio_bucket)
132
- if not result:
133
- logger.error(f"Create bucket '{minio_bucket}' first")
134
- return
135
-
136
- version = image["versions"][0]
137
-
138
- path = urlparse(version["url"])
139
- dirname = image["shortname"]
140
- filename, fileextension = os.path.splitext(os.path.basename(path.path))
141
-
142
- if fileextension not in [".bz2", ".zip", ".xz", ".gz"]:
143
- filename += fileextension
144
-
145
- shortname = image["shortname"]
146
- format = image["format"]
147
- new_version = version["version"]
148
- new_filename = f"{new_version}-{shortname}.{format}"
149
-
150
- try:
151
- client.stat_object(minio_bucket, os.path.join(dirname, new_filename))
152
- logger.info(f"'{new_filename}' available in '{dirname}'")
153
- except S3Error:
154
- logger.info(f"'{new_filename}' not yet available in '{dirname}'")
155
- logger.info(f"Downloading '{latest_url}'")
156
-
157
- response = requests.get(latest_url, stream=True)
158
- with open(os.path.basename(path.path), "wb") as fp:
159
- shutil.copyfileobj(response.raw, fp)
160
- del response
161
-
162
- if fileextension in [".bz2", ".zip", ".xz", ".gz"]:
163
- logger.info(f"Decompressing '{os.path.basename(path.path)}'")
164
- patoolib.extract_archive(os.path.basename(path.path), outdir=".")
165
- os.remove(os.path.basename(path.path))
166
-
167
- logger.info(f"Uploading '{filename}' to '{dirname}' as '{new_filename}'")
168
-
169
- client.fput_object(minio_bucket, os.path.join(dirname, new_filename), filename)
170
- os.remove(filename)
171
-
172
-
173
- def update_image(
174
- image,
175
- getter,
176
- minio_server,
177
- minio_bucket,
178
- minio_access_key,
179
- minio_secret_key,
180
- dry_run=False,
181
- swift_prefix="",
182
- ):
183
- name = image["name"]
184
- logger.info(f"Checking image {name}")
185
-
186
- latest_url = image["latest_url"]
187
- logger.info(f"Latest download URL is {latest_url}")
188
-
189
- latest_checksum_url = image["latest_checksum_url"]
190
- logger.info(f"Getting checksums from {latest_checksum_url}")
191
-
192
- shortname = image["shortname"]
193
- current_checksum, current_url, current_version = getter(
194
- shortname, latest_checksum_url, latest_url
195
- )
196
-
197
- logger.info(
198
- f"Checksum of current {current_url.rsplit('/', 1)[-1]} is {current_checksum}"
199
- )
200
-
201
- if not image["versions"]:
202
- logger.info("No image available so far")
203
- image["versions"].append(
204
- {
205
- "build_date": None,
206
- "checksum": None,
207
- "url": None,
208
- "version": None,
209
- }
210
- )
211
-
212
- latest_checksum = image["versions"][0]["checksum"]
213
- logger.info(f"Our checksum is {latest_checksum}")
214
-
215
- if latest_checksum == current_checksum:
216
- logger.info(f"Image {name} is up-to-date, nothing to do")
217
- return 0
218
-
219
- if current_version is None:
220
- logger.info(f"Checking {current_url}")
221
-
222
- conn = urlopen(current_url, timeout=30)
223
- dt = datetime.strptime(
224
- conn.headers["last-modified"], "%a, %d %b %Y %H:%M:%S %Z"
225
- )
226
- current_version = dt.strftime("%Y%m%d")
227
-
228
- new_values = {
229
- "version": current_version,
230
- "build_date": datetime.strptime(current_version, "%Y%m%d").date(),
231
- "checksum": current_checksum,
232
- "url": current_url,
233
- }
234
- logger.info(f"New values are {new_values}")
235
- image["versions"][0].update(new_values)
236
-
237
- shortname = image["shortname"]
238
- format = image["format"]
239
-
240
- minio_server = str(minio_server)
241
- minio_bucket = str(minio_bucket)
242
- mirror_url = f"https://{minio_server}/{swift_prefix}{minio_bucket}/{shortname}/{current_version}-{shortname}.{format}" # noqa E501
243
- logger.info(f"New URL is {mirror_url}")
244
-
245
- # If `mirror_url` is given, the manage.py script will
246
- # use `mirror_url` for the download and will use `url`
247
- # to set the `image_source` property. This way we keep
248
- # track of the original source of the image.
249
-
250
- image["versions"][0]["mirror_url"] = mirror_url
251
-
252
- # We use `current_url` here and not `latest_url` to keep track
253
- # of the original source of the image. Even if we know that `current_url`
254
- # will not be available in the future. The `latest_url` will always
255
- # be part of the image definition itself.
256
-
257
- image["versions"][0]["url"] = current_url
258
-
259
- if dry_run:
260
- logger.info(f"Not mirroring {mirror_url}, dry-run enabled")
261
- else:
262
- mirror_image(
263
- image,
264
- current_url,
265
- minio_server,
266
- minio_bucket,
267
- minio_access_key,
268
- minio_secret_key,
269
- )
270
- return 1
271
-
272
-
273
- @app.command()
274
- def main(
275
- debug: bool = typer.Option(False, "--debug", help="Enable debug logging"),
276
- dry_run: bool = typer.Option(False, "--dry-run", help="Do not perform any changes"),
277
- minio_access_key: str = typer.Option(
278
- None, help="Minio access key", envvar="MINIO_ACCESS_KEY"
279
- ),
280
- minio_secret_key: str = typer.Option(
281
- None, help="Minio secret key", envvar="MINIO_SECRET_KEY"
282
- ),
283
- minio_server: str = typer.Option(
284
- "swift.services.a.regiocloud.tech", help="Minio server", envvar="MINIO_SERVER"
285
- ),
286
- minio_bucket: str = typer.Option(
287
- "openstack-images", help="Minio bucket", envvar="MINIO_BUCKET"
288
- ),
289
- swift_prefix: str = typer.Option(
290
- "swift/v1/AUTH_b182637428444b9aa302bb8d5a5a418c/",
291
- help="Swift prefix",
292
- envvar="SWIFT_PREFIX",
293
- ),
294
- ):
295
- if debug:
296
- level = "DEBUG"
297
- else:
298
- level = "INFO"
299
-
300
- logger.remove() # remove the default sink
301
- log_fmt = (
302
- "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | "
303
- "<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"
304
- )
305
- logger.add(sys.stderr, format=log_fmt, level=level, colorize=True)
306
-
307
- for image, getter in IMAGES.items():
308
- p = f"etc/images/{image}.yml"
309
- logger.info(f"Processing file {p}")
310
-
311
- ryaml = ruamel.yaml.YAML()
312
- with open(p) as fp:
313
- data = ryaml.load(fp)
314
-
315
- updates = 0
316
- for index, image in enumerate(data["images"]):
317
- if "latest_url" not in image:
318
- continue
319
-
320
- updates += update_image(
321
- image,
322
- getter,
323
- minio_server,
324
- minio_bucket,
325
- minio_access_key,
326
- minio_secret_key,
327
- dry_run,
328
- swift_prefix,
329
- )
330
-
331
- if not updates:
332
- continue
333
-
334
- with open(p, "w+") as fp:
335
- ryaml.explicit_start = True
336
- ryaml.indent(sequence=4, offset=2)
337
- ryaml.dump(data, fp)
338
-
339
-
340
- if __name__ == "__main__":
341
- app()
@@ -1,11 +0,0 @@
1
- openstack_image_manager/__init__.py,sha256=z6lQHDMfCV8IkUz5pM1QYfQ37O2Rdy82jYovN8N9DIU,240
2
- openstack_image_manager/manage.py,sha256=gh8Cf5XAKTOCoKL8jMe-Q6V_d3pQryLc2b457OQLZPI,46764
3
- openstack_image_manager/mirror.py,sha256=_84-vAFfF1a3IuDBzluwTJWo20cyJEJaNIcxn3X87QI,4737
4
- openstack_image_manager/table.py,sha256=cZ6Xuqp8uh2V5_uzT4KwWoiLAUdvsPZkrPjOXdxFeU4,1080
5
- openstack_image_manager/update.py,sha256=MFx7wZ1kEQb4WXYfvdjgSCeyXflkJqbBBx-4z8pQQ4Y,11057
6
- openstack_image_manager-0.20240403.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
7
- openstack_image_manager-0.20240403.0.dist-info/METADATA,sha256=yydaIZqhbmwnh_HU0b6LRrouNba065I5W-1PueTV2NU,14871
8
- openstack_image_manager-0.20240403.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
9
- openstack_image_manager-0.20240403.0.dist-info/entry_points.txt,sha256=AEHPHHHZ3jAZfpvaI5ZzLi3DHb9vGQwL7TJcw_G_5nc,80
10
- openstack_image_manager-0.20240403.0.dist-info/top_level.txt,sha256=iLfREddId51T97Dr9IGRQtJXKJgVy1PB6uHCaQk1j44,24
11
- openstack_image_manager-0.20240403.0.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- openstack-image-manager = openstack_image_manager.manage:main
File without changes