|
2 | 2 | # Use of this source code is governed by a BSD-style
|
3 | 3 | # license that can be found in the LICENSE file.
|
4 | 4 |
|
5 |
| -import gzip |
6 |
| -import hashlib |
7 |
| -import urllib.parse |
8 |
| - |
9 |
| -from python_docker.registry import Image, Registry |
10 |
| -from sqlalchemy.orm import Session |
11 |
| -from traitlets import Callable, Dict, default |
| 5 | +from traitlets import Callable, Dict |
12 | 6 | from traitlets.config import LoggingConfigurable
|
13 | 7 |
|
14 |
| -from conda_store_server._internal import orm, schema, utils |
15 |
| - |
16 | 8 |
|
17 | 9 | class ContainerRegistry(LoggingConfigurable):
|
18 | 10 | container_registries = Dict(
|
19 | 11 | {},
|
20 |
| - help="Registries url to upload built container images with callable function to configure registry instance with credentials", |
| 12 | + help="(deprecated) Registries url to upload built container images with callable function to configure registry instance with credentials", |
21 | 13 | config=True,
|
22 | 14 | )
|
23 | 15 |
|
24 | 16 | container_registry_image_name = Callable(
|
25 |
| - help="Image name to assign to docker image pushed for particular registry", |
| 17 | + help="(deprecated) Image name to assign to docker image pushed for particular registry", |
26 | 18 | config=True,
|
27 | 19 | )
|
28 | 20 |
|
29 |
| - @default("container_registry_image_name") |
30 |
| - def _default_container_registry_image_name(self): |
31 |
| - def _container_registry_image_name(registry: Registry, build: orm.Build): |
32 |
| - return f"{registry.username}/{build.environment.namespace.name}-{build.environment.name}" |
33 |
| - |
34 |
| - return _container_registry_image_name |
35 |
| - |
36 | 21 | container_registry_image_tag = Callable(
|
37 |
| - help="Image name and tag to assign to docker image pushed for particular registry", |
| 22 | + help="(deprecated) Image name and tag to assign to docker image pushed for particular registry", |
38 | 23 | config=True,
|
39 | 24 | )
|
40 |
| - |
41 |
| - @default("container_registry_image_tag") |
42 |
| - def _default_container_registry_image_tag(self): |
43 |
| - def _container_registry_image_tag(registry: Registry, build: orm.Build): |
44 |
| - return build.key |
45 |
| - |
46 |
| - return _container_registry_image_tag |
47 |
| - |
48 |
| - def store_image(self, db: Session, conda_store, build: orm.Build, image: Image): |
49 |
| - self.log.info("storing container image locally") |
50 |
| - with utils.timer(self.log, "storing container image locally"): |
51 |
| - # https://docs.docker.com/registry/spec/manifest-v2-2/#example-image-manifest |
52 |
| - docker_manifest = schema.DockerManifest.construct() |
53 |
| - docker_config = schema.DockerConfig.construct( |
54 |
| - config=schema.DockerConfigConfig(), |
55 |
| - container_config=schema.DockerConfigConfig(), |
56 |
| - rootfs=schema.DockerConfigRootFS(), |
57 |
| - ) |
58 |
| - |
59 |
| - for layer in image.layers: |
60 |
| - # https://github.com/google/nixery/pull/64#issuecomment-541019077 |
61 |
| - # docker manifest expects compressed hash while configuration file |
62 |
| - # expects uncompressed hash -- good luck finding this detail in docs :) |
63 |
| - content_uncompressed_hash = hashlib.sha256(layer.content).hexdigest() |
64 |
| - content_compressed = gzip.compress(layer.content) |
65 |
| - content_compressed_hash = hashlib.sha256(content_compressed).hexdigest() |
66 |
| - conda_store.storage.set( |
67 |
| - db, |
68 |
| - build.id, |
69 |
| - build.docker_blob_key(content_compressed_hash), |
70 |
| - content_compressed, |
71 |
| - content_type="application/gzip", |
72 |
| - artifact_type=schema.BuildArtifactType.DOCKER_BLOB, |
73 |
| - ) |
74 |
| - |
75 |
| - docker_layer = schema.DockerManifestLayer( |
76 |
| - size=len(content_compressed), |
77 |
| - digest=f"sha256:{content_compressed_hash}", |
78 |
| - ) |
79 |
| - docker_manifest.layers.append(docker_layer) |
80 |
| - |
81 |
| - docker_config_history = schema.DockerConfigHistory() |
82 |
| - docker_config.history.append(docker_config_history) |
83 |
| - |
84 |
| - docker_config.rootfs.diff_ids.append( |
85 |
| - f"sha256:{content_uncompressed_hash}" |
86 |
| - ) |
87 |
| - |
88 |
| - docker_config_content = docker_config.json().encode("utf-8") |
89 |
| - docker_config_hash = hashlib.sha256(docker_config_content).hexdigest() |
90 |
| - docker_manifest.config = schema.DockerManifestConfig( |
91 |
| - size=len(docker_config_content), digest=f"sha256:{docker_config_hash}" |
92 |
| - ) |
93 |
| - docker_manifest_content = docker_manifest.json().encode("utf-8") |
94 |
| - docker_manifest_hash = hashlib.sha256(docker_manifest_content).hexdigest() |
95 |
| - |
96 |
| - conda_store.storage.set( |
97 |
| - db, |
98 |
| - build.id, |
99 |
| - build.docker_blob_key(docker_config_hash), |
100 |
| - docker_config_content, |
101 |
| - content_type="application/vnd.docker.container.image.v1+json", |
102 |
| - artifact_type=schema.BuildArtifactType.DOCKER_BLOB, |
103 |
| - ) |
104 |
| - |
105 |
| - # docker likes to have a sha256 key version of the manifest this |
106 |
| - # is sort of hack to avoid having to figure out which sha256 |
107 |
| - # refers to which manifest. |
108 |
| - conda_store.storage.set( |
109 |
| - db, |
110 |
| - build.id, |
111 |
| - f"docker/manifest/sha256:{docker_manifest_hash}", |
112 |
| - docker_manifest_content, |
113 |
| - content_type="application/vnd.docker.distribution.manifest.v2+json", |
114 |
| - artifact_type=schema.BuildArtifactType.DOCKER_BLOB, |
115 |
| - ) |
116 |
| - |
117 |
| - conda_store.storage.set( |
118 |
| - db, |
119 |
| - build.id, |
120 |
| - build.docker_manifest_key, |
121 |
| - docker_manifest_content, |
122 |
| - content_type="application/vnd.docker.distribution.manifest.v2+json", |
123 |
| - artifact_type=schema.BuildArtifactType.DOCKER_MANIFEST, |
124 |
| - ) |
125 |
| - |
126 |
| - conda_store.log.info( |
127 |
| - f"built docker image: {image.name}:{image.tag} layers={len(image.layers)}" |
128 |
| - ) |
129 |
| - |
130 |
| - @staticmethod |
131 |
| - def parse_image_uri(image_name: str): |
132 |
| - """Must be in fully specified format [<scheme>://]<registry_url>/<image_name>:<tag_name>""" |
133 |
| - if not image_name.startswith("http"): |
134 |
| - image_name = f"https://{image_name}" |
135 |
| - |
136 |
| - parsed_url = urllib.parse.urlparse(image_name) |
137 |
| - registry_url = f"{parsed_url.scheme}://{parsed_url.netloc}" |
138 |
| - image_name, tag_name = parsed_url.path.split(":", 1) |
139 |
| - image_name = image_name[1:] # remove beginning "/" |
140 |
| - return registry_url, image_name, tag_name |
141 |
| - |
142 |
| - def pull_image(self, image_name: str) -> Image: |
143 |
| - """Must be in fully specified format [<scheme>://]<registry_url>/<image_name>:<tag_name> |
144 |
| -
|
145 |
| - Docker is the only weird registry where you must use: |
146 |
| - - `https://registry-1.docker.io` |
147 |
| - """ |
148 |
| - registry_url, name, tag = self.parse_image_uri(image_name) |
149 |
| - |
150 |
| - for url in self.container_registries: |
151 |
| - if registry_url in url: |
152 |
| - registry = self.container_registries[registry_url](url) |
153 |
| - break |
154 |
| - else: |
155 |
| - self.log.warning( |
156 |
| - f"registry {registry_url} not configured using registry without authentication" |
157 |
| - ) |
158 |
| - registry = Registry(hostname=registry_url) |
159 |
| - |
160 |
| - return registry.pull_image(name, tag) |
161 |
| - |
162 |
| - def push_image(self, db, build, image: Image): |
163 |
| - for registry_url, configure_registry in self.container_registries.items(): |
164 |
| - self.log.info(f"beginning upload of image to registry {registry_url}") |
165 |
| - with utils.timer(self.log, f"uploading image to registry {registry_url}"): |
166 |
| - registry = configure_registry(registry_url) |
167 |
| - image.name = self.container_registry_image_name(registry, build) |
168 |
| - image.tag = self.container_registry_image_tag(registry, build) |
169 |
| - registry.push_image(image) |
170 |
| - |
171 |
| - registry_build_artifact = orm.BuildArtifact( |
172 |
| - build_id=build.id, |
173 |
| - artifact_type=schema.BuildArtifactType.CONTAINER_REGISTRY, |
174 |
| - key=f"{registry_url}/{image.name}:{image.tag}", |
175 |
| - ) |
176 |
| - db.add(registry_build_artifact) |
177 |
| - db.commit() |
178 |
| - |
179 |
| - def delete_image(self, image_name: str): |
180 |
| - registry_url, name, tag = self.parse_image_uri(image_name) |
181 |
| - |
182 |
| - for url in self.container_registries: |
183 |
| - if registry_url in url: |
184 |
| - registry = self.container_registries[registry_url](url) |
185 |
| - break |
186 |
| - else: |
187 |
| - self.log.warning( |
188 |
| - f"registry {registry_url} not configured using registry without authentication" |
189 |
| - ) |
190 |
| - registry = Registry(hostname=registry_url) |
191 |
| - |
192 |
| - self.log.info(f"deleting container image {image_name}") |
193 |
| - registry.delete_image(name, tag) |
0 commit comments