예제 #1
0
def prepare_disconnected_ocs_deployment(upgrade=False):
    """
    Prepare disconnected ocs deployment:
    - get related images from OCS operator bundle csv
    - mirror related images to mirror registry
    - create imageContentSourcePolicy for the mirrored images
    - disable the default OperatorSources

    Args:
        upgrade (bool): is this fresh installation or upgrade process
            (default: False)

    Returns:
        str: mirrored OCS registry image prepared for disconnected installation
            or None (for live deployment)

    """

    logger.info(
        f"Prepare for disconnected OCS {'upgrade' if upgrade else 'installation'}"
    )
    if config.DEPLOYMENT.get("live_deployment"):
        get_opm_tool()

        pull_secret_path = os.path.join(constants.TOP_DIR, "data",
                                        "pull-secret")
        ocp_version = get_ocp_version()
        index_image = f"{config.DEPLOYMENT['cs_redhat_operators_image']}:v{ocp_version}"
        mirrored_index_image = (
            f"{config.DEPLOYMENT['mirror_registry']}/{constants.MIRRORED_INDEX_IMAGE_NAMESPACE}/"
            f"{constants.MIRRORED_INDEX_IMAGE_NAME}:v{ocp_version}")
        # prune an index image
        logger.info(
            f"Prune index image {index_image} -> {mirrored_index_image} "
            f"(packages: {', '.join(constants.DISCON_CL_REQUIRED_PACKAGES)})")
        cmd = (f"opm index prune -f {index_image} "
               f"-p {','.join(constants.DISCON_CL_REQUIRED_PACKAGES)} "
               f"-t {mirrored_index_image}")
        # opm tool doesn't have --authfile parameter, we have to supply auth
        # file through env variable
        os.environ["REGISTRY_AUTH_FILE"] = pull_secret_path
        exec_cmd(cmd)

        # login to mirror registry
        login_to_mirror_registry(pull_secret_path)

        # push pruned index image to mirror registry
        logger.info(
            f"Push pruned index image to mirror registry: {mirrored_index_image}"
        )
        cmd = f"podman push --authfile {pull_secret_path} --tls-verify=false {mirrored_index_image}"
        exec_cmd(cmd)

        # mirror related images (this might take very long time)
        logger.info(
            f"Mirror images related to index image: {mirrored_index_image}")
        cmd = (
            f"oc adm catalog mirror {mirrored_index_image} -a {pull_secret_path} --insecure "
            f"{config.DEPLOYMENT['mirror_registry']} --index-filter-by-os='.*'"
        )
        oc_acm_result = exec_cmd(cmd, timeout=7200)

        for line in oc_acm_result.stdout.decode("utf-8").splitlines():
            if "wrote mirroring manifests to" in line:
                break
        else:
            raise NotFoundError(
                "Manifests directory not printed to stdout of 'oc adm catalog mirror ...' command."
            )
        mirroring_manifests_dir = line.replace("wrote mirroring manifests to ",
                                               "")
        logger.debug(
            f"Mirrored manifests directory: {mirroring_manifests_dir}")

        # create ImageContentSourcePolicy
        icsp_file = os.path.join(
            f"{mirroring_manifests_dir}",
            "imageContentSourcePolicy.yaml",
        )
        exec_cmd(f"oc apply -f {icsp_file}")

        # Disable the default OperatorSources
        exec_cmd(
            """oc patch OperatorHub cluster --type json """
            """-p '[{"op": "add", "path": "/spec/disableAllDefaultSources", "value": true}]'"""
        )

        # create redhat-operators CatalogSource
        catalog_source_data = templating.load_yaml(
            constants.CATALOG_SOURCE_YAML)

        catalog_source_manifest = tempfile.NamedTemporaryFile(
            mode="w+", prefix="catalog_source_manifest", delete=False)
        catalog_source_data["spec"]["image"] = f"{mirrored_index_image}"
        catalog_source_data["metadata"]["name"] = "redhat-operators"
        catalog_source_data["spec"][
            "displayName"] = "Red Hat Operators - Mirrored"
        # remove ocs-operator-internal label
        catalog_source_data["metadata"]["labels"].pop("ocs-operator-internal",
                                                      None)

        templating.dump_data_to_temp_yaml(catalog_source_data,
                                          catalog_source_manifest.name)
        exec_cmd(f"oc apply -f {catalog_source_manifest.name}")
        catalog_source = CatalogSource(
            resource_name="redhat-operators",
            namespace=constants.MARKETPLACE_NAMESPACE,
        )
        # Wait for catalog source is ready
        catalog_source.wait_for_state("READY")

        return

    if config.DEPLOYMENT.get("stage_rh_osbs"):
        raise NotImplementedError(
            "Disconnected installation from stage is not implemented!")

    if upgrade:
        ocs_registry_image = config.UPGRADE.get("upgrade_ocs_registry_image",
                                                "")
    else:
        ocs_registry_image = config.DEPLOYMENT.get("ocs_registry_image", "")
    logger.debug(f"ocs-registry-image: {ocs_registry_image}")
    ocs_registry_image_and_tag = ocs_registry_image.rsplit(":", 1)
    image_tag = (ocs_registry_image_and_tag[1]
                 if len(ocs_registry_image_and_tag) == 2 else None)
    if not image_tag and config.REPORTING.get("us_ds") == "DS":
        image_tag = get_latest_ds_olm_tag(
            upgrade=False if upgrade else config.UPGRADE.get("upgrade", False),
            latest_tag=config.DEPLOYMENT.get("default_latest_tag", "latest"),
        )
        ocs_registry_image = f"{config.DEPLOYMENT['default_ocs_registry_image'].split(':')[0]}:{image_tag}"
    bundle_image = f"{constants.OCS_OPERATOR_BUNDLE_IMAGE}:{image_tag}"
    logger.debug(f"ocs-operator-bundle image: {bundle_image}")

    csv_yaml = get_csv_from_image(bundle_image)
    ocs_operator_image = (csv_yaml.get("spec", {}).get("install", {}).get(
        "spec",
        {}).get("deployments",
                [{}])[0].get("spec",
                             {}).get("template",
                                     {}).get("spec",
                                             {}).get("containers",
                                                     [{}])[0].get("image"))
    logger.debug(f"ocs-operator-image: {ocs_operator_image}")

    # prepare list related images (bundle, registry and operator images and all
    # images from relatedImages section from csv)
    ocs_related_images = []
    ocs_related_images.append(get_image_with_digest(bundle_image))
    ocs_registry_image_with_digest = get_image_with_digest(ocs_registry_image)
    ocs_related_images.append(ocs_registry_image_with_digest)
    ocs_related_images.append(get_image_with_digest(ocs_operator_image))
    ocs_related_images += [
        image["image"] for image in csv_yaml.get("spec").get("relatedImages")
    ]
    logger.debug(f"OCS Related Images: {ocs_related_images}")

    mirror_registry = config.DEPLOYMENT["mirror_registry"]
    # prepare images mapping file for mirroring
    mapping_file_content = [
        f"{image}={mirror_registry}{image[image.index('/'):image.index('@')]}\n"
        for image in ocs_related_images
    ]
    logger.debug(f"Mapping file content: {mapping_file_content}")

    name = "ocs-images"
    mapping_file = os.path.join(config.ENV_DATA["cluster_path"],
                                f"{name}-mapping.txt")
    # write mapping file to disk
    with open(mapping_file, "w") as f:
        f.writelines(mapping_file_content)

    # prepare ImageContentSourcePolicy for OCS images
    with open(constants.TEMPLATE_IMAGE_CONTENT_SOURCE_POLICY_YAML) as f:
        ocs_icsp = yaml.safe_load(f)

    ocs_icsp["metadata"]["name"] = name
    ocs_icsp["spec"]["repositoryDigestMirrors"] = []
    for image in ocs_related_images:
        ocs_icsp["spec"]["repositoryDigestMirrors"].append({
            "mirrors":
            [f"{mirror_registry}{image[image.index('/'):image.index('@')]}"],
            "source":
            image[:image.index("@")],
        })
    logger.debug(f"OCS imageContentSourcePolicy: {yaml.safe_dump(ocs_icsp)}")

    ocs_icsp_file = os.path.join(config.ENV_DATA["cluster_path"],
                                 f"{name}-imageContentSourcePolicy.yaml")
    with open(ocs_icsp_file, "w+") as fs:
        yaml.safe_dump(ocs_icsp, fs)

    # create ImageContentSourcePolicy
    exec_cmd(f"oc apply -f {ocs_icsp_file}")

    # mirror images based on mapping file
    with prepare_customized_pull_secret(ocs_related_images) as authfile_fo:
        login_to_mirror_registry(authfile_fo.name)
        exec_cmd(
            f"oc image mirror --filter-by-os='.*' -f {mapping_file} --insecure "
            f"--registry-config={authfile_fo.name} --max-per-registry=2",
            timeout=3600,
        )

        # mirror also OCS registry image with the original version tag (it will
        # be used for creating CatalogSource)
        mirrored_ocs_registry_image = (
            f"{mirror_registry}{ocs_registry_image[ocs_registry_image.index('/'):]}"
        )
        exec_cmd(
            f"podman push --tls-verify=false --authfile {authfile_fo.name} "
            f"{ocs_registry_image} {mirrored_ocs_registry_image}")

    # Disable the default OperatorSources
    exec_cmd(
        """oc patch OperatorHub cluster --type json """
        """-p '[{"op": "add", "path": "/spec/disableAllDefaultSources", "value": true}]'"""
    )

    # wait for newly created imageContentSourcePolicy is applied on all nodes
    wait_for_machineconfigpool_status("all")

    return mirrored_ocs_registry_image
예제 #2
0
def prune_and_mirror_index_image(
    index_image, mirrored_index_image, packages, icsp=None
):
    """
    Prune given index image and push it to mirror registry, mirror all related
    images to mirror registry and create relevant imageContentSourcePolicy

    Args:
        index_image (str): index image which will be pruned and mirrored
        mirrored_index_image (str): mirrored index image which will be pushed to
            mirror registry
        packages (list): list of packages to keep
        icsp (dict): ImageContentSourcePolicy used for mirroring (workaround for
            stage images, which are pointing to different registry than they
            really are)

    Returns:
        str: path to generated catalogSource.yaml file

    """
    get_opm_tool()
    pull_secret_path = os.path.join(constants.TOP_DIR, "data", "pull-secret")

    # prune an index image
    logger.info(
        f"Prune index image {index_image} -> {mirrored_index_image} "
        f"(packages: {', '.join(packages)})"
    )
    cmd = (
        f"opm index prune -f {index_image} "
        f"-p {','.join(packages)} "
        f"-t {mirrored_index_image}"
    )
    if config.DEPLOYMENT.get("opm_index_prune_binary_image"):
        cmd += (
            f" --binary-image {config.DEPLOYMENT.get('opm_index_prune_binary_image')}"
        )
    # opm tool doesn't have --authfile parameter, we have to supply auth
    # file through env variable
    os.environ["REGISTRY_AUTH_FILE"] = pull_secret_path
    exec_cmd(cmd)

    # login to mirror registry
    login_to_mirror_registry(pull_secret_path)

    # push pruned index image to mirror registry
    logger.info(f"Push pruned index image to mirror registry: {mirrored_index_image}")
    cmd = f"podman push --authfile {pull_secret_path} --tls-verify=false {mirrored_index_image}"
    exec_cmd(cmd)

    # mirror related images (this might take very long time)
    logger.info(f"Mirror images related to index image: {mirrored_index_image}")
    cmd = (
        f"oc adm catalog mirror {mirrored_index_image} -a {pull_secret_path} --insecure "
        f"{config.DEPLOYMENT['mirror_registry']} --index-filter-by-os='.*' --max-per-registry=2"
    )
    oc_acm_result = exec_cmd(cmd, timeout=7200)

    for line in oc_acm_result.stdout.decode("utf-8").splitlines():
        if "wrote mirroring manifests to" in line:
            break
    else:
        raise NotFoundError(
            "Manifests directory not printed to stdout of 'oc adm catalog mirror ...' command."
        )
    mirroring_manifests_dir = line.replace("wrote mirroring manifests to ", "")
    logger.debug(f"Mirrored manifests directory: {mirroring_manifests_dir}")

    if icsp:
        # update mapping.txt file with urls updated based on provided
        # imageContentSourcePolicy
        mapping_file = os.path.join(
            f"{mirroring_manifests_dir}",
            "mapping.txt",
        )
        with open(mapping_file) as mf:
            mapping_file_content = []
            for line in mf:
                # exclude mirrored_index_image
                if mirrored_index_image in line:
                    continue
                # apply any matching policy to all lines from mapping file
                for policy in icsp["spec"]["repositoryDigestMirrors"]:
                    # we use only first defined mirror for particular source,
                    # because we don't use any ICSP with more mirrors for one
                    # source and it will make the logic very complex and
                    # confusing
                    line = line.replace(policy["source"], policy["mirrors"][0])
                mapping_file_content.append(line)
        # write mapping file to disk
        mapping_file_updated = os.path.join(
            f"{mirroring_manifests_dir}",
            "mapping_updated.txt",
        )
        with open(mapping_file_updated, "w") as f:
            f.writelines(mapping_file_content)
        # mirror images based on the updated mapping file
        # ignore errors, because some of the images might be already mirrored
        # via the `oc adm catalog mirror ...` command and not available on the
        # mirror
        exec_cmd(
            f"oc image mirror --filter-by-os='.*' -f {mapping_file_updated} "
            f"--insecure --registry-config={pull_secret_path} "
            "--max-per-registry=2 --continue-on-error=true --skip-missing=true",
            timeout=3600,
            ignore_error=True,
        )

    # create ImageContentSourcePolicy
    icsp_file = os.path.join(
        f"{mirroring_manifests_dir}",
        "imageContentSourcePolicy.yaml",
    )
    exec_cmd(f"oc apply -f {icsp_file}")
    logger.info("Sleeping for 60 sec to start update machineconfigpool status")
    time.sleep(60)
    wait_for_machineconfigpool_status("all")

    cs_file = os.path.join(
        f"{mirroring_manifests_dir}",
        "catalogSource.yaml",
    )
    return cs_file