|
9 | 9 | from __future__ import annotations
|
10 | 10 | import json
|
11 | 11 | import requests
|
12 |
| -from typing import List, Dict, Any, \ |
13 |
| - Union, Type, Iterator |
14 |
| -from image.byteunit import ByteUnit |
15 |
| -from image.client import ContainerImageRegistryClient |
16 |
| -from image.config import ContainerImageConfig |
17 |
| -from image.errors import ContainerImageError |
18 |
| -from image.manifestfactory import ContainerImageManifestFactory |
19 |
| -from image.manifestlist import ContainerImageManifestList |
20 |
| -from image.oci import ContainerImageManifestOCI, \ |
21 |
| - ContainerImageIndexOCI |
22 |
| -from image.platform import ContainerImagePlatform |
23 |
| -from image.reference import ContainerImageReference |
24 |
| -from image.v2s2 import ContainerImageManifestV2S2, \ |
25 |
| - ContainerImageManifestListV2S2 |
| 12 | +from typing import List, Dict, Any, \ |
| 13 | + Union, Type, Iterator |
| 14 | +from image.byteunit import ByteUnit |
| 15 | +from image.client import ContainerImageRegistryClient |
| 16 | +from image.config import ContainerImageConfig |
| 17 | +from image.containerimageinspect import ContainerImageInspect |
| 18 | +from image.errors import ContainerImageError |
| 19 | +from image.manifestfactory import ContainerImageManifestFactory |
| 20 | +from image.manifestlist import ContainerImageManifestList |
| 21 | +from image.oci import ContainerImageManifestOCI, \ |
| 22 | + ContainerImageIndexOCI |
| 23 | +from image.platform import ContainerImagePlatform |
| 24 | +from image.reference import ContainerImageReference |
| 25 | +from image.v2s2 import ContainerImageManifestV2S2, \ |
| 26 | + ContainerImageManifestListV2S2 |
26 | 27 |
|
27 | 28 | #########################################
|
28 | 29 | # Classes for managing container images #
|
@@ -85,6 +86,103 @@ def is_oci_static(
|
85 | 86 | return isinstance(manifest, ContainerImageManifestOCI) or \
|
86 | 87 | isinstance(manifest, ContainerImageIndexOCI)
|
87 | 88 |
|
| 89 | + @staticmethod |
| 90 | + def get_host_platform_manifest_static( |
| 91 | + ref: ContainerImageReference, |
| 92 | + manifest: Union[ |
| 93 | + ContainerImageManifestV2S2, |
| 94 | + ContainerImageManifestListV2S2, |
| 95 | + ContainerImageManifestOCI, |
| 96 | + ContainerImageIndexOCI |
| 97 | + ], |
| 98 | + auth: Dict[str, Any] |
| 99 | + ) -> Union[ |
| 100 | + ContainerImageManifestV2S2, |
| 101 | + ContainerImageManifestOCI |
| 102 | + ]: |
| 103 | + """ |
| 104 | + Given an image's reference and manifest, this static method checks if |
| 105 | + the manifest is a manifest list, and attempts to get the manifest from |
| 106 | + the list matching the host platform. |
| 107 | +
|
| 108 | + Args: |
| 109 | + ref (ContainerImageReference): The image reference corresponding to the manifest |
| 110 | + manifest (Union[ContainerImageManifestV2S2,ContainerImageManifestListV2S2,ContainerImageManifestOCI,ContainerImageIndexOCI]): The manifest object, generally from get_manifest method |
| 111 | + auth (Dict[str, Any]): A valid docker config JSON with auth into the ref's registry |
| 112 | + |
| 113 | + Returns: |
| 114 | + Union[ContainerImageManifestV2S2,ContainerImageManifestOCI]: The manifest response from the registry API |
| 115 | +
|
| 116 | + Raises: |
| 117 | + ContainerImageError: Error if the image is a manifest list without a manifest matching the host platform |
| 118 | + """ |
| 119 | + host_manifest = manifest |
| 120 | + |
| 121 | + # If manifest list, get the manifest matching the host platform |
| 122 | + if ContainerImage.is_manifest_list_static(manifest): |
| 123 | + found = False |
| 124 | + host_entry_digest = None |
| 125 | + host_plt = ContainerImagePlatform.get_host_platform() |
| 126 | + entries = manifest.get_entries() |
| 127 | + for entry in entries: |
| 128 | + if entry.get_platform() == host_plt: |
| 129 | + found = True |
| 130 | + host_entry_digest = entry.get_digest() |
| 131 | + if not found: |
| 132 | + raise ContainerImageError( |
| 133 | + "no image found in manifest list for platform: " + \ |
| 134 | + f"{str(host_plt)}" |
| 135 | + ) |
| 136 | + host_ref = ContainerImage( |
| 137 | + f"{ref.get_name()}@{host_entry_digest}" |
| 138 | + ) |
| 139 | + host_manifest = host_ref.get_manifest(auth=auth) |
| 140 | + |
| 141 | + # Return the manifest matching the host platform |
| 142 | + return host_manifest |
| 143 | + |
| 144 | + @staticmethod |
| 145 | + def get_config_static( |
| 146 | + ref: ContainerImageReference, |
| 147 | + manifest: Union[ |
| 148 | + ContainerImageManifestV2S2, |
| 149 | + ContainerImageManifestListV2S2, |
| 150 | + ContainerImageManifestOCI, |
| 151 | + ContainerImageIndexOCI |
| 152 | + ], |
| 153 | + auth: Dict[str, Any] |
| 154 | + ) -> ContainerImageConfig: |
| 155 | + """ |
| 156 | + Given an image's manifest, this static method fetches that image's |
| 157 | + config from the distribution registry API. If the image is a manifest |
| 158 | + list, then it gets the config corresponding to the manifest matching |
| 159 | + the host platform. |
| 160 | +
|
| 161 | + Args: |
| 162 | + ref (ContainerImageReference): The image reference corresponding to the manifest |
| 163 | + manifest (Union[ContainerImageManifestV2S2,ContainerImageManifestListV2S2,ContainerImageManifestOCI,ContainerImageIndexOCI]): The manifest object, generally from get_manifest method |
| 164 | + auth (Dict[str, Any]): A valid docker config JSON with auth into this image's registry |
| 165 | + |
| 166 | + Returns: |
| 167 | + ContainerImageConfig: The config for this image |
| 168 | +
|
| 169 | + Raises: |
| 170 | + ContainerImageError: Error if the image is a manifest list without a manifest matching the host platform |
| 171 | + """ |
| 172 | + # If manifest list, get the manifest matching the host platform |
| 173 | + manifest = ContainerImage.get_host_platform_manifest_static( |
| 174 | + ref, manifest, auth |
| 175 | + ) |
| 176 | + |
| 177 | + # Get the image's config |
| 178 | + return ContainerImageConfig( |
| 179 | + ContainerImageRegistryClient.get_config( |
| 180 | + ref, |
| 181 | + manifest.get_config_descriptor(), |
| 182 | + auth=auth |
| 183 | + ) |
| 184 | + ) |
| 185 | + |
88 | 186 | def __init__(self, ref: str):
|
89 | 187 | """
|
90 | 188 | Constructor for the ContainerImage class
|
@@ -179,6 +277,61 @@ def get_manifest(self, auth: Dict[str, Any]) -> Union[
|
179 | 277 | ContainerImageRegistryClient.get_manifest(self, auth)
|
180 | 278 | )
|
181 | 279 |
|
| 280 | + def get_host_platform_manifest(self, auth: Dict[str, Any]) -> Union[ |
| 281 | + ContainerImageManifestOCI, |
| 282 | + ContainerImageManifestV2S2 |
| 283 | + ]: |
| 284 | + """ |
| 285 | + Fetches the manifest from the distribution registry API. If the |
| 286 | + manifest is a manifest list, then it attempts to fetch the manifest |
| 287 | + in the list matching the host platform. If not found, an exception is |
| 288 | + raised. |
| 289 | +
|
| 290 | + Args: |
| 291 | + auth (Dict[str, Any]): A valid docker config JSON with auth into this image's registry |
| 292 | + |
| 293 | + Returns: |
| 294 | + Union[ContainerImageManifestV2S2,ContainerImageManifestOCI]: The manifest response from the registry API |
| 295 | +
|
| 296 | + Raises: |
| 297 | + ContainerImageError: Error if the image is a manifest list without a manifest matching the host platform |
| 298 | + """ |
| 299 | + # Get the container image's manifest |
| 300 | + manifest = self.get_manifest(auth=auth) |
| 301 | + |
| 302 | + # Return the host platform manifest |
| 303 | + return ContainerImage.get_host_platform_manifest_static( |
| 304 | + self, |
| 305 | + manifest, |
| 306 | + auth |
| 307 | + ) |
| 308 | + |
| 309 | + def get_config(self, auth: Dict[str, Any]) -> ContainerImageConfig: |
| 310 | + """ |
| 311 | + Fetches the image's config from the distribution registry API. If the |
| 312 | + image is a manifest list, then it gets the config corresponding to the |
| 313 | + manifest matching the host platform. |
| 314 | +
|
| 315 | + Args: |
| 316 | + auth (Dict[str, Any]): A valid docker config JSON with auth into this image's registry |
| 317 | + |
| 318 | + Returns: |
| 319 | + ContainerImageConfig: The config for this image |
| 320 | +
|
| 321 | + Raises: |
| 322 | + ContainerImageError: Error if the image is a manifest list without a manifest matching the host platform |
| 323 | + """ |
| 324 | + # Get the image's manifest |
| 325 | + manifest = self.get_manifest(auth=auth) |
| 326 | + |
| 327 | + # Use the image's manifest to get the image's config |
| 328 | + config = ContainerImage.get_config_static( |
| 329 | + self, manifest, auth |
| 330 | + ) |
| 331 | + |
| 332 | + # Return the image's config |
| 333 | + return config |
| 334 | + |
182 | 335 | def exists(self, auth: Dict[str, Any]) -> bool:
|
183 | 336 | """
|
184 | 337 | Determine if the image reference corresponds to an image in the remote
|
@@ -266,6 +419,60 @@ def get_size_formatted(self, auth: Dict[str, Any]) -> str:
|
266 | 419 | """
|
267 | 420 | return ByteUnit.format_size_bytes(self.get_size(auth))
|
268 | 421 |
|
| 422 | + def inspect(self, auth: Dict[str, Any]) -> ContainerImageInspect: |
| 423 | + """ |
| 424 | + Returns a collection of basic information about the image, equivalent |
| 425 | + to skopeo inspect. |
| 426 | +
|
| 427 | + Args: |
| 428 | + auth (Dict[str, Any]): A valid docker config JSON loaded into a dict |
| 429 | + |
| 430 | + Returns: |
| 431 | + ContainerImageInspect: A collection of information about the image |
| 432 | + """ |
| 433 | + # Get the image's manifest |
| 434 | + manifest = self.get_host_platform_manifest(auth=auth) |
| 435 | + |
| 436 | + # Use the image's manifest to get the image's config |
| 437 | + config = ContainerImage.get_config_static( |
| 438 | + self, manifest, auth |
| 439 | + ) |
| 440 | + |
| 441 | + # Format the inspect dictionary |
| 442 | + inspect = { |
| 443 | + "Name": self.get_name(), |
| 444 | + "Digest": self.get_digest(auth=auth), |
| 445 | + # TODO: Implement v2s1 manifest extension - only v2s1 manifests use this value |
| 446 | + "DockerVersion": "", |
| 447 | + "Created": config.get_created_date(), |
| 448 | + "Labels": config.get_labels(), |
| 449 | + "Architecture": config.get_architecture(), |
| 450 | + "Variant": config.get_variant() or "", |
| 451 | + "Os": config.get_os(), |
| 452 | + "Layers": [ |
| 453 | + layer.get_digest() \ |
| 454 | + for layer \ |
| 455 | + in manifest.get_layer_descriptors() |
| 456 | + ], |
| 457 | + "LayersData": [ |
| 458 | + { |
| 459 | + "MIMEType": layer.get_media_type(), |
| 460 | + "Digest": layer.get_digest(), |
| 461 | + "Size": layer.get_size(), |
| 462 | + "Annotations": layer.get_annotations() or {} |
| 463 | + } for layer in manifest.get_layer_descriptors() |
| 464 | + ], |
| 465 | + "Env": config.get_env(), |
| 466 | + "Author": config.get_author() |
| 467 | + } |
| 468 | + |
| 469 | + # Set the tag in the inspect dict |
| 470 | + if self.is_tag_ref(): |
| 471 | + inspect["Tag"] = self.get_identifier() |
| 472 | + |
| 473 | + # TODO: Get the RepoTags for the image |
| 474 | + return ContainerImageInspect(inspect) |
| 475 | + |
269 | 476 | def delete(self, auth: Dict[str, Any]):
|
270 | 477 | """
|
271 | 478 | Deletes the image from the registry.
|
|
0 commit comments