def __init__(self, workflow): """ constructor :param workflow: DockerBuildWorkflow instance """ super(PushFloatingTagsPlugin, self).__init__(workflow) self.manifest_util = ManifestUtil(workflow, self.log)
class PushFloatingTagsPlugin(PostBuildPlugin): """ Push floating tags to registry """ key = PLUGIN_PUSH_FLOATING_TAGS_KEY is_allowed_to_fail = False def __init__(self, workflow): """ constructor :param workflow: DockerBuildWorkflow instance """ super(PushFloatingTagsPlugin, self).__init__(workflow) self.manifest_util = ManifestUtil(workflow, self.log) def add_floating_tags(self, session, manifest_list_data, floating_images): list_type = manifest_list_data.get("media_type") manifest = manifest_list_data.get("manifest") manifest_digest = manifest_list_data.get("manifest_digest") for image in floating_images: target_repo = image.to_str(registry=False, tag=False) # We have to call store_manifest_in_repository directly for each # referenced manifest, since each one should be a new tag that requires uploading # the manifest again self.log.debug("storing %s as %s", target_repo, image.tag) self.manifest_util.store_manifest_in_repository(session, manifest, list_type, target_repo, target_repo, ref=image.tag) registry_image = get_unique_images(self.workflow)[0] return registry_image.get_repo(explicit_namespace=False), manifest_digest def run(self): """ Run the plugin. """ floating_tags = get_floating_images(self.workflow) if not floating_tags: self.log.info('No floating images to tag, skipping %s', PLUGIN_PUSH_FLOATING_TAGS_KEY) return manifest_data = self.workflow.data.postbuild_results.get(PLUGIN_GROUP_MANIFESTS_KEY) if not manifest_data or not manifest_data.get("manifest_digest"): self.log.info('No manifest digest available, skipping %s', PLUGIN_PUSH_FLOATING_TAGS_KEY) return digests = dict() session = self.manifest_util.get_registry_session() repo, digest = self.add_floating_tags(session, manifest_data, floating_tags) digests[repo] = digest return digests
def __init__(self, workflow): """ constructor :param workflow: DockerBuildWorkflow instance """ # call parent constructor super(GroupManifestsPlugin, self).__init__(workflow) self.group = self.workflow.conf.group_manifests self.goarch = self.workflow.conf.platform_to_goarch_mapping self.manifest_util = ManifestUtil(self.workflow, self.log) self.non_floating_images = None
def __init__(self, tasker, workflow, registries=None, group=True, goarch=None): """ constructor :param tasker: ContainerTasker instance :param workflow: DockerBuildWorkflow instance :param registries: dict, keys are docker registries, values are dicts containing per-registry parameters. Params: * "secret" optional string - path to the secret, which stores login and password for remote registry :param group: bool, if true, create a manifest list; otherwise only add tags to amd64 image manifest :param goarch: dict, keys are platform, values are go language platform names """ # call parent constructor super(GroupManifestsPlugin, self).__init__(tasker, workflow) self.group = get_group_manifests(self.workflow, group) plat_des_fallback = [] for platform, architecture in (goarch or {}).items(): plat_dic = {'platform': platform, 'architecture': architecture} plat_des_fallback.append(plat_dic) platform_descriptors = get_platform_descriptors( self.workflow, plat_des_fallback) goarch_from_pd = {} for platform in platform_descriptors: goarch_from_pd[platform['platform']] = platform['architecture'] self.goarch = goarch_from_pd self.manifest_util = ManifestUtil(self.workflow, registries, self.log) self.non_floating_images = None
class GroupManifestsPlugin(PostBuildPlugin): is_allowed_to_fail = False key = PLUGIN_GROUP_MANIFESTS_KEY def __init__(self, tasker, workflow, registries=None, group=True, goarch=None): """ constructor :param tasker: ContainerTasker instance :param workflow: DockerBuildWorkflow instance :param registries: dict, keys are docker registries, values are dicts containing per-registry parameters. Params: * "secret" optional string - path to the secret, which stores login and password for remote registry :param group: bool, if true, create a manifest list; otherwise only add tags to amd64 image manifest :param goarch: dict, keys are platform, values are go language platform names """ # call parent constructor super(GroupManifestsPlugin, self).__init__(tasker, workflow) self.group = get_group_manifests(self.workflow, group) plat_des_fallback = [] for platform, architecture in (goarch or {}).items(): plat_dic = {'platform': platform, 'architecture': architecture} plat_des_fallback.append(plat_dic) platform_descriptors = get_platform_descriptors( self.workflow, plat_des_fallback) goarch_from_pd = {} for platform in platform_descriptors: goarch_from_pd[platform['platform']] = platform['architecture'] self.goarch = goarch_from_pd self.manifest_util = ManifestUtil(self.workflow, registries, self.log) self.non_floating_images = None def group_manifests_and_tag(self, session, worker_digests): """ Creates a manifest list or OCI image index that groups the different manifests in worker_digests, then tags the result with with all the configured tags found in workflow.tag_conf. """ self.log.info("%s: Creating manifest list", session.registry) # Extract information about the manifests that we will group - we get the # size and content type of the manifest by querying the registry manifests = [] for platform, worker_image in worker_digests.items(): repository = worker_image['repository'] digest = worker_image['digest'] media_type = get_manifest_media_type(worker_image['version']) if media_type not in self.manifest_util.manifest_media_types: continue content, _, media_type, size = self.manifest_util.get_manifest( session, repository, digest) manifests.append({ 'content': content, 'repository': repository, 'digest': digest, 'size': size, 'media_type': media_type, 'architecture': self.goarch.get(platform, platform), }) list_type, list_json = self.manifest_util.build_list(manifests) self.log.info("%s: Created manifest, Content-Type=%s\n%s", session.registry, list_type, list_json) # Now push the manifest list to the registry once per each tag self.log.info("%s: Tagging manifest list", session.registry) for image in self.non_floating_images: target_repo = image.to_str(registry=False, tag=False) # We have to call store_manifest_in_repository directly for each # referenced manifest, since they potentially come from different repos for manifest in manifests: self.manifest_util.store_manifest_in_repository( session, manifest['content'], manifest['media_type'], manifest['repository'], target_repo, ref=manifest['digest']) self.manifest_util.store_manifest_in_repository(session, list_json, list_type, target_repo, target_repo, ref=image.tag) # Get the digest of the manifest list using one of the tags registry_image = get_unique_images(self.workflow)[0] _, digest_str, _, _ = self.manifest_util.get_manifest( session, registry_image.to_str(registry=False, tag=False), registry_image.tag) if list_type == MEDIA_TYPE_OCI_V1_INDEX: digest = ManifestDigest(oci_index=digest_str) else: digest = ManifestDigest(v2_list=digest_str) # And store the manifest list in the push_conf push_conf_registry = self.workflow.push_conf.add_docker_registry( session.registry, insecure=session.insecure) tags = [] for image in self.non_floating_images: push_conf_registry.digests[image.tag] = digest tags.append(image.tag) self.log.info("%s: Manifest list digest is %s", session.registry, digest_str) self.log.debug("tags: %s digest: %s", tags, digest) return { 'manifest': list_json, 'media_type': list_type, 'manifest_digest': digest } def sort_annotations(self): """ Return a map of maps to look up a single "worker digest" that has information about where to find an image manifest for each registry/architecture combination: worker_digest = <result>[registry][architecture] """ all_annotations = self.workflow.build_result.annotations[ 'worker-builds'] all_platforms = set(all_annotations) if len(all_platforms) == 0: raise RuntimeError("No worker builds found, cannot group them") return self.manifest_util.sort_annotations(all_annotations) def tag_manifest_into_registry(self, session, source_digest, source_repo, images): manifest, media, digest = self.manifest_util.tag_manifest_into_registry( session, source_digest, source_repo, images) return { 'manifest': manifest, 'media_type': media, 'manifest_digest': digest } def run(self): primary_images = get_primary_images(self.workflow) unique_images = get_unique_images(self.workflow) self.non_floating_images = primary_images + unique_images for registry, source in self.sort_annotations().items(): session = self.manifest_util.get_registry_session(registry) if self.group: return self.group_manifests_and_tag(session, source) else: if len(source) != 1: raise RuntimeError( 'Without grouping only one source is expected') # source.values() isn't a list and can't be indexed, so this clumsy workaround _, orig_digest = source.popitem() source_digest = orig_digest['digest'] source_repo = orig_digest['repository'] return self.tag_manifest_into_registry( session, source_digest, source_repo, self.non_floating_images)
class GroupManifestsPlugin(PostBuildPlugin): is_allowed_to_fail = False key = PLUGIN_GROUP_MANIFESTS_KEY def __init__(self, workflow): """ constructor :param workflow: DockerBuildWorkflow instance """ # call parent constructor super(GroupManifestsPlugin, self).__init__(workflow) self.group = self.workflow.conf.group_manifests self.goarch = self.workflow.conf.platform_to_goarch_mapping self.manifest_util = ManifestUtil(self.workflow, self.log) self.non_floating_images = None def get_built_images(self, session: RegistrySession) -> List[BuiltImage]: """Get information about all the per-arch images that were built by the build tasks.""" tag_conf = self.workflow.data.tag_conf client = RegistryClient(session) built_images = [] for platform in get_platforms(self.workflow.data): # At this point, only the unique image has been built and pushed. Primary tags will # be pushed by this plugin, floating tags by the push_floating_tags plugin. image = tag_conf.get_unique_images_with_platform(platform)[0] manifest_digests = client.get_manifest_digests(image, versions=("v2", "oci")) if len(manifest_digests) != 1: raise RuntimeError( f"Expected to find a single manifest digest for {image}, " f"but found multiple: {manifest_digests}") manifest_version, manifest_digest = manifest_digests.popitem() built_images.append( BuiltImage(image, platform, manifest_digest, manifest_version)) return built_images def group_manifests_and_tag( self, session: RegistrySession, built_images: List[BuiltImage] ) -> Dict[str, Union[str, ManifestDigest]]: """ Creates a manifest list or OCI image index that groups the different manifests in built_images, then tags the result with all the configured tags found in workflow.data.tag_conf. """ self.log.info("%s: Creating manifest list", session.registry) # Extract information about the manifests that we will group - we get the # size and content type of the manifest by querying the registry manifests = [] for built_image in built_images: repository = built_image.repository manifest_digest = built_image.manifest_digest media_type = get_manifest_media_type(built_image.manifest_version) if media_type not in self.manifest_util.manifest_media_types: continue content, _, media_type, size = self.manifest_util.get_manifest( session, repository, manifest_digest) manifests.append({ 'content': content, 'repository': repository, 'digest': manifest_digest, 'size': size, 'media_type': media_type, 'architecture': self.goarch[built_image.platform], }) list_type, list_json = self.manifest_util.build_list(manifests) self.log.info("%s: Created manifest, Content-Type=%s\n%s", session.registry, list_type, list_json) # Now push the manifest list to the registry once per each tag self.log.info("%s: Tagging manifest list", session.registry) for image in self.non_floating_images: target_repo = image.to_str(registry=False, tag=False) # We have to call store_manifest_in_repository directly for each # referenced manifest, since they potentially come from different repos for manifest in manifests: self.manifest_util.store_manifest_in_repository( session, manifest['content'], manifest['media_type'], manifest['repository'], target_repo, ref=manifest['digest']) self.manifest_util.store_manifest_in_repository(session, list_json, list_type, target_repo, target_repo, ref=image.tag) # Get the digest of the manifest list using one of the tags registry_image = get_unique_images(self.workflow)[0] _, digest_str, _, _ = self.manifest_util.get_manifest( session, registry_image.to_str(registry=False, tag=False), registry_image.tag) if list_type == MEDIA_TYPE_OCI_V1_INDEX: digest = ManifestDigest(oci_index=digest_str) else: digest = ManifestDigest(v2_list=digest_str) tags = [] for image in self.non_floating_images: tags.append(image.tag) self.log.info("%s: Manifest list digest is %s", session.registry, digest_str) self.log.debug("tags: %s digest: %s", tags, digest) return { 'manifest': list_json, 'media_type': list_type, 'manifest_digest': digest } def tag_manifest_into_registry(self, session, source_digest: str, source_repo, images): manifest, media, digest = self.manifest_util.tag_manifest_into_registry( session, source_digest, source_repo, images) return { 'manifest': manifest.decode('utf-8'), 'media_type': media, 'manifest_digest': digest, } def run(self): primary_images = get_primary_images(self.workflow) unique_images = get_unique_images(self.workflow) self.non_floating_images = primary_images + unique_images session = self.manifest_util.get_registry_session() built_images = self.get_built_images(session) if self.group: return self.group_manifests_and_tag(session, built_images) else: if len(built_images) != 1: raise RuntimeError( 'Without grouping only one built image is expected') built_image = built_images[0] source_digest = built_image.manifest_digest source_repo = built_image.repository return self.tag_manifest_into_registry(session, source_digest, source_repo, self.non_floating_images)
class PushFloatingTagsPlugin(ExitPlugin): """ Push floating tags to registry """ key = PLUGIN_PUSH_FLOATING_TAGS_KEY is_allowed_to_fail = False def __init__(self, tasker, workflow): """ constructor :param tasker: DockerTasker instance :param workflow: DockerBuildWorkflow instance """ super(PushFloatingTagsPlugin, self).__init__(tasker, workflow) self.manifest_util = ManifestUtil(workflow, None, self.log) def add_floating_tags(self, session, manifest_list_data, floating_images): list_type = manifest_list_data.get("media_type") manifest = manifest_list_data.get("manifest") manifest_digest = manifest_list_data.get("manifest_digest") for image in floating_images: target_repo = image.to_str(registry=False, tag=False) # We have to call store_manifest_in_repository directly for each # referenced manifest, since each one should be a new tag that requires uploading # the manifest again self.log.debug("storing %s as %s", target_repo, image.tag) self.manifest_util.store_manifest_in_repository(session, manifest, list_type, target_repo, target_repo, ref=image.tag) # And store the manifest list in the push_conf push_conf_registry = self.workflow.push_conf.add_docker_registry( session.registry, insecure=session.insecure) for image in floating_images: push_conf_registry.digests[image.tag] = manifest_digest registry_image = get_unique_images(self.workflow)[0] return registry_image.get_repo( explicit_namespace=False), manifest_digest def run(self): """ Run the plugin. """ if self.workflow.build_process_failed: self.log.info('Build failed, skipping %s', PLUGIN_PUSH_FLOATING_TAGS_KEY) return floating_tags = get_floating_images(self.workflow) if not floating_tags: self.log.info('No floating images to tag, skipping %s', PLUGIN_PUSH_FLOATING_TAGS_KEY) return # can't run in the worker build if not self.workflow.is_orchestrator_build(): self.log.warning('%s cannot be used by a worker builder', PLUGIN_PUSH_FLOATING_TAGS_KEY) return manifest_data = self.workflow.postbuild_results.get( PLUGIN_GROUP_MANIFESTS_KEY) if not manifest_data or not manifest_data.get("manifest_digest"): self.log.info('No manifest digest available, skipping %s', PLUGIN_PUSH_FLOATING_TAGS_KEY) return digests = dict() for registry in self.manifest_util.registries: session = self.manifest_util.get_registry_session(registry) repo, digest = self.add_floating_tags(session, manifest_data, floating_tags) digests[repo] = digest return digests