Ejemplo n.º 1
0
class BuildCommon(BuildParamsBase):
    """Common user parameters, class should be considered abstract"""

    # Must be defined in subclasses
    KIND = NotImplemented

    build_json_dir = BuildParam("build_json_dir", required=True)
    component = BuildParam("component")
    image_tag = BuildParam("image_tag")
    koji_target = BuildParam("koji_target")
    koji_task_id = BuildParam("koji_task_id")
    namespace = BuildParam("namespace")
    pipeline_run_name = BuildParam("pipeline_run_name")
    platform = BuildParam("platform")
    reactor_config_map = BuildParam("reactor_config_map")
    scratch = BuildParam("scratch")
    signing_intent = BuildParam("signing_intent")
    user = BuildParam("user", required=True)
    userdata = BuildParam("userdata")

    def __setattr__(self, name, value):
        super(BuildCommon, self).__setattr__(name, value)
        logger.debug("%s = %s", name, value)

    @classmethod
    def make_params(cls,
                    build_conf=None,
                    build_json_dir=None,
                    component=None,
                    koji_target=None,
                    koji_task_id=None,
                    pipeline_run_name=None,
                    platform=None,
                    scratch=None,
                    signing_intent=None,
                    user=None,
                    userdata=None,
                    **kwargs):
        """
        Create a user_params instance.

        Most parameters will simply be used as the value of the corresponding BuildParam.
        The notable exception is `build_conf`, which contains values for other params but
        is not a BuildParam itself (list of params set from build_conf can be found below).

        Arguments that are None (either passed as None, or None by default) are ignored.
        This is important to avoid overwriting default values of params. Once the instance
        is created, however, overwriting defaults by setting None is allowed, e.g.:

        >>> params = BuildCommon.make_params(build_conf=bc)  # does not overwrite defaults
        >>> params.version = None  # does overwrite the default

        these parameters are accepted:
        :param base_image: str, name of the parent image
        :param build_conf: BuildConfiguration, the build configuration
        :param build_json_dir: str, path to directory with JSON build templates
        :param component: str, name of the component
        :param koji_parent_build: str,
        :param koji_target: str, koji tag with packages used to build the image
        :param koji_task_id: int, koji *task* ID
        :param koji_upload_dir: str, koji directory where the completed image will be uploaded
        :param pipeline_run_name: str, name of the pipeline run
        :param platform: str, platform
        :param scratch: bool, build as a scratch build (if not specified in build_conf)
        :param signing_intent: bool, True to sign the resulting image
        :param user: str, name of the user requesting the build
        :param userdata: dict, custom user data

        Please keep the paramater list alphabetized for easier tracking of changes

        the following parameters are pulled from the BuildConfiguration (ie, build_conf)
        :param namespace: str, openshift namespace
        :param reactor_config_map: str, name of the config map containing the reactor environment
        :param scratch: bool, build as a scratch build
        """
        if not build_conf:
            raise OsbsValidationException('build_conf must be defined')

        if build_conf.get_scratch(scratch):
            reactor_config = build_conf.get_reactor_config_map_scratch()
        else:
            reactor_config = build_conf.get_reactor_config_map()
        # Update kwargs with arguments explicitly accepted by this method
        kwargs.update({
            "build_json_dir": build_json_dir,
            "component": component,
            "koji_target": koji_target,
            "koji_task_id": koji_task_id,
            "namespace": build_conf.get_namespace(),
            "platform": platform,
            "signing_intent": signing_intent,
            "user": user,
            "userdata": userdata,
            # Potentially pulled from build_conf
            "pipeline_run_name": pipeline_run_name,
            "reactor_config_map": reactor_config,
            "scratch": build_conf.get_scratch(scratch),
        })

        # Drop arguments that are:
        # - unknown; some callers may pass deprecated params
        # - not set (set to None, either explicitly or implicitly)
        kwargs = {
            k: v
            for k, v in kwargs.items()
            if v is not None and cls.get_param(k) is not None
        }

        params = cls(**kwargs)
        params._populate_image_tag()
        return params

    @classmethod
    def _make_params_super(cls, *args, **kwargs):
        # Pylint cannot properly infer the return type of an overridden classmethod
        # that returns cls(). This is an ugly workaround that prevents pylint from
        # inferring any type at all (thus preventing false-positive warnings).
        # See https://github.com/PyCQA/pylint/issues/981
        return BuildCommon.make_params.__func__(cls, *args, **kwargs)

    def _populate_image_tag(self):
        timestamp = utcnow().strftime('%Y%m%d%H%M%S')
        # RNG is seeded once its imported, so in cli calls scratch builds would get unique name.
        # On brew builders we import osbs once - thus RNG is seeded once and `randrange`
        # returns the same values throughout the life of the builder.
        # Before each `randrange` call we should be calling `.seed` to prevent this
        random.seed()

        tag_segments = [
            self.koji_target or 'none',
            str(random.randrange(10**(RAND_DIGITS - 1), 10**RAND_DIGITS)),
            timestamp
        ]

        if self.platform:
            tag_segments.append(self.platform)

        tag = '-'.join(tag_segments)
        self.image_tag = '{}/{}:{}'.format(self.user, self.component, tag)

    def validate(self):
        logger.info("Validating params of %s", self.__class__.__name__)
        # pylint: disable=not-an-iterable; pylint does not understand metaclass properties
        missing = [
            p for p in self.__class__.required_params
            if p.__get__(self) is None
        ]
        if missing:
            missing_repr = ", ".join(repr(p.name) for p in missing)
            raise OsbsValidationException(
                "Missing required params: {}".format(missing_repr))

    @classmethod
    def from_json(cls, user_params_json):
        if not user_params_json:
            return cls()
        try:
            json_dict = json.loads(user_params_json)
        except ValueError:
            logger.debug('failed to convert %s', user_params_json)
            raise
        # Drop invalid keys
        json_dict = {
            k: v
            for k, v in json_dict.items() if cls.get_param(k) is not None
        }
        return cls(**json_dict)

    def to_dict(self, keys):
        retdict = {}
        for key in keys:
            value = getattr(self, key)
            if value:
                retdict[key] = value
        return retdict

    def to_json(self):
        # pylint: disable=not-an-iterable; pylint does not understand metaclass properties
        keys = (p.name for p in self.__class__.params if p.include_in_json)
        json_dict = self.to_dict(keys)
        json_dict[KIND_KEY] = self.KIND
        return json.dumps(json_dict, sort_keys=True)
Ejemplo n.º 2
0
class BuildUserParams(BuildCommon):

    KIND = USER_PARAMS_KIND_IMAGE_BUILDS

    additional_tags = BuildParam("additional_tags")
    base_image = BuildParam("base_image")
    compose_ids = BuildParam("compose_ids")
    dependency_replacements = BuildParam("dependency_replacements")
    filesystem_koji_task_id = BuildParam("filesystem_koji_task_id")
    flatpak = BuildParam("flatpak", default=False)
    git_branch = BuildParam("git_branch")
    git_commit_depth = BuildParam("git_commit_depth")
    git_ref = BuildParam("git_ref", default=DEFAULT_GIT_REF, required=True)
    git_uri = BuildParam("git_uri", required=True)
    include_koji_repo = BuildParam("include_koji_repo", default=False)
    isolated = BuildParam("isolated")
    koji_parent_build = BuildParam("koji_parent_build")
    koji_upload_dir = BuildParam("koji_upload_dir")
    name = BuildIDParam()
    operator_bundle_replacement_pullspecs = BuildParam(
        "operator_bundle_replacement_pullspecs")
    operator_csv_modifications_url = BuildParam(
        "operator_csv_modifications_url")
    operator_manifests_extract_platform = BuildParam(
        "operator_manifests_extract_platform")
    parent_images_digests = BuildParam("parent_images_digests")
    platforms = BuildParam("platforms")
    release = BuildParam("release")
    remote_sources = BuildParam("remote_sources")
    tags_from_yaml = BuildParam("tags_from_yaml")
    yum_repourls = BuildParam("yum_repourls")

    @classmethod
    def make_params(cls,
                    additional_tags=None,
                    base_image=None,
                    build_conf=None,
                    compose_ids=None,
                    dependency_replacements=None,
                    filesystem_koji_task_id=None,
                    flatpak=None,
                    git_branch=None,
                    git_commit_depth=None,
                    git_ref=None,
                    git_uri=None,
                    include_koji_repo=None,
                    isolated=None,
                    koji_parent_build=None,
                    koji_upload_dir=None,
                    name_label=None,
                    operator_bundle_replacement_pullspecs=None,
                    operator_csv_modifications_url=None,
                    operator_manifests_extract_platform=None,
                    parent_images_digests=None,
                    platform=None,
                    platforms=None,
                    release=None,
                    remote_sources=None,
                    repo_info=None,
                    tags_from_yaml=None,
                    yum_repourls=None,
                    **kwargs):
        """
        Create a BuildUserParams instance.

        Like the parent method, most params are simply used as values for the corresponding
        BuildParam, this time with two notable exceptions: `build_conf` and `repo_info`.
        Compared to the parent method, this one pulls even more param values from `build_conf`
        and may also pull some values from `repo_info` (see below).

        these parameters are accepted:
        :param build_conf: BuildConfiguration, optional build configuration
        :param compose_ids: list of int, ODCS composes to use instead of generating new ones
        :param dependency_replacements: list of str, dependencies to be replaced by cachito, as
        pkg_manager:name:version[:new_name]
        :param filesystem_koji_task_id: int, Koji Task that created the base filesystem
        :param flatpak: if we should build a Flatpak OCI Image
        :param git_branch: str, branch name of the branch to be pulled
        :param git_ref: str, commit ID of the branch to be pulled
        :param git_uri: str, uri of the git repository for the source
        :param include_koji_repo: include the repo from the target build tag, even if other
                                                   repourls are provided.
        :param isolated: bool, build as an isolated build
        :param koji_parent_build: str,
        :param koji_upload_dir: str, koji directory where the completed image will be uploaded
        :param name_label: str, label of the parent image
        :param user: str, name of the user requesting the build
        :param operator_bundle_replacement_pullspecs: dict, mapping of original pullspecs to
                                                      replacement pullspecs for operator manifest
                                                      bundle builds
        :param operator_csv_modifications_url: str, URL to JSON file describing operator CSV changes
        :param operator_manifests_extract_platform: str, indicates which platform should upload
                                                    operator manifests to koji
        :param parent_images_digests: dict, mapping image digests to names and platforms
        :param platforms: list of str, platforms to build on
        :param platform: str, platform
        :param reactor_config_map: str, name of the config map containing the reactor environment
        :param release: str,
        :param remote_sources: list of dicts, each dict contains info about particular
        remote source with the following keys:
            build_args: dict, extra args for `builder.build_args`, if any
            configs: list of str, configuration files to be injected into
            the exploded remote sources dir
            request_id: int, cachito request id; used to request the
            Image Content Manifest
            url: str, URL from which to download a source archive
            name: str, name of remote source
        :param repo_info: RepoInfo, git repo data for the build
        :param scratch: bool, build as a scratch build
        :param signing_intent: bool, True to sign the resulting image
        :param yum_repourls: list of str, uris of the yum repos to pull from

        Please keep the paramater list alphabetized for easier tracking of changes

        the following parameters are pulled from the BuildConfiguration (ie, build_conf)

        the following parameters can be pulled from the RepoInfo (ie, repo_info)
        :param git_branch: str, branch name of the branch to be pulled
        :param git_ref: str, commit ID of the branch to be pulled
        :param git_uri: str, uri of the git repository for the source
        """
        if repo_info:
            additional_tags = repo_info.additional_tags.tags
            git_branch = repo_info.git_branch
            git_commit_depth = repo_info.git_commit_depth
            git_ref = repo_info.git_ref
            git_uri = repo_info.git_uri
            tags_from_yaml = repo_info.additional_tags.from_container_yaml
        elif not git_uri:
            raise OsbsValidationException(
                'no repo_info passed to BuildUserParams')

        # For flatpaks, we can set this later from the reactor config
        if not base_image and not flatpak:
            raise OsbsValidationException("base_image must be provided")

        if not name_label:
            raise OsbsValidationException("name_label must be provided")

        if kwargs.get('signing_intent') and compose_ids:
            raise OsbsValidationException(
                'Please only define signing_intent -OR- compose_ids, not both')
        if not (compose_ids is None or isinstance(compose_ids, list)):
            raise OsbsValidationException("compose_ids must be a list")
        if not (dependency_replacements is None
                or isinstance(dependency_replacements, list)):
            raise OsbsValidationException(
                "dependency_replacements must be a list")
        if not (yum_repourls is None or isinstance(yum_repourls, list)):
            raise OsbsValidationException("yum_repourls must be a list")

        kwargs.update({
            "base_image": base_image,
            "build_conf": build_conf,
            "compose_ids": compose_ids or [],
            "dependency_replacements": dependency_replacements or [],
            "filesystem_koji_task_id": filesystem_koji_task_id,
            "flatpak": flatpak,
            "include_koji_repo": include_koji_repo,
            "isolated": isolated,
            "koji_parent_build": koji_parent_build,
            "koji_upload_dir": koji_upload_dir,
            "operator_bundle_replacement_pullspecs":
            operator_bundle_replacement_pullspecs,
            "operator_csv_modifications_url": operator_csv_modifications_url,
            "operator_manifests_extract_platform":
            operator_manifests_extract_platform,
            "parent_images_digests": parent_images_digests,
            "platform": platform,
            "platforms": platforms,
            "release": release,
            "remote_sources": remote_sources,
            "yum_repourls": yum_repourls or [],
            # Potentially pulled from repo_info
            "additional_tags": additional_tags or set(),
            "git_branch": git_branch,
            "git_commit_depth": git_commit_depth,
            "git_ref": git_ref,
            "git_uri": git_uri,
            "name": make_name_from_git(git_uri, git_branch),
            "tags_from_yaml": tags_from_yaml,
        })

        params = cls._make_params_super(**kwargs)

        if (params.scratch, params.isolated).count(True) > 1:
            raise OsbsValidationException(
                'Build variations are mutually exclusive. '
                'Must set either scratch, isolated, or none. ')

        return params

    def set_base_image(self, base_image):
        self.base_image = base_image