openstack-image-manager 0.20240318.0__py3-none-any.whl → 0.20240403.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.
- openstack_image_manager/manage.py +89 -120
- openstack_image_manager/update.py +23 -12
- {openstack_image_manager-0.20240318.0.dist-info → openstack_image_manager-0.20240403.0.dist-info}/METADATA +4 -4
- openstack_image_manager-0.20240403.0.dist-info/RECORD +11 -0
- openstack_image_manager-0.20240318.0.dist-info/RECORD +0 -11
- {openstack_image_manager-0.20240318.0.dist-info → openstack_image_manager-0.20240403.0.dist-info}/LICENSE +0 -0
- {openstack_image_manager-0.20240318.0.dist-info → openstack_image_manager-0.20240403.0.dist-info}/WHEEL +0 -0
- {openstack_image_manager-0.20240318.0.dist-info → openstack_image_manager-0.20240403.0.dist-info}/entry_points.txt +0 -0
- {openstack_image_manager-0.20240318.0.dist-info → openstack_image_manager-0.20240403.0.dist-info}/top_level.txt +0 -0
@@ -8,6 +8,7 @@ import os
|
|
8
8
|
import re
|
9
9
|
import sys
|
10
10
|
import typer
|
11
|
+
import typing
|
11
12
|
from typing import Dict, Set
|
12
13
|
import yamale
|
13
14
|
import urllib.parse
|
@@ -202,12 +203,12 @@ class ImageManager:
|
|
202
203
|
Read all files in etc/images/ and process each image
|
203
204
|
Rename outdated images when not dry-running
|
204
205
|
"""
|
205
|
-
logger.debug("cloud =
|
206
|
-
logger.debug("dry-run =
|
207
|
-
logger.debug("images =
|
208
|
-
logger.debug("tag =
|
206
|
+
logger.debug(f"cloud = {self.CONF.cloud}")
|
207
|
+
logger.debug(f"dry-run = {self.CONF.dry_run}")
|
208
|
+
logger.debug(f"images = {self.CONF.images}")
|
209
|
+
logger.debug(f"tag = {self.CONF.tag}")
|
209
210
|
logger.debug(
|
210
|
-
"yes-i-really-know-what-i-do =
|
211
|
+
f"yes-i-really-know-what-i-do = {self.CONF.yes_i_really_know_what_i_do}"
|
211
212
|
)
|
212
213
|
|
213
214
|
# check local image definitions with yamale
|
@@ -280,13 +281,12 @@ class ImageManager:
|
|
280
281
|
for required_key in REQUIRED_KEYS:
|
281
282
|
if required_key not in image:
|
282
283
|
logger.error(
|
283
|
-
"'
|
284
|
-
% (image["name"], required_key)
|
284
|
+
f"'{image['name']}' lacks the necessary key {required_key}"
|
285
285
|
)
|
286
286
|
self.exit_with_error = True
|
287
287
|
continue
|
288
288
|
|
289
|
-
logger.debug("Processing '
|
289
|
+
logger.debug(f"Processing '{image['name']}'")
|
290
290
|
|
291
291
|
try:
|
292
292
|
versions = dict()
|
@@ -331,7 +331,8 @@ class ImageManager:
|
|
331
331
|
# strip any directory path for file: urls in order to
|
332
332
|
# avoid exposing local filesystem details to other users
|
333
333
|
if url.startswith("file:") and "/" in url:
|
334
|
-
|
334
|
+
urlfile = url.rsplit("/", 1)[1]
|
335
|
+
url = f"file:{urlfile}"
|
335
336
|
versions[version["version"]]["meta"]["image_source"] = url
|
336
337
|
|
337
338
|
if "build_date" in version:
|
@@ -352,7 +353,7 @@ class ImageManager:
|
|
352
353
|
image["tags"].append(self.CONF.tag)
|
353
354
|
|
354
355
|
if "os_distro" in image["meta"]:
|
355
|
-
image["tags"].append("os
|
356
|
+
image["tags"].append(f"os:{image['meta']['os_distro']}")
|
356
357
|
|
357
358
|
if "image_description" not in image["meta"]:
|
358
359
|
image["meta"]["image_description"] = image["name"]
|
@@ -371,7 +372,7 @@ class ImageManager:
|
|
371
372
|
|
372
373
|
def import_image(
|
373
374
|
self, image: dict, name: str, url: str, versions: dict, version: str
|
374
|
-
) -> Image
|
375
|
+
) -> typing.Union[Image, None]:
|
375
376
|
"""
|
376
377
|
Create a new image in Glance and upload it using the web-download method
|
377
378
|
|
@@ -382,7 +383,8 @@ class ImageManager:
|
|
382
383
|
versions: versions dict generated by main()
|
383
384
|
version: currently processed version
|
384
385
|
"""
|
385
|
-
logger.info("Importing image
|
386
|
+
logger.info(f"Importing image {name}")
|
387
|
+
logger.info(f"Importing from URL {url}")
|
386
388
|
|
387
389
|
properties = {
|
388
390
|
"container_format": "bare",
|
@@ -403,16 +405,13 @@ class ImageManager:
|
|
403
405
|
with local_file:
|
404
406
|
try:
|
405
407
|
logger.info(
|
406
|
-
"Uploading local file '
|
407
|
-
% (parsed_url.path, name)
|
408
|
+
f"Uploading local file '{parsed_url.path}' as image {name}"
|
408
409
|
)
|
409
410
|
new_image.data = local_file
|
410
411
|
new_image.upload(self.conn.image)
|
411
412
|
except Exception as e:
|
412
413
|
self.conn.image.delete_image(new_image)
|
413
|
-
logger.error(
|
414
|
-
"Failed to upload local file for image %s\n%s" % (name, e)
|
415
|
-
)
|
414
|
+
logger.error(f"Failed to upload local file for image {name}\n{e}")
|
416
415
|
self.exit_with_error = True
|
417
416
|
return None
|
418
417
|
else:
|
@@ -435,13 +434,9 @@ class ImageManager:
|
|
435
434
|
or image.owner == self.conn.current_project_id
|
436
435
|
):
|
437
436
|
result[image.name] = image
|
438
|
-
logger.debug(
|
439
|
-
"Managed image '%s' (tags = %s)" % (image.name, image.tags)
|
440
|
-
)
|
437
|
+
logger.debug(f"Managed image '{image.name}' (tags = {image.tags})")
|
441
438
|
else:
|
442
|
-
logger.debug(
|
443
|
-
"Unmanaged image '%s' (tags = %s)" % (image.name, image.tags)
|
444
|
-
)
|
439
|
+
logger.debug(f"Unmanaged image '{image.name}' (tags = {image.tags})")
|
445
440
|
|
446
441
|
if self.CONF.use_os_hidden:
|
447
442
|
for image in self.conn.image.images(**{"os_hidden": True}):
|
@@ -451,17 +446,15 @@ class ImageManager:
|
|
451
446
|
):
|
452
447
|
result[image.name] = image
|
453
448
|
logger.debug(
|
454
|
-
"Managed hidden image '
|
455
|
-
% (image.name, image.tags)
|
449
|
+
f"Managed hidden image '{image.name}' (tags = {image.tags})"
|
456
450
|
)
|
457
451
|
else:
|
458
452
|
logger.debug(
|
459
|
-
"Unmanaged hidden image '
|
460
|
-
% (image.name, image.tags)
|
453
|
+
f"Unmanaged hidden image '{image.name}' (tags = {image.tags})"
|
461
454
|
)
|
462
455
|
return result
|
463
456
|
|
464
|
-
def wait_for_image(self, image: Image) -> Image
|
457
|
+
def wait_for_image(self, image: Image) -> typing.Union[Image, None]:
|
465
458
|
"""
|
466
459
|
Wait for an imported image to reach "active" state
|
467
460
|
|
@@ -482,9 +475,7 @@ class ImageManager:
|
|
482
475
|
# indefinitely stuck in "queued" state.
|
483
476
|
if imported_image.status == "queued":
|
484
477
|
if retry_attempts_for_queued_state < 0:
|
485
|
-
logger.error(
|
486
|
-
"Image %s seems stuck in queued state" % image.name
|
487
|
-
)
|
478
|
+
logger.error(f"Image {image.name} seems stuck in queued state")
|
488
479
|
self.exit_with_error = True
|
489
480
|
return None
|
490
481
|
else:
|
@@ -497,7 +488,7 @@ class ImageManager:
|
|
497
488
|
else:
|
498
489
|
return imported_image
|
499
490
|
except Exception as e:
|
500
|
-
logger.error("Exception while importing image
|
491
|
+
logger.error(f"Exception while importing image {image.name}\n{e}")
|
501
492
|
self.exit_with_error = True
|
502
493
|
|
503
494
|
def process_image(
|
@@ -525,12 +516,12 @@ class ImageManager:
|
|
525
516
|
|
526
517
|
for version in sorted_versions:
|
527
518
|
if image["multi"]:
|
528
|
-
name = "
|
519
|
+
name = f"{image['name']} ({version})"
|
529
520
|
else:
|
530
|
-
name = "
|
521
|
+
name = f"{image['name']} {version}"
|
531
522
|
|
532
|
-
logger.info("Processing image '
|
533
|
-
logger.debug("Checking existence of '
|
523
|
+
logger.info(f"Processing image '{name}'")
|
524
|
+
logger.debug(f"Checking existence of '{name}'")
|
534
525
|
existence = name in cloud_images
|
535
526
|
|
536
527
|
if existence and cloud_images[name].status != "active":
|
@@ -553,8 +544,7 @@ class ImageManager:
|
|
553
544
|
)
|
554
545
|
except KeyError:
|
555
546
|
logger.error(
|
556
|
-
"Image
|
557
|
-
% image["name"]
|
547
|
+
f"Image {image['name']} is missing property 'internal_version'"
|
558
548
|
)
|
559
549
|
|
560
550
|
elif (
|
@@ -563,7 +553,7 @@ class ImageManager:
|
|
563
553
|
and version == sorted_versions[-1]
|
564
554
|
and not existence
|
565
555
|
):
|
566
|
-
previous = "
|
556
|
+
previous = f"{image['name']} ({sorted_versions[-2]})"
|
567
557
|
existence = previous in cloud_images and image["name"] in cloud_images
|
568
558
|
|
569
559
|
elif (
|
@@ -584,8 +574,7 @@ class ImageManager:
|
|
584
574
|
)
|
585
575
|
if not upstream_checksum:
|
586
576
|
logger.error(
|
587
|
-
"Could not find checksum for image '
|
588
|
-
% image["name"]
|
577
|
+
f"Could not find checksum for image '{image['name']}', check the checksums_url"
|
589
578
|
)
|
590
579
|
return existing_images, imported_image, previous_image
|
591
580
|
|
@@ -596,11 +585,11 @@ class ImageManager:
|
|
596
585
|
else ""
|
597
586
|
)
|
598
587
|
if image_checksum == upstream_checksum:
|
599
|
-
logger.info("No new version for '
|
588
|
+
logger.info(f"No new version for '{image['name']}'")
|
600
589
|
existing_images.add(image["name"])
|
601
590
|
return existing_images, imported_image, previous_image
|
602
591
|
else:
|
603
|
-
logger.info("New version for '
|
592
|
+
logger.info(f"New version for '{image['name']}'")
|
604
593
|
existence = False
|
605
594
|
except KeyError:
|
606
595
|
# when switching from a release pointer to a latest pointer, the image has no checksum property
|
@@ -619,8 +608,7 @@ class ImageManager:
|
|
619
608
|
file_path = parsed_url.path
|
620
609
|
if not (os.path.exists(file_path) and os.path.isfile(file_path)):
|
621
610
|
logger.error(
|
622
|
-
"Skipping '
|
623
|
-
% (name, file_path)
|
611
|
+
f"Skipping '{name}' due to file '{file_path}' not found locally"
|
624
612
|
)
|
625
613
|
self.exit_with_error = True
|
626
614
|
return existing_images, imported_image, previous_image
|
@@ -628,12 +616,11 @@ class ImageManager:
|
|
628
616
|
r = requests.head(url)
|
629
617
|
|
630
618
|
if r.status_code in [200, 302]:
|
631
|
-
logger.info("Tested URL
|
619
|
+
logger.info(f"Tested URL {url}: {r.status_code}")
|
632
620
|
else:
|
633
|
-
logger.error("Tested URL
|
621
|
+
logger.error(f"Tested URL {url}: {r.status_code}")
|
634
622
|
logger.error(
|
635
|
-
"Skipping '
|
636
|
-
% (name, r.status_code)
|
623
|
+
f"Skipping '{name}' due to HTTP status code {r.status_code}"
|
637
624
|
)
|
638
625
|
self.exit_with_error = True
|
639
626
|
return existing_images, imported_image, previous_image
|
@@ -647,8 +634,7 @@ class ImageManager:
|
|
647
634
|
)
|
648
635
|
if import_result:
|
649
636
|
logger.info(
|
650
|
-
"Import of '
|
651
|
-
% name
|
637
|
+
f"Import of '{name}' successfully completed, reloading images"
|
652
638
|
)
|
653
639
|
cloud_images = self.get_images()
|
654
640
|
imported_image = cloud_images.get(name, None)
|
@@ -659,8 +645,7 @@ class ImageManager:
|
|
659
645
|
|
660
646
|
elif self.CONF.latest and version != sorted_versions[-1]:
|
661
647
|
logger.info(
|
662
|
-
"Skipping image '
|
663
|
-
% name
|
648
|
+
f"Skipping image '{name}' (only importing the latest version from type multi)"
|
664
649
|
)
|
665
650
|
|
666
651
|
if image["multi"]:
|
@@ -697,7 +682,7 @@ class ImageManager:
|
|
697
682
|
image["meta"] = meta.copy()
|
698
683
|
|
699
684
|
if name in cloud_images:
|
700
|
-
logger.info("Checking parameters of '
|
685
|
+
logger.info(f"Checking parameters of '{name}'")
|
701
686
|
|
702
687
|
cloud_image = cloud_images[name]
|
703
688
|
real_image_size = int(
|
@@ -708,8 +693,7 @@ class ImageManager:
|
|
708
693
|
|
709
694
|
if "min_disk" in image and image["min_disk"] != cloud_image.min_disk:
|
710
695
|
logger.info(
|
711
|
-
"Setting min_disk:
|
712
|
-
% (image["min_disk"], cloud_image.min_disk)
|
696
|
+
f"Setting min_disk: {image['min_disk']} != {cloud_image.min_disk}"
|
713
697
|
)
|
714
698
|
self.conn.image.update_image(
|
715
699
|
cloud_image.id, **{"min_disk": int(image["min_disk"])}
|
@@ -718,15 +702,14 @@ class ImageManager:
|
|
718
702
|
if (
|
719
703
|
"min_disk" in image and real_image_size > image["min_disk"]
|
720
704
|
) or "min_disk" not in image:
|
721
|
-
logger.info("Setting min_disk =
|
705
|
+
logger.info(f"Setting min_disk = {real_image_size}")
|
722
706
|
self.conn.image.update_image(
|
723
707
|
cloud_image.id, **{"min_disk": real_image_size}
|
724
708
|
)
|
725
709
|
|
726
710
|
if "min_ram" in image and image["min_ram"] != cloud_image.min_ram:
|
727
711
|
logger.info(
|
728
|
-
"Setting min_ram:
|
729
|
-
% (image["min_ram"], cloud_image.min_ram)
|
712
|
+
f"Setting min_ram: {image['min_ram']} != {cloud_image.min_ram}"
|
730
713
|
)
|
731
714
|
self.conn.image.update_image(
|
732
715
|
cloud_image.id, **{"min_ram": int(image["min_ram"])}
|
@@ -734,7 +717,7 @@ class ImageManager:
|
|
734
717
|
|
735
718
|
if self.CONF.use_os_hidden:
|
736
719
|
if "hidden" in versions[version]:
|
737
|
-
logger.info("Setting os_hidden =
|
720
|
+
logger.info(f"Setting os_hidden = {versions[version]['hidden']}")
|
738
721
|
self.conn.image.update_image(
|
739
722
|
cloud_image.id, **{"os_hidden": versions[version]["hidden"]}
|
740
723
|
)
|
@@ -756,24 +739,23 @@ class ImageManager:
|
|
756
739
|
)
|
757
740
|
modify_date = modify_date.replace("-", "")
|
758
741
|
|
759
|
-
logger.info("Setting internal_version =
|
742
|
+
logger.info(f"Setting internal_version = {modify_date}")
|
760
743
|
image["meta"]["internal_version"] = modify_date
|
761
744
|
except Exception:
|
762
745
|
logger.error(
|
763
|
-
"Error when retrieving the modification date of image '
|
764
|
-
image["name"],
|
746
|
+
f"Error when retrieving the modification date of image '{image['name']}'"
|
765
747
|
)
|
766
|
-
logger.info("Setting internal_version =
|
748
|
+
logger.info(f"Setting internal_version = {version}")
|
767
749
|
image["meta"]["internal_version"] = version
|
768
750
|
else:
|
769
|
-
logger.info("Setting internal_version =
|
751
|
+
logger.info(f"Setting internal_version = {version}")
|
770
752
|
image["meta"]["internal_version"] = version
|
771
753
|
|
772
|
-
logger.info("Setting image_original_user =
|
754
|
+
logger.info(f"Setting image_original_user = {image['login']}")
|
773
755
|
image["meta"]["image_original_user"] = image["login"]
|
774
756
|
|
775
757
|
if self.CONF.hypervisor:
|
776
|
-
logger.info("Setting hypervisor type =
|
758
|
+
logger.info(f"Setting hypervisor type = {self.CONF.hypervisor}")
|
777
759
|
image["meta"]["hypervisor_type"] = self.CONF.hypervisor
|
778
760
|
|
779
761
|
if version == "latest" and upstream_checksum:
|
@@ -786,12 +768,12 @@ class ImageManager:
|
|
786
768
|
|
787
769
|
for tag in image["tags"]:
|
788
770
|
if tag not in cloud_image.tags:
|
789
|
-
logger.info("Adding tag
|
771
|
+
logger.info(f"Adding tag {tag}")
|
790
772
|
self.conn.image.add_tag(cloud_image.id, tag)
|
791
773
|
|
792
774
|
for tag in cloud_image.tags:
|
793
775
|
if tag not in image["tags"]:
|
794
|
-
logger.info("Deleting tag
|
776
|
+
logger.info(f"Deleting tag {tag}")
|
795
777
|
self.conn.image.remove_tag(cloud_image.id, tag)
|
796
778
|
|
797
779
|
if "meta" in versions[version]:
|
@@ -803,8 +785,7 @@ class ImageManager:
|
|
803
785
|
if property in image["meta"]:
|
804
786
|
if image["meta"][property] != properties[property]:
|
805
787
|
logger.info(
|
806
|
-
"Setting property
|
807
|
-
% (property, properties[property], image["meta"][property])
|
788
|
+
f"Setting property {property}: {properties[property]} != {image['meta'][property]}"
|
808
789
|
)
|
809
790
|
self.conn.image.update_image(
|
810
791
|
cloud_image.id, **{property: str(image["meta"][property])}
|
@@ -816,37 +797,37 @@ class ImageManager:
|
|
816
797
|
"stores",
|
817
798
|
] or not property.startswith("os_"):
|
818
799
|
# FIXME: handle deletion of properties
|
819
|
-
logger.debug("Deleting property
|
800
|
+
logger.debug(f"Deleting property {property}")
|
820
801
|
|
821
802
|
for property in image["meta"]:
|
822
803
|
if property not in properties:
|
823
804
|
logger.info(
|
824
|
-
"Setting property
|
805
|
+
f"Setting property {property}: {image['meta'][property]}"
|
825
806
|
)
|
826
807
|
self.conn.image.update_image(
|
827
808
|
cloud_image.id, **{property: str(image["meta"][property])}
|
828
809
|
)
|
829
810
|
|
830
|
-
logger.info("Checking status of '
|
811
|
+
logger.info(f"Checking status of '{name}'")
|
831
812
|
if (
|
832
813
|
cloud_image.status != image["status"]
|
833
814
|
and image["status"] == "deactivated"
|
834
815
|
):
|
835
|
-
logger.info("Deactivating image '
|
816
|
+
logger.info(f"Deactivating image '{name}'")
|
836
817
|
self.conn.image.deactivate_image(cloud_image.id)
|
837
818
|
|
838
819
|
elif cloud_image.status != image["status"] and image["status"] == "active":
|
839
|
-
logger.info("Reactivating image '
|
820
|
+
logger.info(f"Reactivating image '{name}'")
|
840
821
|
self.conn.image.reactivate_image(cloud_image.id)
|
841
822
|
|
842
|
-
logger.info("Checking visibility of '
|
823
|
+
logger.info(f"Checking visibility of '{name}'")
|
843
824
|
if "visibility" in versions[version]:
|
844
825
|
visibility = versions[version]["visibility"]
|
845
826
|
else:
|
846
827
|
visibility = image["visibility"]
|
847
828
|
|
848
829
|
if cloud_image.visibility != visibility:
|
849
|
-
logger.info("Setting visibility of '
|
830
|
+
logger.info(f"Setting visibility of '{name}' to '{visibility}'")
|
850
831
|
self.conn.image.update_image(cloud_image.id, visibility=visibility)
|
851
832
|
|
852
833
|
def rename_images(
|
@@ -868,17 +849,17 @@ class ImageManager:
|
|
868
849
|
cloud_images = self.get_images()
|
869
850
|
|
870
851
|
if len(sorted_versions) > 1:
|
871
|
-
latest = "
|
872
|
-
previous_latest = "
|
852
|
+
latest = f"{name} ({sorted_versions[-1]})"
|
853
|
+
previous_latest = f"{name} ({sorted_versions[-2]})"
|
873
854
|
|
874
855
|
if name in cloud_images and previous_latest not in cloud_images:
|
875
|
-
logger.info("Renaming
|
856
|
+
logger.info(f"Renaming {name} to {previous_latest}")
|
876
857
|
self.conn.image.update_image(
|
877
858
|
cloud_images[name].id, name=previous_latest
|
878
859
|
)
|
879
860
|
|
880
861
|
if latest in cloud_images:
|
881
|
-
logger.info("Renaming
|
862
|
+
logger.info(f"Renaming {latest} to {name}")
|
882
863
|
self.conn.image.update_image(cloud_images[latest].id, name=name)
|
883
864
|
|
884
865
|
elif len(sorted_versions) == 1 and name in cloud_images:
|
@@ -891,34 +872,30 @@ class ImageManager:
|
|
891
872
|
)
|
892
873
|
create_date = create_date.replace("-", "")
|
893
874
|
|
894
|
-
previous_latest = "
|
875
|
+
previous_latest = f"{name} ({create_date})"
|
895
876
|
|
896
877
|
logger.info(
|
897
|
-
"Setting internal_version:
|
898
|
-
% (create_date, previous_latest)
|
878
|
+
f"Setting internal_version: {create_date} for {previous_latest}"
|
899
879
|
)
|
900
880
|
self.conn.image.update_image(
|
901
881
|
previous_image.id, **{"internal_version": create_date}
|
902
882
|
)
|
903
883
|
else:
|
904
|
-
previous_latest =
|
905
|
-
name
|
906
|
-
previous_image["properties"]["internal_version"],
|
884
|
+
previous_latest = (
|
885
|
+
f"{name} ({previous_image['properties']['internal_version']})"
|
907
886
|
)
|
908
887
|
|
909
|
-
logger.info("Renaming old latest '
|
888
|
+
logger.info(f"Renaming old latest '{name}' to '{previous_latest}'")
|
910
889
|
self.conn.image.update_image(previous_image.id, name=previous_latest)
|
911
890
|
|
912
|
-
logger.info(
|
913
|
-
"Renaming imported image '%s' to '%s'" % (imported_image.name, name)
|
914
|
-
)
|
891
|
+
logger.info(f"Renaming imported image '{imported_image.name}' to '{name}'")
|
915
892
|
self.conn.image.update_image(imported_image.id, name=name)
|
916
893
|
|
917
894
|
elif len(sorted_versions) == 1:
|
918
|
-
latest = "
|
895
|
+
latest = f"{name} ({sorted_versions[-1]})"
|
919
896
|
|
920
897
|
if latest in cloud_images:
|
921
|
-
logger.info("Renaming
|
898
|
+
logger.info(f"Renaming {latest} to {name}")
|
922
899
|
self.conn.image.update_image(cloud_images[latest].id, name=name)
|
923
900
|
|
924
901
|
def check_image_age(self) -> set:
|
@@ -1064,10 +1041,10 @@ class ImageManager:
|
|
1064
1041
|
and not self.CONF.dry_run
|
1065
1042
|
):
|
1066
1043
|
try:
|
1067
|
-
logger.info("Deactivating image '
|
1044
|
+
logger.info(f"Deactivating image '{image}'")
|
1068
1045
|
self.conn.image.deactivate_image(cloud_image.id)
|
1069
1046
|
|
1070
|
-
logger.info("Setting visibility of '
|
1047
|
+
logger.info(f"Setting visibility of '{image}' to 'community'")
|
1071
1048
|
self.conn.image.update_image(
|
1072
1049
|
cloud_image.id, visibility="community"
|
1073
1050
|
)
|
@@ -1076,25 +1053,24 @@ class ImageManager:
|
|
1076
1053
|
"keep" not in image_definition
|
1077
1054
|
or not image_definition["keep"]
|
1078
1055
|
):
|
1079
|
-
logger.info("Deleting
|
1056
|
+
logger.info(f"Deleting {image}")
|
1080
1057
|
self.conn.image.delete_image(cloud_image.id)
|
1081
1058
|
else:
|
1082
1059
|
logger.info(
|
1083
|
-
"Image '
|
1084
|
-
% image
|
1060
|
+
f"Image '{image}' will not be deleted, because 'keep' flag is True"
|
1085
1061
|
)
|
1086
1062
|
except Exception as e:
|
1087
1063
|
logger.info(
|
1088
|
-
"
|
1064
|
+
f"{image} is still in use and cannot be deleted\n {e}"
|
1089
1065
|
)
|
1090
1066
|
|
1091
1067
|
else:
|
1092
1068
|
logger.warning(
|
1093
|
-
"Image
|
1069
|
+
f"Image {image} should be deleted, but deletion is disabled"
|
1094
1070
|
)
|
1095
1071
|
try:
|
1096
1072
|
if self.CONF.deactivate and not self.CONF.dry_run:
|
1097
|
-
logger.info("Deactivating image '
|
1073
|
+
logger.info(f"Deactivating image '{image}'")
|
1098
1074
|
self.conn.image.deactivate_image(cloud_image.id)
|
1099
1075
|
|
1100
1076
|
if (
|
@@ -1103,13 +1079,13 @@ class ImageManager:
|
|
1103
1079
|
and cloud_image.visibility != "community"
|
1104
1080
|
):
|
1105
1081
|
logger.info(
|
1106
|
-
"Setting visibility of '
|
1082
|
+
f"Setting visibility of '{image}' to 'community'"
|
1107
1083
|
)
|
1108
1084
|
self.conn.image.update_image(
|
1109
1085
|
cloud_image.id, visibility="community"
|
1110
1086
|
)
|
1111
1087
|
except Exception as e:
|
1112
|
-
logger.error("An Exception occurred: \n
|
1088
|
+
logger.error(f"An Exception occurred: \n{e}")
|
1113
1089
|
self.exit_with_error = True
|
1114
1090
|
elif counter[image_name] <= last:
|
1115
1091
|
logger.info(
|
@@ -1120,12 +1096,12 @@ class ImageManager:
|
|
1120
1096
|
and not self.CONF.dry_run
|
1121
1097
|
and cloud_image.visibility != "community"
|
1122
1098
|
):
|
1123
|
-
logger.info("Setting visibility of '
|
1099
|
+
logger.info(f"Setting visibility of '{image}' to 'community'")
|
1124
1100
|
self.conn.image.update_image(cloud_image.id, visibility="community")
|
1125
1101
|
elif (
|
1126
1102
|
counter[image_name] < last and self.CONF.hide and not self.CONF.dry_run
|
1127
1103
|
):
|
1128
|
-
logger.info("Setting visibility of '
|
1104
|
+
logger.info(f"Setting visibility of '{image}' to 'community'")
|
1129
1105
|
self.conn.image.update_image(cloud_image.id, visibility="community")
|
1130
1106
|
return unmanaged_images
|
1131
1107
|
|
@@ -1141,39 +1117,32 @@ class ImageManager:
|
|
1141
1117
|
self.exit_with_error = True
|
1142
1118
|
for result in e.results:
|
1143
1119
|
logger.error(
|
1144
|
-
"Error validating data '
|
1145
|
-
% (result.data, result.schema)
|
1120
|
+
f"Error validating data '{result.data}' with '{result.schema}'"
|
1146
1121
|
)
|
1147
1122
|
for error in result.errors:
|
1148
|
-
logger.error("\t
|
1123
|
+
logger.error(f"\t{error}")
|
1149
1124
|
else:
|
1150
|
-
logger.debug("Image file
|
1125
|
+
logger.debug(f"Image file {file} is valid")
|
1151
1126
|
except FileNotFoundError:
|
1152
|
-
logger.error("Invalid path '
|
1127
|
+
logger.error(f"Invalid path '{self.CONF.images}'")
|
1153
1128
|
|
1154
1129
|
def share_image_with_project(self, image, project):
|
1155
1130
|
member = self.conn.image.find_member(project.id, image.id)
|
1156
1131
|
|
1157
1132
|
if not member:
|
1158
|
-
logger.info(
|
1159
|
-
"add - %s - %s (%s)" % (image.name, project.name, project.domain_id)
|
1160
|
-
)
|
1133
|
+
logger.info(f"add - {image.name} - {project.name} ({project.domain_id})")
|
1161
1134
|
if not self.CONF.dry_run:
|
1162
1135
|
member = self.conn.image.add_member(image.id, member_id=project.id)
|
1163
1136
|
|
1164
1137
|
if not self.CONF.dry_run and member.status != "accepted":
|
1165
|
-
logger.info(
|
1166
|
-
"accept - %s - %s (%s)" % (image.name, project.name, project.domain_id)
|
1167
|
-
)
|
1138
|
+
logger.info(f"accept - {image.name} - {project.name} ({project.domain_id})")
|
1168
1139
|
self.conn.image.update_member(member, image.id, status="accepted")
|
1169
1140
|
|
1170
1141
|
def unshare_image_with_project(self, image, project):
|
1171
1142
|
member = self.conn.image.find_member(project.id, image.id)
|
1172
1143
|
|
1173
1144
|
if member:
|
1174
|
-
logger.info(
|
1175
|
-
"del - %s - %s (%s)" % (image.name, project.name, project.domain_id)
|
1176
|
-
)
|
1145
|
+
logger.info(f"del - {image.name} - {project.name} ({project.domain_id})")
|
1177
1146
|
if not self.CONF.dry_run:
|
1178
1147
|
self.conn.image.remove_member(member, image.id)
|
1179
1148
|
|
@@ -37,7 +37,7 @@ def get_latest_default(
|
|
37
37
|
|
38
38
|
checksums = {}
|
39
39
|
for line in result.text.split("\n"):
|
40
|
-
cs = re.split("\s+", line)
|
40
|
+
cs = re.split(r"\s+", line)
|
41
41
|
if shortname in ["rocky-8", "rocky-9"]:
|
42
42
|
if len(cs) == 4 and cs[0] == "SHA256":
|
43
43
|
checksums[latest_filename] = cs[3]
|
@@ -149,10 +149,10 @@ def mirror_image(
|
|
149
149
|
|
150
150
|
try:
|
151
151
|
client.stat_object(minio_bucket, os.path.join(dirname, new_filename))
|
152
|
-
logger.info("'
|
152
|
+
logger.info(f"'{new_filename}' available in '{dirname}'")
|
153
153
|
except S3Error:
|
154
|
-
logger.info("'
|
155
|
-
logger.info("Downloading '
|
154
|
+
logger.info(f"'{new_filename}' not yet available in '{dirname}'")
|
155
|
+
logger.info(f"Downloading '{latest_url}'")
|
156
156
|
|
157
157
|
response = requests.get(latest_url, stream=True)
|
158
158
|
with open(os.path.basename(path.path), "wb") as fp:
|
@@ -160,13 +160,11 @@ def mirror_image(
|
|
160
160
|
del response
|
161
161
|
|
162
162
|
if fileextension in [".bz2", ".zip", ".xz", ".gz"]:
|
163
|
-
logger.info("Decompressing '
|
163
|
+
logger.info(f"Decompressing '{os.path.basename(path.path)}'")
|
164
164
|
patoolib.extract_archive(os.path.basename(path.path), outdir=".")
|
165
165
|
os.remove(os.path.basename(path.path))
|
166
166
|
|
167
|
-
logger.info(
|
168
|
-
"Uploading '%s' to '%s' as '%s'" % (filename, dirname, new_filename)
|
169
|
-
)
|
167
|
+
logger.info(f"Uploading '{filename}' to '{dirname}' as '{new_filename}'")
|
170
168
|
|
171
169
|
client.fput_object(minio_bucket, os.path.join(dirname, new_filename), filename)
|
172
170
|
os.remove(filename)
|
@@ -241,12 +239,25 @@ def update_image(
|
|
241
239
|
|
242
240
|
minio_server = str(minio_server)
|
243
241
|
minio_bucket = str(minio_bucket)
|
244
|
-
|
245
|
-
logger.info(f"New URL is {
|
246
|
-
|
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
|
247
258
|
|
248
259
|
if dry_run:
|
249
|
-
logger.info(f"Not mirroring {
|
260
|
+
logger.info(f"Not mirroring {mirror_url}, dry-run enabled")
|
250
261
|
else:
|
251
262
|
mirror_image(
|
252
263
|
image,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: openstack-image-manager
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20240403.0
|
4
4
|
Summary: OpenStack image manager
|
5
5
|
Author-email: OSISM community <info@osism.tech>
|
6
6
|
License: Apache License
|
@@ -232,13 +232,13 @@ 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.
|
236
|
-
Requires-Dist: yamale ==5.
|
235
|
+
Requires-Dist: typer[all] ==0.12.0
|
236
|
+
Requires-Dist: yamale ==5.1.0
|
237
237
|
|
238
238
|
# openstack-image-manager
|
239
239
|
|
240
240
|
[](https://pypi.org/project/openstack-image-manager/)
|
241
241
|
[](https://pypi.org/project/openstack-image-manager/)
|
242
|
-
[](https://osism.
|
242
|
+
[](https://osism.tech/docs/guides/operations-guide/openstack/day2-operations/image-manager)
|
243
243
|
|
244
244
|
Easily manage and keep up to date a large number of images on an OpenStack environment
|
@@ -0,0 +1,11 @@
|
|
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,11 +0,0 @@
|
|
1
|
-
openstack_image_manager/__init__.py,sha256=z6lQHDMfCV8IkUz5pM1QYfQ37O2Rdy82jYovN8N9DIU,240
|
2
|
-
openstack_image_manager/manage.py,sha256=biCPWdV5-_4rI_6DtieOWe_HXrcnWP67hs1RzwnOdts,47688
|
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=RBH47LjWsWVKaYVwBxlDviSSZY6RxUyUQdrwtKG5bKs,10545
|
6
|
-
openstack_image_manager-0.20240318.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
7
|
-
openstack_image_manager-0.20240318.0.dist-info/METADATA,sha256=IEeNJuQY-DBWauuhBsj2CZzHGRWHLbGg4LeU6X_Ua1g,14875
|
8
|
-
openstack_image_manager-0.20240318.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
9
|
-
openstack_image_manager-0.20240318.0.dist-info/entry_points.txt,sha256=AEHPHHHZ3jAZfpvaI5ZzLi3DHb9vGQwL7TJcw_G_5nc,80
|
10
|
-
openstack_image_manager-0.20240318.0.dist-info/top_level.txt,sha256=iLfREddId51T97Dr9IGRQtJXKJgVy1PB6uHCaQk1j44,24
|
11
|
-
openstack_image_manager-0.20240318.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|