blob: fbc8f2ca2df560b876e6ab96308f3f744f50e067 [file] [log] [blame]
Mohammed Naser8613c862023-04-24 17:26:51 -04001#!/usr/bin/env python3
2
3import argparse
4import functools
5
Mohammed Naser12207172024-02-05 18:49:35 -05006import requests
Mohammed Naser8613c862023-04-24 17:26:51 -04007from docker_image import reference
8from oslo_config import cfg
9from oslo_log import log as logging
10from ruyaml import YAML
Mohammed Naser8613c862023-04-24 17:26:51 -040011
12LOG = logging.getLogger(__name__)
13CONF = cfg.CONF
14
Oleksandr Kd4e5b232024-01-18 17:20:16 +010015SKIP_IMAGE_LIST = ["secretgen_controller"]
Mohammed Naser8613c862023-04-24 17:26:51 -040016
Mohammed Naser891a2902024-01-23 09:47:47 -050017
Mohammed Naser21066a12024-01-02 11:14:53 -050018def get_digest(image_ref, token=None):
vexxhost-botf5ee7992024-06-19 00:13:44 +020019 url = f"https://{image_ref.domain()}/v2/{image_ref.path()}/manifests/{image_ref['tag']}"
20
Mohammed Naser21066a12024-01-02 11:14:53 -050021 headers = {}
22 if token:
23 headers["Authorization"] = f"Bearer {token}"
vexxhost-botf5ee7992024-06-19 00:13:44 +020024 else:
Yaguang Tang6db2e112025-01-17 20:14:29 +080025 r = requests.get(url, timeout=5, verify=False)
vexxhost-botf5ee7992024-06-19 00:13:44 +020026 auth_header = r.headers.get("Www-Authenticate")
27 if auth_header:
28 realm = auth_header.split(",")[0].split("=")[1].strip('"')
29
30 r = requests.get(
31 realm,
32 timeout=5,
Yaguang Tang6db2e112025-01-17 20:14:29 +080033 verify=False,
vexxhost-botf5ee7992024-06-19 00:13:44 +020034 params={"scope": f"repository:{image_ref.path()}:pull"},
vexxhost-botf5ee7992024-06-19 00:13:44 +020035 )
36 r.raise_for_status()
37
38 headers["Authorization"] = f"Bearer {r.json()['token']}"
Mohammed Naser21066a12024-01-02 11:14:53 -050039
40 try:
41 headers["Accept"] = "application/vnd.docker.distribution.manifest.v2+json"
42
43 r = requests.get(
44 f"https://{image_ref.domain()}/v2/{image_ref.path()}/manifests/{image_ref['tag']}",
45 timeout=5,
Yaguang Tang6db2e112025-01-17 20:14:29 +080046 verify=False,
Mohammed Naser21066a12024-01-02 11:14:53 -050047 headers=headers,
48 )
49 r.raise_for_status()
50 return r.headers["Docker-Content-Digest"]
51 except requests.exceptions.HTTPError:
52 headers["Accept"] = "application/vnd.oci.image.index.v1+json"
53
54 r = requests.get(
55 f"https://{image_ref.domain()}/v2/{image_ref.path()}/manifests/{image_ref['tag']}",
56 timeout=5,
Yaguang Tang6db2e112025-01-17 20:14:29 +080057 verify=False,
Mohammed Naser21066a12024-01-02 11:14:53 -050058 headers=headers,
59 )
60 r.raise_for_status()
61 return r.headers["Docker-Content-Digest"]
62
63
Mohammed Naser8613c862023-04-24 17:26:51 -040064@functools.cache
65def get_pinned_image(image_src):
66 image_ref = reference.Reference.parse(image_src)
Mohammed Naser859988e2025-01-19 00:04:34 -050067 if image_ref.domain() != "harbor.atmosphere.dev":
Yaguang Tang6db2e112025-01-17 20:14:29 +080068 try:
69 image_ref = reference.Reference.parse("harbor.atmosphere.dev/" + image_src)
70 except Exception:
71 LOG.warn(f"failed to parse image path {image_src}")
Mohammed Naser8613c862023-04-24 17:26:51 -040072
Yaguang Tang0953b612024-12-13 04:14:34 +080073 if (
74 image_ref.domain() == "registry.atmosphere.dev"
75 or image_ref.domain() == "harbor.atmosphere.dev"
76 ):
Mohammed Naser1dfea6b2024-02-09 01:04:26 -050077 # Get token for docker.io
78 r = requests.get(
Yaguang Tang0953b612024-12-13 04:14:34 +080079 "https://harbor.atmosphere.dev/service/token",
Mohammed Naser1dfea6b2024-02-09 01:04:26 -050080 timeout=5,
81 params={
82 "service": "harbor-registry",
83 "scope": f"repository:{image_ref.path()}:pull",
84 },
85 )
86 r.raise_for_status()
87 token = r.json()["token"]
88
89 digest = get_digest(image_ref, token=token)
vexxhost-botf5ee7992024-06-19 00:13:44 +020090 elif image_ref.domain() == "quay.io":
Mohammed Naser8613c862023-04-24 17:26:51 -040091 r = requests.get(
92 f"https://quay.io/api/v1/repository/{image_ref.path()}/tag/",
Mohammed Naser21066a12024-01-02 11:14:53 -050093 timeout=5,
Mohammed Naser8613c862023-04-24 17:26:51 -040094 params={"specificTag": image_ref["tag"]},
95 )
96 r.raise_for_status()
97 digest = r.json()["tags"][0]["manifest_digest"]
vexxhost-botf5ee7992024-06-19 00:13:44 +020098 elif image_ref.domain() == "docker.io":
Mohammed Naser49e66372023-07-10 14:57:00 -040099 # Get token for docker.io
100 r = requests.get(
101 "https://auth.docker.io/token",
Mohammed Naser21066a12024-01-02 11:14:53 -0500102 timeout=5,
Mohammed Naser16baaab2023-07-10 15:07:11 -0400103 params={
104 "service": "registry.docker.io",
105 "scope": f"repository:{image_ref.path()}:pull",
106 },
Mohammed Naser49e66372023-07-10 14:57:00 -0400107 )
108 r.raise_for_status()
109 token = r.json()["token"]
110
111 r = requests.get(
112 f"https://registry-1.docker.io/v2/{image_ref.path()}/manifests/{image_ref['tag']}",
Mohammed Naser21066a12024-01-02 11:14:53 -0500113 timeout=5,
Mohammed Naser16baaab2023-07-10 15:07:11 -0400114 headers={
115 "Accept": "application/vnd.docker.distribution.manifest.v2+json",
116 "Authorization": f"Bearer {token}",
117 },
Mohammed Naser49e66372023-07-10 14:57:00 -0400118 )
119 r.raise_for_status()
120 digest = r.headers["Docker-Content-Digest"]
vexxhost-botf5ee7992024-06-19 00:13:44 +0200121 elif image_ref.domain() == "ghcr.io":
Mohammed Naser21066a12024-01-02 11:14:53 -0500122 # Get token for docker.io
123 r = requests.get(
124 "https://ghcr.io/token",
125 timeout=5,
126 params={
127 "service": "ghcr.io",
128 "scope": f"repository:{image_ref.path()}:pull",
129 },
130 )
131 r.raise_for_status()
132 token = r.json()["token"]
133
134 digest = get_digest(image_ref, token=token)
vexxhost-botf5ee7992024-06-19 00:13:44 +0200135 else:
136 digest = get_digest(image_ref)
Mohammed Naser21066a12024-01-02 11:14:53 -0500137
Mohammed Naser859988e2025-01-19 00:04:34 -0500138 original_ref = reference.Reference.parse(image_src)
139 return (
140 f"{original_ref.domain()}/{original_ref.path()}:{original_ref['tag']}@{digest}"
141 )
Mohammed Naser8613c862023-04-24 17:26:51 -0400142
143
144def main():
145 logging.register_options(CONF)
146 logging.setup(CONF, "atmosphere-bump-images")
147
148 parser = argparse.ArgumentParser("bump-images")
149 parser.add_argument(
150 "src", help="Path for default values file", type=argparse.FileType("r")
151 )
Mohammed Nasereb257cb2024-04-10 21:14:24 -0400152 parser.add_argument(
153 "dst", help="Path for output file", type=argparse.FileType("r+")
154 )
Mohammed Naser8613c862023-04-24 17:26:51 -0400155
156 args = parser.parse_args()
157
158 yaml = YAML(typ="rt")
159 data = yaml.load(args.src)
160
Mohammed Naser21066a12024-01-02 11:14:53 -0500161 for image in data["_atmosphere_images"]:
Oleksandr Kd4e5b232024-01-18 17:20:16 +0100162 if image in SKIP_IMAGE_LIST:
163 continue
Mohammed Naserfdd5cee2024-02-07 23:45:52 -0500164
Jason Hall222923e2024-02-19 12:56:23 -0600165 image_src = (
166 data["_atmosphere_images"][image]
167 .replace("{{ atmosphere_release }}", data["atmosphere_release"])
168 .replace("{{ atmosphere_image_prefix }}", "")
Mohammed Naser12207172024-02-05 18:49:35 -0500169 )
Mohammed Naser859988e2025-01-19 00:04:34 -0500170 pinned_image = get_pinned_image(image_src).replace(
171 "harbor.atmosphere.dev", "registry.atmosphere.dev"
172 )
Mohammed Naser8613c862023-04-24 17:26:51 -0400173
174 LOG.info("Pinning image %s from %s to %s", image, image_src, pinned_image)
Jason Hall222923e2024-02-19 12:56:23 -0600175 data["_atmosphere_images"][image] = "{{ atmosphere_image_prefix }}%s" % (
176 pinned_image,
177 )
Mohammed Naser8613c862023-04-24 17:26:51 -0400178
179 yaml.dump(data, args.dst)
180
181
182if __name__ == "__main__":
183 main()