Commit affc3f4e authored by Pantelis Malekas's avatar Pantelis Malekas
Browse files

feat: add update ci digest script

parent 613fc924
Loading
Loading
Loading
Loading
Loading
+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