openstack-image-manager 0.20240327.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.
- openstack_image_manager/{manage.py → main.py} +86 -118
- {openstack_image_manager-0.20240327.0.dist-info → openstack_image_manager-0.20240411.0.dist-info}/METADATA +2 -2
- openstack_image_manager-0.20240411.0.dist-info/RECORD +8 -0
- openstack_image_manager-0.20240411.0.dist-info/entry_points.txt +2 -0
- openstack_image_manager/mirror.py +0 -144
- openstack_image_manager/table.py +0 -47
- openstack_image_manager/update.py +0 -343
- openstack_image_manager-0.20240327.0.dist-info/RECORD +0 -11
- openstack_image_manager-0.20240327.0.dist-info/entry_points.txt +0 -2
- {openstack_image_manager-0.20240327.0.dist-info → openstack_image_manager-0.20240411.0.dist-info}/LICENSE +0 -0
- {openstack_image_manager-0.20240327.0.dist-info → openstack_image_manager-0.20240411.0.dist-info}/WHEEL +0 -0
- {openstack_image_manager-0.20240327.0.dist-info → openstack_image_manager-0.20240411.0.dist-info}/top_level.txt +0 -0
@@ -203,12 +203,12 @@ class ImageManager:
|
|
203
203
|
Read all files in etc/images/ and process each image
|
204
204
|
Rename outdated images when not dry-running
|
205
205
|
"""
|
206
|
-
logger.debug("cloud =
|
207
|
-
logger.debug("dry-run =
|
208
|
-
logger.debug("images =
|
209
|
-
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}")
|
210
210
|
logger.debug(
|
211
|
-
"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}"
|
212
212
|
)
|
213
213
|
|
214
214
|
# check local image definitions with yamale
|
@@ -281,13 +281,12 @@ class ImageManager:
|
|
281
281
|
for required_key in REQUIRED_KEYS:
|
282
282
|
if required_key not in image:
|
283
283
|
logger.error(
|
284
|
-
"'
|
285
|
-
% (image["name"], required_key)
|
284
|
+
f"'{image['name']}' lacks the necessary key {required_key}"
|
286
285
|
)
|
287
286
|
self.exit_with_error = True
|
288
287
|
continue
|
289
288
|
|
290
|
-
logger.debug("Processing '
|
289
|
+
logger.debug(f"Processing '{image['name']}'")
|
291
290
|
|
292
291
|
try:
|
293
292
|
versions = dict()
|
@@ -332,7 +331,8 @@ class ImageManager:
|
|
332
331
|
# strip any directory path for file: urls in order to
|
333
332
|
# avoid exposing local filesystem details to other users
|
334
333
|
if url.startswith("file:") and "/" in url:
|
335
|
-
|
334
|
+
urlfile = url.rsplit("/", 1)[1]
|
335
|
+
url = f"file:{urlfile}"
|
336
336
|
versions[version["version"]]["meta"]["image_source"] = url
|
337
337
|
|
338
338
|
if "build_date" in version:
|
@@ -353,7 +353,7 @@ class ImageManager:
|
|
353
353
|
image["tags"].append(self.CONF.tag)
|
354
354
|
|
355
355
|
if "os_distro" in image["meta"]:
|
356
|
-
image["tags"].append("os
|
356
|
+
image["tags"].append(f"os:{image['meta']['os_distro']}")
|
357
357
|
|
358
358
|
if "image_description" not in image["meta"]:
|
359
359
|
image["meta"]["image_description"] = image["name"]
|
@@ -383,7 +383,8 @@ class ImageManager:
|
|
383
383
|
versions: versions dict generated by main()
|
384
384
|
version: currently processed version
|
385
385
|
"""
|
386
|
-
logger.info("Importing image
|
386
|
+
logger.info(f"Importing image {name}")
|
387
|
+
logger.info(f"Importing from URL {url}")
|
387
388
|
|
388
389
|
properties = {
|
389
390
|
"container_format": "bare",
|
@@ -404,16 +405,13 @@ class ImageManager:
|
|
404
405
|
with local_file:
|
405
406
|
try:
|
406
407
|
logger.info(
|
407
|
-
"Uploading local file '
|
408
|
-
% (parsed_url.path, name)
|
408
|
+
f"Uploading local file '{parsed_url.path}' as image {name}"
|
409
409
|
)
|
410
410
|
new_image.data = local_file
|
411
411
|
new_image.upload(self.conn.image)
|
412
412
|
except Exception as e:
|
413
413
|
self.conn.image.delete_image(new_image)
|
414
|
-
logger.error(
|
415
|
-
"Failed to upload local file for image %s\n%s" % (name, e)
|
416
|
-
)
|
414
|
+
logger.error(f"Failed to upload local file for image {name}\n{e}")
|
417
415
|
self.exit_with_error = True
|
418
416
|
return None
|
419
417
|
else:
|
@@ -436,13 +434,9 @@ class ImageManager:
|
|
436
434
|
or image.owner == self.conn.current_project_id
|
437
435
|
):
|
438
436
|
result[image.name] = image
|
439
|
-
logger.debug(
|
440
|
-
"Managed image '%s' (tags = %s)" % (image.name, image.tags)
|
441
|
-
)
|
437
|
+
logger.debug(f"Managed image '{image.name}' (tags = {image.tags})")
|
442
438
|
else:
|
443
|
-
logger.debug(
|
444
|
-
"Unmanaged image '%s' (tags = %s)" % (image.name, image.tags)
|
445
|
-
)
|
439
|
+
logger.debug(f"Unmanaged image '{image.name}' (tags = {image.tags})")
|
446
440
|
|
447
441
|
if self.CONF.use_os_hidden:
|
448
442
|
for image in self.conn.image.images(**{"os_hidden": True}):
|
@@ -452,13 +446,11 @@ class ImageManager:
|
|
452
446
|
):
|
453
447
|
result[image.name] = image
|
454
448
|
logger.debug(
|
455
|
-
"Managed hidden image '
|
456
|
-
% (image.name, image.tags)
|
449
|
+
f"Managed hidden image '{image.name}' (tags = {image.tags})"
|
457
450
|
)
|
458
451
|
else:
|
459
452
|
logger.debug(
|
460
|
-
"Unmanaged hidden image '
|
461
|
-
% (image.name, image.tags)
|
453
|
+
f"Unmanaged hidden image '{image.name}' (tags = {image.tags})"
|
462
454
|
)
|
463
455
|
return result
|
464
456
|
|
@@ -483,9 +475,7 @@ class ImageManager:
|
|
483
475
|
# indefinitely stuck in "queued" state.
|
484
476
|
if imported_image.status == "queued":
|
485
477
|
if retry_attempts_for_queued_state < 0:
|
486
|
-
logger.error(
|
487
|
-
"Image %s seems stuck in queued state" % image.name
|
488
|
-
)
|
478
|
+
logger.error(f"Image {image.name} seems stuck in queued state")
|
489
479
|
self.exit_with_error = True
|
490
480
|
return None
|
491
481
|
else:
|
@@ -498,7 +488,7 @@ class ImageManager:
|
|
498
488
|
else:
|
499
489
|
return imported_image
|
500
490
|
except Exception as e:
|
501
|
-
logger.error("Exception while importing image
|
491
|
+
logger.error(f"Exception while importing image {image.name}\n{e}")
|
502
492
|
self.exit_with_error = True
|
503
493
|
|
504
494
|
def process_image(
|
@@ -526,12 +516,12 @@ class ImageManager:
|
|
526
516
|
|
527
517
|
for version in sorted_versions:
|
528
518
|
if image["multi"]:
|
529
|
-
name = "
|
519
|
+
name = f"{image['name']} ({version})"
|
530
520
|
else:
|
531
|
-
name = "
|
521
|
+
name = f"{image['name']} {version}"
|
532
522
|
|
533
|
-
logger.info("Processing image '
|
534
|
-
logger.debug("Checking existence of '
|
523
|
+
logger.info(f"Processing image '{name}'")
|
524
|
+
logger.debug(f"Checking existence of '{name}'")
|
535
525
|
existence = name in cloud_images
|
536
526
|
|
537
527
|
if existence and cloud_images[name].status != "active":
|
@@ -554,8 +544,7 @@ class ImageManager:
|
|
554
544
|
)
|
555
545
|
except KeyError:
|
556
546
|
logger.error(
|
557
|
-
"Image
|
558
|
-
% image["name"]
|
547
|
+
f"Image {image['name']} is missing property 'internal_version'"
|
559
548
|
)
|
560
549
|
|
561
550
|
elif (
|
@@ -564,7 +553,7 @@ class ImageManager:
|
|
564
553
|
and version == sorted_versions[-1]
|
565
554
|
and not existence
|
566
555
|
):
|
567
|
-
previous = "
|
556
|
+
previous = f"{image['name']} ({sorted_versions[-2]})"
|
568
557
|
existence = previous in cloud_images and image["name"] in cloud_images
|
569
558
|
|
570
559
|
elif (
|
@@ -585,8 +574,7 @@ class ImageManager:
|
|
585
574
|
)
|
586
575
|
if not upstream_checksum:
|
587
576
|
logger.error(
|
588
|
-
"Could not find checksum for image '
|
589
|
-
% image["name"]
|
577
|
+
f"Could not find checksum for image '{image['name']}', check the checksums_url"
|
590
578
|
)
|
591
579
|
return existing_images, imported_image, previous_image
|
592
580
|
|
@@ -597,11 +585,11 @@ class ImageManager:
|
|
597
585
|
else ""
|
598
586
|
)
|
599
587
|
if image_checksum == upstream_checksum:
|
600
|
-
logger.info("No new version for '
|
588
|
+
logger.info(f"No new version for '{image['name']}'")
|
601
589
|
existing_images.add(image["name"])
|
602
590
|
return existing_images, imported_image, previous_image
|
603
591
|
else:
|
604
|
-
logger.info("New version for '
|
592
|
+
logger.info(f"New version for '{image['name']}'")
|
605
593
|
existence = False
|
606
594
|
except KeyError:
|
607
595
|
# when switching from a release pointer to a latest pointer, the image has no checksum property
|
@@ -620,8 +608,7 @@ class ImageManager:
|
|
620
608
|
file_path = parsed_url.path
|
621
609
|
if not (os.path.exists(file_path) and os.path.isfile(file_path)):
|
622
610
|
logger.error(
|
623
|
-
"Skipping '
|
624
|
-
% (name, file_path)
|
611
|
+
f"Skipping '{name}' due to file '{file_path}' not found locally"
|
625
612
|
)
|
626
613
|
self.exit_with_error = True
|
627
614
|
return existing_images, imported_image, previous_image
|
@@ -629,12 +616,11 @@ class ImageManager:
|
|
629
616
|
r = requests.head(url)
|
630
617
|
|
631
618
|
if r.status_code in [200, 302]:
|
632
|
-
logger.info("Tested URL
|
619
|
+
logger.info(f"Tested URL {url}: {r.status_code}")
|
633
620
|
else:
|
634
|
-
logger.error("Tested URL
|
621
|
+
logger.error(f"Tested URL {url}: {r.status_code}")
|
635
622
|
logger.error(
|
636
|
-
"Skipping '
|
637
|
-
% (name, r.status_code)
|
623
|
+
f"Skipping '{name}' due to HTTP status code {r.status_code}"
|
638
624
|
)
|
639
625
|
self.exit_with_error = True
|
640
626
|
return existing_images, imported_image, previous_image
|
@@ -648,8 +634,7 @@ class ImageManager:
|
|
648
634
|
)
|
649
635
|
if import_result:
|
650
636
|
logger.info(
|
651
|
-
"Import of '
|
652
|
-
% name
|
637
|
+
f"Import of '{name}' successfully completed, reloading images"
|
653
638
|
)
|
654
639
|
cloud_images = self.get_images()
|
655
640
|
imported_image = cloud_images.get(name, None)
|
@@ -660,8 +645,7 @@ class ImageManager:
|
|
660
645
|
|
661
646
|
elif self.CONF.latest and version != sorted_versions[-1]:
|
662
647
|
logger.info(
|
663
|
-
"Skipping image '
|
664
|
-
% name
|
648
|
+
f"Skipping image '{name}' (only importing the latest version from type multi)"
|
665
649
|
)
|
666
650
|
|
667
651
|
if image["multi"]:
|
@@ -698,7 +682,7 @@ class ImageManager:
|
|
698
682
|
image["meta"] = meta.copy()
|
699
683
|
|
700
684
|
if name in cloud_images:
|
701
|
-
logger.info("Checking parameters of '
|
685
|
+
logger.info(f"Checking parameters of '{name}'")
|
702
686
|
|
703
687
|
cloud_image = cloud_images[name]
|
704
688
|
real_image_size = int(
|
@@ -709,8 +693,7 @@ class ImageManager:
|
|
709
693
|
|
710
694
|
if "min_disk" in image and image["min_disk"] != cloud_image.min_disk:
|
711
695
|
logger.info(
|
712
|
-
"Setting min_disk:
|
713
|
-
% (image["min_disk"], cloud_image.min_disk)
|
696
|
+
f"Setting min_disk: {image['min_disk']} != {cloud_image.min_disk}"
|
714
697
|
)
|
715
698
|
self.conn.image.update_image(
|
716
699
|
cloud_image.id, **{"min_disk": int(image["min_disk"])}
|
@@ -719,15 +702,14 @@ class ImageManager:
|
|
719
702
|
if (
|
720
703
|
"min_disk" in image and real_image_size > image["min_disk"]
|
721
704
|
) or "min_disk" not in image:
|
722
|
-
logger.info("Setting min_disk =
|
705
|
+
logger.info(f"Setting min_disk = {real_image_size}")
|
723
706
|
self.conn.image.update_image(
|
724
707
|
cloud_image.id, **{"min_disk": real_image_size}
|
725
708
|
)
|
726
709
|
|
727
710
|
if "min_ram" in image and image["min_ram"] != cloud_image.min_ram:
|
728
711
|
logger.info(
|
729
|
-
"Setting min_ram:
|
730
|
-
% (image["min_ram"], cloud_image.min_ram)
|
712
|
+
f"Setting min_ram: {image['min_ram']} != {cloud_image.min_ram}"
|
731
713
|
)
|
732
714
|
self.conn.image.update_image(
|
733
715
|
cloud_image.id, **{"min_ram": int(image["min_ram"])}
|
@@ -735,7 +717,7 @@ class ImageManager:
|
|
735
717
|
|
736
718
|
if self.CONF.use_os_hidden:
|
737
719
|
if "hidden" in versions[version]:
|
738
|
-
logger.info("Setting os_hidden =
|
720
|
+
logger.info(f"Setting os_hidden = {versions[version]['hidden']}")
|
739
721
|
self.conn.image.update_image(
|
740
722
|
cloud_image.id, **{"os_hidden": versions[version]["hidden"]}
|
741
723
|
)
|
@@ -757,24 +739,23 @@ class ImageManager:
|
|
757
739
|
)
|
758
740
|
modify_date = modify_date.replace("-", "")
|
759
741
|
|
760
|
-
logger.info("Setting internal_version =
|
742
|
+
logger.info(f"Setting internal_version = {modify_date}")
|
761
743
|
image["meta"]["internal_version"] = modify_date
|
762
744
|
except Exception:
|
763
745
|
logger.error(
|
764
|
-
"Error when retrieving the modification date of image '
|
765
|
-
image["name"],
|
746
|
+
f"Error when retrieving the modification date of image '{image['name']}'"
|
766
747
|
)
|
767
|
-
logger.info("Setting internal_version =
|
748
|
+
logger.info(f"Setting internal_version = {version}")
|
768
749
|
image["meta"]["internal_version"] = version
|
769
750
|
else:
|
770
|
-
logger.info("Setting internal_version =
|
751
|
+
logger.info(f"Setting internal_version = {version}")
|
771
752
|
image["meta"]["internal_version"] = version
|
772
753
|
|
773
|
-
logger.info("Setting image_original_user =
|
754
|
+
logger.info(f"Setting image_original_user = {image['login']}")
|
774
755
|
image["meta"]["image_original_user"] = image["login"]
|
775
756
|
|
776
757
|
if self.CONF.hypervisor:
|
777
|
-
logger.info("Setting hypervisor type =
|
758
|
+
logger.info(f"Setting hypervisor type = {self.CONF.hypervisor}")
|
778
759
|
image["meta"]["hypervisor_type"] = self.CONF.hypervisor
|
779
760
|
|
780
761
|
if version == "latest" and upstream_checksum:
|
@@ -787,12 +768,12 @@ class ImageManager:
|
|
787
768
|
|
788
769
|
for tag in image["tags"]:
|
789
770
|
if tag not in cloud_image.tags:
|
790
|
-
logger.info("Adding tag
|
771
|
+
logger.info(f"Adding tag {tag}")
|
791
772
|
self.conn.image.add_tag(cloud_image.id, tag)
|
792
773
|
|
793
774
|
for tag in cloud_image.tags:
|
794
775
|
if tag not in image["tags"]:
|
795
|
-
logger.info("Deleting tag
|
776
|
+
logger.info(f"Deleting tag {tag}")
|
796
777
|
self.conn.image.remove_tag(cloud_image.id, tag)
|
797
778
|
|
798
779
|
if "meta" in versions[version]:
|
@@ -804,8 +785,7 @@ class ImageManager:
|
|
804
785
|
if property in image["meta"]:
|
805
786
|
if image["meta"][property] != properties[property]:
|
806
787
|
logger.info(
|
807
|
-
"Setting property
|
808
|
-
% (property, properties[property], image["meta"][property])
|
788
|
+
f"Setting property {property}: {properties[property]} != {image['meta'][property]}"
|
809
789
|
)
|
810
790
|
self.conn.image.update_image(
|
811
791
|
cloud_image.id, **{property: str(image["meta"][property])}
|
@@ -817,37 +797,37 @@ class ImageManager:
|
|
817
797
|
"stores",
|
818
798
|
] or not property.startswith("os_"):
|
819
799
|
# FIXME: handle deletion of properties
|
820
|
-
logger.debug("Deleting property
|
800
|
+
logger.debug(f"Deleting property {property}")
|
821
801
|
|
822
802
|
for property in image["meta"]:
|
823
803
|
if property not in properties:
|
824
804
|
logger.info(
|
825
|
-
"Setting property
|
805
|
+
f"Setting property {property}: {image['meta'][property]}"
|
826
806
|
)
|
827
807
|
self.conn.image.update_image(
|
828
808
|
cloud_image.id, **{property: str(image["meta"][property])}
|
829
809
|
)
|
830
810
|
|
831
|
-
logger.info("Checking status of '
|
811
|
+
logger.info(f"Checking status of '{name}'")
|
832
812
|
if (
|
833
813
|
cloud_image.status != image["status"]
|
834
814
|
and image["status"] == "deactivated"
|
835
815
|
):
|
836
|
-
logger.info("Deactivating image '
|
816
|
+
logger.info(f"Deactivating image '{name}'")
|
837
817
|
self.conn.image.deactivate_image(cloud_image.id)
|
838
818
|
|
839
819
|
elif cloud_image.status != image["status"] and image["status"] == "active":
|
840
|
-
logger.info("Reactivating image '
|
820
|
+
logger.info(f"Reactivating image '{name}'")
|
841
821
|
self.conn.image.reactivate_image(cloud_image.id)
|
842
822
|
|
843
|
-
logger.info("Checking visibility of '
|
823
|
+
logger.info(f"Checking visibility of '{name}'")
|
844
824
|
if "visibility" in versions[version]:
|
845
825
|
visibility = versions[version]["visibility"]
|
846
826
|
else:
|
847
827
|
visibility = image["visibility"]
|
848
828
|
|
849
829
|
if cloud_image.visibility != visibility:
|
850
|
-
logger.info("Setting visibility of '
|
830
|
+
logger.info(f"Setting visibility of '{name}' to '{visibility}'")
|
851
831
|
self.conn.image.update_image(cloud_image.id, visibility=visibility)
|
852
832
|
|
853
833
|
def rename_images(
|
@@ -869,17 +849,17 @@ class ImageManager:
|
|
869
849
|
cloud_images = self.get_images()
|
870
850
|
|
871
851
|
if len(sorted_versions) > 1:
|
872
|
-
latest = "
|
873
|
-
previous_latest = "
|
852
|
+
latest = f"{name} ({sorted_versions[-1]})"
|
853
|
+
previous_latest = f"{name} ({sorted_versions[-2]})"
|
874
854
|
|
875
855
|
if name in cloud_images and previous_latest not in cloud_images:
|
876
|
-
logger.info("Renaming
|
856
|
+
logger.info(f"Renaming {name} to {previous_latest}")
|
877
857
|
self.conn.image.update_image(
|
878
858
|
cloud_images[name].id, name=previous_latest
|
879
859
|
)
|
880
860
|
|
881
861
|
if latest in cloud_images:
|
882
|
-
logger.info("Renaming
|
862
|
+
logger.info(f"Renaming {latest} to {name}")
|
883
863
|
self.conn.image.update_image(cloud_images[latest].id, name=name)
|
884
864
|
|
885
865
|
elif len(sorted_versions) == 1 and name in cloud_images:
|
@@ -892,34 +872,30 @@ class ImageManager:
|
|
892
872
|
)
|
893
873
|
create_date = create_date.replace("-", "")
|
894
874
|
|
895
|
-
previous_latest = "
|
875
|
+
previous_latest = f"{name} ({create_date})"
|
896
876
|
|
897
877
|
logger.info(
|
898
|
-
"Setting internal_version:
|
899
|
-
% (create_date, previous_latest)
|
878
|
+
f"Setting internal_version: {create_date} for {previous_latest}"
|
900
879
|
)
|
901
880
|
self.conn.image.update_image(
|
902
881
|
previous_image.id, **{"internal_version": create_date}
|
903
882
|
)
|
904
883
|
else:
|
905
|
-
previous_latest =
|
906
|
-
name
|
907
|
-
previous_image["properties"]["internal_version"],
|
884
|
+
previous_latest = (
|
885
|
+
f"{name} ({previous_image['properties']['internal_version']})"
|
908
886
|
)
|
909
887
|
|
910
|
-
logger.info("Renaming old latest '
|
888
|
+
logger.info(f"Renaming old latest '{name}' to '{previous_latest}'")
|
911
889
|
self.conn.image.update_image(previous_image.id, name=previous_latest)
|
912
890
|
|
913
|
-
logger.info(
|
914
|
-
"Renaming imported image '%s' to '%s'" % (imported_image.name, name)
|
915
|
-
)
|
891
|
+
logger.info(f"Renaming imported image '{imported_image.name}' to '{name}'")
|
916
892
|
self.conn.image.update_image(imported_image.id, name=name)
|
917
893
|
|
918
894
|
elif len(sorted_versions) == 1:
|
919
|
-
latest = "
|
895
|
+
latest = f"{name} ({sorted_versions[-1]})"
|
920
896
|
|
921
897
|
if latest in cloud_images:
|
922
|
-
logger.info("Renaming
|
898
|
+
logger.info(f"Renaming {latest} to {name}")
|
923
899
|
self.conn.image.update_image(cloud_images[latest].id, name=name)
|
924
900
|
|
925
901
|
def check_image_age(self) -> set:
|
@@ -1065,10 +1041,10 @@ class ImageManager:
|
|
1065
1041
|
and not self.CONF.dry_run
|
1066
1042
|
):
|
1067
1043
|
try:
|
1068
|
-
logger.info("Deactivating image '
|
1044
|
+
logger.info(f"Deactivating image '{image}'")
|
1069
1045
|
self.conn.image.deactivate_image(cloud_image.id)
|
1070
1046
|
|
1071
|
-
logger.info("Setting visibility of '
|
1047
|
+
logger.info(f"Setting visibility of '{image}' to 'community'")
|
1072
1048
|
self.conn.image.update_image(
|
1073
1049
|
cloud_image.id, visibility="community"
|
1074
1050
|
)
|
@@ -1077,25 +1053,24 @@ class ImageManager:
|
|
1077
1053
|
"keep" not in image_definition
|
1078
1054
|
or not image_definition["keep"]
|
1079
1055
|
):
|
1080
|
-
logger.info("Deleting
|
1056
|
+
logger.info(f"Deleting {image}")
|
1081
1057
|
self.conn.image.delete_image(cloud_image.id)
|
1082
1058
|
else:
|
1083
1059
|
logger.info(
|
1084
|
-
"Image '
|
1085
|
-
% image
|
1060
|
+
f"Image '{image}' will not be deleted, because 'keep' flag is True"
|
1086
1061
|
)
|
1087
1062
|
except Exception as e:
|
1088
1063
|
logger.info(
|
1089
|
-
"
|
1064
|
+
f"{image} is still in use and cannot be deleted\n {e}"
|
1090
1065
|
)
|
1091
1066
|
|
1092
1067
|
else:
|
1093
1068
|
logger.warning(
|
1094
|
-
"Image
|
1069
|
+
f"Image {image} should be deleted, but deletion is disabled"
|
1095
1070
|
)
|
1096
1071
|
try:
|
1097
1072
|
if self.CONF.deactivate and not self.CONF.dry_run:
|
1098
|
-
logger.info("Deactivating image '
|
1073
|
+
logger.info(f"Deactivating image '{image}'")
|
1099
1074
|
self.conn.image.deactivate_image(cloud_image.id)
|
1100
1075
|
|
1101
1076
|
if (
|
@@ -1104,13 +1079,13 @@ class ImageManager:
|
|
1104
1079
|
and cloud_image.visibility != "community"
|
1105
1080
|
):
|
1106
1081
|
logger.info(
|
1107
|
-
"Setting visibility of '
|
1082
|
+
f"Setting visibility of '{image}' to 'community'"
|
1108
1083
|
)
|
1109
1084
|
self.conn.image.update_image(
|
1110
1085
|
cloud_image.id, visibility="community"
|
1111
1086
|
)
|
1112
1087
|
except Exception as e:
|
1113
|
-
logger.error("An Exception occurred: \n
|
1088
|
+
logger.error(f"An Exception occurred: \n{e}")
|
1114
1089
|
self.exit_with_error = True
|
1115
1090
|
elif counter[image_name] <= last:
|
1116
1091
|
logger.info(
|
@@ -1121,12 +1096,12 @@ class ImageManager:
|
|
1121
1096
|
and not self.CONF.dry_run
|
1122
1097
|
and cloud_image.visibility != "community"
|
1123
1098
|
):
|
1124
|
-
logger.info("Setting visibility of '
|
1099
|
+
logger.info(f"Setting visibility of '{image}' to 'community'")
|
1125
1100
|
self.conn.image.update_image(cloud_image.id, visibility="community")
|
1126
1101
|
elif (
|
1127
1102
|
counter[image_name] < last and self.CONF.hide and not self.CONF.dry_run
|
1128
1103
|
):
|
1129
|
-
logger.info("Setting visibility of '
|
1104
|
+
logger.info(f"Setting visibility of '{image}' to 'community'")
|
1130
1105
|
self.conn.image.update_image(cloud_image.id, visibility="community")
|
1131
1106
|
return unmanaged_images
|
1132
1107
|
|
@@ -1142,39 +1117,32 @@ class ImageManager:
|
|
1142
1117
|
self.exit_with_error = True
|
1143
1118
|
for result in e.results:
|
1144
1119
|
logger.error(
|
1145
|
-
"Error validating data '
|
1146
|
-
% (result.data, result.schema)
|
1120
|
+
f"Error validating data '{result.data}' with '{result.schema}'"
|
1147
1121
|
)
|
1148
1122
|
for error in result.errors:
|
1149
|
-
logger.error("\t
|
1123
|
+
logger.error(f"\t{error}")
|
1150
1124
|
else:
|
1151
|
-
logger.debug("Image file
|
1125
|
+
logger.debug(f"Image file {file} is valid")
|
1152
1126
|
except FileNotFoundError:
|
1153
|
-
logger.error("Invalid path '
|
1127
|
+
logger.error(f"Invalid path '{self.CONF.images}'")
|
1154
1128
|
|
1155
1129
|
def share_image_with_project(self, image, project):
|
1156
1130
|
member = self.conn.image.find_member(project.id, image.id)
|
1157
1131
|
|
1158
1132
|
if not member:
|
1159
|
-
logger.info(
|
1160
|
-
"add - %s - %s (%s)" % (image.name, project.name, project.domain_id)
|
1161
|
-
)
|
1133
|
+
logger.info(f"add - {image.name} - {project.name} ({project.domain_id})")
|
1162
1134
|
if not self.CONF.dry_run:
|
1163
1135
|
member = self.conn.image.add_member(image.id, member_id=project.id)
|
1164
1136
|
|
1165
1137
|
if not self.CONF.dry_run and member.status != "accepted":
|
1166
|
-
logger.info(
|
1167
|
-
"accept - %s - %s (%s)" % (image.name, project.name, project.domain_id)
|
1168
|
-
)
|
1138
|
+
logger.info(f"accept - {image.name} - {project.name} ({project.domain_id})")
|
1169
1139
|
self.conn.image.update_member(member, image.id, status="accepted")
|
1170
1140
|
|
1171
1141
|
def unshare_image_with_project(self, image, project):
|
1172
1142
|
member = self.conn.image.find_member(project.id, image.id)
|
1173
1143
|
|
1174
1144
|
if member:
|
1175
|
-
logger.info(
|
1176
|
-
"del - %s - %s (%s)" % (image.name, project.name, project.domain_id)
|
1177
|
-
)
|
1145
|
+
logger.info(f"del - {image.name} - {project.name} ({project.domain_id})")
|
1178
1146
|
if not self.CONF.dry_run:
|
1179
1147
|
self.conn.image.remove_member(member, image.id)
|
1180
1148
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: openstack-image-manager
|
3
|
-
Version: 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.
|
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,,
|
@@ -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()
|
openstack_image_manager/table.py
DELETED
@@ -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,343 +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("'%s' available in '%s'" % (new_filename, dirname))
|
153
|
-
except S3Error:
|
154
|
-
logger.info("'%s' not yet available in '%s'" % (new_filename, dirname))
|
155
|
-
logger.info("Downloading '%s'" % 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("Decompressing '%s'" % 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(
|
168
|
-
"Uploading '%s' to '%s' as '%s'" % (filename, dirname, new_filename)
|
169
|
-
)
|
170
|
-
|
171
|
-
client.fput_object(minio_bucket, os.path.join(dirname, new_filename), filename)
|
172
|
-
os.remove(filename)
|
173
|
-
|
174
|
-
|
175
|
-
def update_image(
|
176
|
-
image,
|
177
|
-
getter,
|
178
|
-
minio_server,
|
179
|
-
minio_bucket,
|
180
|
-
minio_access_key,
|
181
|
-
minio_secret_key,
|
182
|
-
dry_run=False,
|
183
|
-
swift_prefix="",
|
184
|
-
):
|
185
|
-
name = image["name"]
|
186
|
-
logger.info(f"Checking image {name}")
|
187
|
-
|
188
|
-
latest_url = image["latest_url"]
|
189
|
-
logger.info(f"Latest download URL is {latest_url}")
|
190
|
-
|
191
|
-
latest_checksum_url = image["latest_checksum_url"]
|
192
|
-
logger.info(f"Getting checksums from {latest_checksum_url}")
|
193
|
-
|
194
|
-
shortname = image["shortname"]
|
195
|
-
current_checksum, current_url, current_version = getter(
|
196
|
-
shortname, latest_checksum_url, latest_url
|
197
|
-
)
|
198
|
-
|
199
|
-
logger.info(
|
200
|
-
f"Checksum of current {current_url.rsplit('/', 1)[-1]} is {current_checksum}"
|
201
|
-
)
|
202
|
-
|
203
|
-
if not image["versions"]:
|
204
|
-
logger.info("No image available so far")
|
205
|
-
image["versions"].append(
|
206
|
-
{
|
207
|
-
"build_date": None,
|
208
|
-
"checksum": None,
|
209
|
-
"url": None,
|
210
|
-
"version": None,
|
211
|
-
}
|
212
|
-
)
|
213
|
-
|
214
|
-
latest_checksum = image["versions"][0]["checksum"]
|
215
|
-
logger.info(f"Our checksum is {latest_checksum}")
|
216
|
-
|
217
|
-
if latest_checksum == current_checksum:
|
218
|
-
logger.info(f"Image {name} is up-to-date, nothing to do")
|
219
|
-
return 0
|
220
|
-
|
221
|
-
if current_version is None:
|
222
|
-
logger.info(f"Checking {current_url}")
|
223
|
-
|
224
|
-
conn = urlopen(current_url, timeout=30)
|
225
|
-
dt = datetime.strptime(
|
226
|
-
conn.headers["last-modified"], "%a, %d %b %Y %H:%M:%S %Z"
|
227
|
-
)
|
228
|
-
current_version = dt.strftime("%Y%m%d")
|
229
|
-
|
230
|
-
new_values = {
|
231
|
-
"version": current_version,
|
232
|
-
"build_date": datetime.strptime(current_version, "%Y%m%d").date(),
|
233
|
-
"checksum": current_checksum,
|
234
|
-
"url": current_url,
|
235
|
-
}
|
236
|
-
logger.info(f"New values are {new_values}")
|
237
|
-
image["versions"][0].update(new_values)
|
238
|
-
|
239
|
-
shortname = image["shortname"]
|
240
|
-
format = image["format"]
|
241
|
-
|
242
|
-
minio_server = str(minio_server)
|
243
|
-
minio_bucket = str(minio_bucket)
|
244
|
-
mirror_url = f"https://{minio_server}/{swift_prefix}{minio_bucket}/{shortname}/{current_version}-{shortname}.{format}" # noqa E501
|
245
|
-
logger.info(f"New URL is {mirror_url}")
|
246
|
-
|
247
|
-
# If `mirror_url` is given, the manage.py script will
|
248
|
-
# use `mirror_url` for the download and will use `url`
|
249
|
-
# to set the `image_source` property. This way we keep
|
250
|
-
# track of the original source of the image.
|
251
|
-
|
252
|
-
image["versions"][0]["mirror_url"] = mirror_url
|
253
|
-
|
254
|
-
# We use `current_url` here and not `latest_url` to keep track
|
255
|
-
# of the original source of the image. Even if we know that `current_url`
|
256
|
-
# will not be available in the future. The `latest_url` will always
|
257
|
-
# be part of the image definition itself.
|
258
|
-
|
259
|
-
image["versions"][0]["url"] = current_url
|
260
|
-
|
261
|
-
if dry_run:
|
262
|
-
logger.info(f"Not mirroring {mirror_url}, dry-run enabled")
|
263
|
-
else:
|
264
|
-
mirror_image(
|
265
|
-
image,
|
266
|
-
current_url,
|
267
|
-
minio_server,
|
268
|
-
minio_bucket,
|
269
|
-
minio_access_key,
|
270
|
-
minio_secret_key,
|
271
|
-
)
|
272
|
-
return 1
|
273
|
-
|
274
|
-
|
275
|
-
@app.command()
|
276
|
-
def main(
|
277
|
-
debug: bool = typer.Option(False, "--debug", help="Enable debug logging"),
|
278
|
-
dry_run: bool = typer.Option(False, "--dry-run", help="Do not perform any changes"),
|
279
|
-
minio_access_key: str = typer.Option(
|
280
|
-
None, help="Minio access key", envvar="MINIO_ACCESS_KEY"
|
281
|
-
),
|
282
|
-
minio_secret_key: str = typer.Option(
|
283
|
-
None, help="Minio secret key", envvar="MINIO_SECRET_KEY"
|
284
|
-
),
|
285
|
-
minio_server: str = typer.Option(
|
286
|
-
"swift.services.a.regiocloud.tech", help="Minio server", envvar="MINIO_SERVER"
|
287
|
-
),
|
288
|
-
minio_bucket: str = typer.Option(
|
289
|
-
"openstack-images", help="Minio bucket", envvar="MINIO_BUCKET"
|
290
|
-
),
|
291
|
-
swift_prefix: str = typer.Option(
|
292
|
-
"swift/v1/AUTH_b182637428444b9aa302bb8d5a5a418c/",
|
293
|
-
help="Swift prefix",
|
294
|
-
envvar="SWIFT_PREFIX",
|
295
|
-
),
|
296
|
-
):
|
297
|
-
if debug:
|
298
|
-
level = "DEBUG"
|
299
|
-
else:
|
300
|
-
level = "INFO"
|
301
|
-
|
302
|
-
logger.remove() # remove the default sink
|
303
|
-
log_fmt = (
|
304
|
-
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | "
|
305
|
-
"<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"
|
306
|
-
)
|
307
|
-
logger.add(sys.stderr, format=log_fmt, level=level, colorize=True)
|
308
|
-
|
309
|
-
for image, getter in IMAGES.items():
|
310
|
-
p = f"etc/images/{image}.yml"
|
311
|
-
logger.info(f"Processing file {p}")
|
312
|
-
|
313
|
-
ryaml = ruamel.yaml.YAML()
|
314
|
-
with open(p) as fp:
|
315
|
-
data = ryaml.load(fp)
|
316
|
-
|
317
|
-
updates = 0
|
318
|
-
for index, image in enumerate(data["images"]):
|
319
|
-
if "latest_url" not in image:
|
320
|
-
continue
|
321
|
-
|
322
|
-
updates += update_image(
|
323
|
-
image,
|
324
|
-
getter,
|
325
|
-
minio_server,
|
326
|
-
minio_bucket,
|
327
|
-
minio_access_key,
|
328
|
-
minio_secret_key,
|
329
|
-
dry_run,
|
330
|
-
swift_prefix,
|
331
|
-
)
|
332
|
-
|
333
|
-
if not updates:
|
334
|
-
continue
|
335
|
-
|
336
|
-
with open(p, "w+") as fp:
|
337
|
-
ryaml.explicit_start = True
|
338
|
-
ryaml.indent(sequence=4, offset=2)
|
339
|
-
ryaml.dump(data, fp)
|
340
|
-
|
341
|
-
|
342
|
-
if __name__ == "__main__":
|
343
|
-
app()
|
@@ -1,11 +0,0 @@
|
|
1
|
-
openstack_image_manager/__init__.py,sha256=z6lQHDMfCV8IkUz5pM1QYfQ37O2Rdy82jYovN8N9DIU,240
|
2
|
-
openstack_image_manager/manage.py,sha256=zEspJ8vKHg3xT2trKOmf1fCUsIMlJFxXU1BeOt-At-E,47728
|
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=kSEG3HJPAtazIy9S2uEuLOZq4VDOpZifeO8OngClcK0,11103
|
6
|
-
openstack_image_manager-0.20240327.0.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
7
|
-
openstack_image_manager-0.20240327.0.dist-info/METADATA,sha256=zg24GictICQvJaQm_BuP6llALIY4s-8ri_kW03Pb6cU,14871
|
8
|
-
openstack_image_manager-0.20240327.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
9
|
-
openstack_image_manager-0.20240327.0.dist-info/entry_points.txt,sha256=AEHPHHHZ3jAZfpvaI5ZzLi3DHb9vGQwL7TJcw_G_5nc,80
|
10
|
-
openstack_image_manager-0.20240327.0.dist-info/top_level.txt,sha256=iLfREddId51T97Dr9IGRQtJXKJgVy1PB6uHCaQk1j44,24
|
11
|
-
openstack_image_manager-0.20240327.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|