Loading scripts/update_cd_image_digests.py 0 → 100644 +115 −0 Changes for scripts/update_cd_image_digests.py: 115 added lines, 0 removed lines. Original line number Diff line number Diff line #!/usr/bin/env python3 import re import sys from pathlib import Path from urllib.error import HTTPError, URLError from urllib.parse import quote from urllib.request import Request, urlopen import yaml REPOSITORIES = { "tmf-api": (470, 3021), "sonata": (479, 3031), "oss-client": (474, 3023), "pkg-manager": (478, 3033), "portal-cart": (465, 3041), "telemetry-client": (475, 3028), "peering-api": (468, 3014), "registry-api": (469, 3018), "fabric-api": (467, 3011), "portal": (466, 3038), "service-monitor": (480, 3119), } REGISTRY_API = "https://labs.etsi.org/rep/api/v4/projects" DEFAULT_VALUES = Path(__file__).resolve().parent.parent / "charts/hypo/values-cd.yaml" def get_digest(token, project_id, repository_id, tag): url = ( f"{REGISTRY_API}/{project_id}" f"/registry/repositories/{repository_id}/tags/{quote(tag, safe='')}" ) request = Request(url, headers={"PRIVATE-TOKEN": token}) try: with urlopen(request, timeout=30) as response: import json data = json.load(response) except (HTTPError, URLError, TimeoutError) as exc: raise RuntimeError(f"Failed to fetch digest for tag {tag}: {exc}") from exc digest = data.get("digest") if not isinstance(digest, str) or not re.fullmatch( r"sha256:[0-9a-fA-F]{64}", digest ): raise RuntimeError(f"Invalid or missing digest for tag {tag}") return digest def main(): if len(sys.argv) < 2: sys.exit( f"Usage: {Path(sys.argv[0]).name} " "<PRIVATE_TOKEN> [values-file]" ) token = sys.argv[1] values_file = Path(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_VALUES if not values_file.is_file(): sys.exit(f"ERROR: Values file not found: {values_file}") # Preserve key order when loading YAML. with values_file.open() as file: values = yaml.safe_load(file) if not isinstance(values, dict): sys.exit(f"ERROR: Invalid values YAML: {values_file}") changed = [] for service, (project_id, repository_id) in REPOSITORIES.items(): config = values.get(service) if not isinstance(config, dict): print(f"Skipping {service}: not present") continue image = config.get("image") if not isinstance(image, dict): print(f"Skipping {service}: no image configuration") continue tag = image.get("tag") if not isinstance(tag, str) or not tag: raise RuntimeError(f"{service}: missing image.tag") if tag == "latest": digest = get_digest(token, project_id, repository_id, tag) image["digest"] = digest print(f"{service}: latest -> {digest}") else: image.pop("digest", None) print(f"{service}: {tag} (no digest)") changed.append(service) # Write only after every digest lookup succeeds. # Note: PyYAML rewrites formatting/comments when dumping YAML. with values_file.open("w") as file: yaml.safe_dump(values, file, sort_keys=False, default_flow_style=False) print(f"\nUpdated {values_file}; processed {len(changed)} images.") if __name__ == "__main__": main() No newline at end of file Loading
scripts/update_cd_image_digests.py 0 → 100644 +115 −0 Changes for scripts/update_cd_image_digests.py: 115 added lines, 0 removed lines. Original line number Diff line number Diff line #!/usr/bin/env python3 import re import sys from pathlib import Path from urllib.error import HTTPError, URLError from urllib.parse import quote from urllib.request import Request, urlopen import yaml REPOSITORIES = { "tmf-api": (470, 3021), "sonata": (479, 3031), "oss-client": (474, 3023), "pkg-manager": (478, 3033), "portal-cart": (465, 3041), "telemetry-client": (475, 3028), "peering-api": (468, 3014), "registry-api": (469, 3018), "fabric-api": (467, 3011), "portal": (466, 3038), "service-monitor": (480, 3119), } REGISTRY_API = "https://labs.etsi.org/rep/api/v4/projects" DEFAULT_VALUES = Path(__file__).resolve().parent.parent / "charts/hypo/values-cd.yaml" def get_digest(token, project_id, repository_id, tag): url = ( f"{REGISTRY_API}/{project_id}" f"/registry/repositories/{repository_id}/tags/{quote(tag, safe='')}" ) request = Request(url, headers={"PRIVATE-TOKEN": token}) try: with urlopen(request, timeout=30) as response: import json data = json.load(response) except (HTTPError, URLError, TimeoutError) as exc: raise RuntimeError(f"Failed to fetch digest for tag {tag}: {exc}") from exc digest = data.get("digest") if not isinstance(digest, str) or not re.fullmatch( r"sha256:[0-9a-fA-F]{64}", digest ): raise RuntimeError(f"Invalid or missing digest for tag {tag}") return digest def main(): if len(sys.argv) < 2: sys.exit( f"Usage: {Path(sys.argv[0]).name} " "<PRIVATE_TOKEN> [values-file]" ) token = sys.argv[1] values_file = Path(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_VALUES if not values_file.is_file(): sys.exit(f"ERROR: Values file not found: {values_file}") # Preserve key order when loading YAML. with values_file.open() as file: values = yaml.safe_load(file) if not isinstance(values, dict): sys.exit(f"ERROR: Invalid values YAML: {values_file}") changed = [] for service, (project_id, repository_id) in REPOSITORIES.items(): config = values.get(service) if not isinstance(config, dict): print(f"Skipping {service}: not present") continue image = config.get("image") if not isinstance(image, dict): print(f"Skipping {service}: no image configuration") continue tag = image.get("tag") if not isinstance(tag, str) or not tag: raise RuntimeError(f"{service}: missing image.tag") if tag == "latest": digest = get_digest(token, project_id, repository_id, tag) image["digest"] = digest print(f"{service}: latest -> {digest}") else: image.pop("digest", None) print(f"{service}: {tag} (no digest)") changed.append(service) # Write only after every digest lookup succeeds. # Note: PyYAML rewrites formatting/comments when dumping YAML. with values_file.open("w") as file: yaml.safe_dump(values, file, sort_keys=False, default_flow_style=False) print(f"\nUpdated {values_file}; processed {len(changed)} images.") if __name__ == "__main__": main() No newline at end of file