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
Exemple #4
0
    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
Exemple #5
0
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