diff --git a/.gitignore b/.gitignore index dd6db73f5..64f916db5 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ rhdh-operator-*.yaml !tests/e2e/testdata/rhdh-operator-*.yaml rhdh-disconnected-install/ rhdh-disconnected-install.Dockerfile + +.oc-mirror.log \ No newline at end of file diff --git a/.rhdh/docs/airgap.adoc b/.rhdh/docs/airgap.adoc index 8ec2976b0..ed9a2dd10 100644 --- a/.rhdh/docs/airgap.adoc +++ b/.rhdh/docs/airgap.adoc @@ -1,106 +1,205 @@ -==== Installing Red Hat Developer Hub (RHDH) in restricted environments +== Deploying the Red Hat Developer Hub (RHDH) Operator and RHDH instances in disconnected environments -WARNING: The procedure below will not work properly on OpenShift clusters with hosted control planes, like link:https://hypershift-docs.netlify.app/[HyperShift] or link:https://www.redhat.com/en/blog/red-hat-openshift-service-aws-hosted-control-planes-now-available[ROSA with hosted control planes]. This is due to a limitation preventing link:https://docs.openshift.com/container-platform/4.14/rest_api/operator_apis/imagecontentsourcepolicy-operator-openshift-io-v1alpha1.html[`ImageContentSourcePolicy`] resources from being propagated to the cluster nodes. There is currently no workaround for these clusters. +A disconnected or restricted environment is an environment that does not have full access to the internet. So the cluster will not be able to pull any images from public registries like the Red Hat Ecosystem Catalog. -On an OpenShift cluster operating in a restricted network, public resources are not available. -However, deploying the RHDH Operator and running RHDH requires the following public resources: +Deploying the RHDH Operator and running RHDH requires the following public resources: * Operator images (bundle, operator, catalog) * Operands images (RHDH, PostgreSQL) -To make these resources available, we will need to replace them with their copies in a mirror registry accessible by the OpenShift cluster. +To make these resources available, you will need to replace them with their copies in a mirror registry accessible by the cluster. -We provide a helper script that mirrors all the necessary images and does the necessary plumbing to ensure those images will be used when installing the RHDH Operator and creating RHDH instances. +We provide a helper script that mirrors all the necessary images and does all the heavy lifting to ensure those images will be used when installing the RHDH Operator and creating RHDH instances. -This requires a target mirror registry, which you should already have if your OpenShift cluster is already ready to operate on a restricted network. +This requires a target mirror registry, which you should already have if your cluster is already operating on a restricted network. If not and if you have an OpenShift cluster, you may want to expose and leverage the internal cluster registry. -However, if you are preparing your cluster for disconnected usage, the script can also deploy a mirror registry in the cluster and use it for the mirroring process. +Note that you can mirror the images to a mirror registry in a link:#_partially_disconnected_environments[partially disconnected environment] or in a link:#_fully_disconnected_environments[fully disconnected environment]. + +Below are the prerequisites regardless of the target disconnected environment. + +*Prerequisites* + +* `GNU sed`. See link:https://www.gnu.org/software/sed/[GNU sed]. +* (*Optional*) `oc-mirror` (the OpenShift Client mirror plugin) if you want to mirror using `oc-mirror` on OpenShift. See link:https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html-single/disconnected_environments/index#installation-oc-mirror-installing-plugin_about-installing-oc-mirror-v2[Installing the oc-mirror OpenShift CLI plugin]. +* `Skopeo 1.17+`. See link:https://github.com/containers/skopeo/blob/main/install.md[Installing Skopeo]. +* `umoci`. See link:https://github.com/opencontainers/umoci#install[Install]. +* `yq v4.44+`. See link:https://github.com/mikefarah/yq#install[Install]. +* (*Required on non-OpenShift clusters*) The cluster needs to have the Operator Lifecycle Manager installed and already working in disconnected environments. See link:https://github.com/operator-framework/operator-lifecycle-manager/blob/master/doc/install/install.md#installing-olm[Installing OLM]. +* (*Required on non-OpenShift clusters*) A mirror registry reachable from the cluster. + +=== Mirroring and deploying the RHDH Operator + +[#_partially_disconnected_environments] +==== Partially disconnected environments + +In a partially disconnected environment, you (or the machine where you are running the mirroring tool) has direct access to the mirror registry. + +Besides the prerequisites listed above, you will also need: *Prerequisites* -* An active `oc` session with administrative permissions to the OpenShift cluster. See link:https://docs.openshift.com/container-platform/4.14/cli_reference/openshift_cli/getting-started-cli.html[Getting started with the OpenShift CLI]. * An active `oc registry` session to the `registry.redhat.io` Red Hat Ecosystem Catalog. See link:https://access.redhat.com/RegistryAuthentication[Red Hat Container Registry Authentication]. -* `opm`. See link:https://docs.openshift.com/container-platform/4.14/cli_reference/opm/cli-opm-install.html[Installing the opm CLI]. -* `jq`. See link:https://jqlang.github.io/jq/download/[Download jq]. -* `podman`. See link:https://podman.io/docs/installation[Podman Installation Instructions]. -* `skopeo` version 1.14 or higher. See link:https://github.com/containers/skopeo/blob/main/install.md[Installing Skopeo]. -* If you already have a mirror registry for your cluster, an active `skopeo` session with administrative access to this registry is required. See link:https://github.com/containers/skopeo#authenticating-to-a-registry[Authenticating to a registry] and link:https://docs.openshift.com/container-platform/4.14/installing/disconnected_install/installing-mirroring-installation-images.html[Mirroring images for a disconnected installation]. -+ -NOTE: The internal OpenShift cluster image registry cannot be used as target mirror registry. See link:https://docs.openshift.com/container-platform/4.14/installing/disconnected_install/installing-mirroring-installation-images.html#installation-about-mirror-registry_installing-mirroring-installation-images[About the mirror registry]. -* If you prefer to create your own mirror registry, refer to link:https://docs.openshift.com/container-platform/4.14/installing/disconnected_install/installing-mirroring-creating-registry.html[Creating a mirror registry with mirror registry for Red Hat OpenShift]. -* If you don't already have a mirror registry and want the helper script to create one for you: -** `curl`. On Red Hat Enterprise Linux, this is available by installing the `curl` package; for other platforms, see link:https://curl.se/[the cURL website]. +* An active `skopeo` session with administrative access to the target mirror registry is required. See link:https://github.com/containers/skopeo#authenticating-to-a-registry[Authenticating to a registry]. The script can detect if you are connected to an OpenShift cluster and will automatically expose the cluster registry, unless you specify the target registry to mirror to. +* `opm`. See link:https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/cli_tools/opm-cli#olm-about-opm_cli-opm-install[Installing the opm CLI]. +* `podman 5.3+`. See link:https://podman.io/docs/installation[Podman Installation Instructions]. + +**Procedure** + +. Download and execute the mirroring script to install the RHDH Operator catalog and mirror the related images: link:../scripts/prepare-restricted-environment.sh[`prepare-restricted-environment.sh`] + [source,console] ---- -$ sudo yum install curl +# +# For GA releases +# + +# NOTES: +# 1. Omit `--to-registry` if you are connected against an OpenShift cluster and want the script to expose the internal cluster registry and use that for mirroring. +# 2. Omit `--use-oc-mirror` if you don't want to use `oc-mirror`. `oc-mirror` makes sense only if targetting OpenShift. + +bash prepare-restricted-environment.sh \ + [--to-registry my.registry.example.com/namespace] \ + [--use-oc-mirror true] + +#---------------------------------------------------- + +# +# For CI Builds +# + +# NOTES: +# 1. Omit `--to-registry` if you are connected against an OpenShift cluster and want the script to expose the internal cluster registry and use that for mirroring. +# 2. Omit `--use-oc-mirror` if you don't want to use `oc-mirror`. `oc-mirror` makes sense only if targetting OpenShift. +# 3. Specify `*` for `--filter-versions` to disable version filtering. It will include all channels of the rhdh package from the index image. + +bash prepare-restricted-environment.sh \ + --index-image "quay.io/rhdh/iib:next-v4.18-x86_64" \ + [--filter-versions '*'] \ + [--to-registry my.registry.example.com/namespace] \ + [--use-oc-mirror true] ---- -** `htpasswd` from your package manager. On Red Hat Enterprise Linux, this is available by installing the `httpd-tools` package: ++ +Be patient, the script can take several minutes to complete. It will automatically install the RHDH Operator. + +*Verification* + +* On OpenShift, you should see the Red Hat Developer Hub Operator in the list of Installed Operators in the web console. +* In a vanilla Kubernetes cluster, you can check the list of Pods running in the `rhdh-operator` namespace: + [source,console] ---- -$ sudo yum install httpd-tools +kubectl -n rhdh-operator get pods ---- +[#_fully_disconnected_environments] +==== Fully disconnected environments + +In a fully disconnected environment, you usually need to connect to an intermediate bastion host or may even need to move physically there in order to reach the target mirror registry. In this context, you will need to follow the process below: + +. link:#_mirroring_to_disk[*Mirror all the required images to disk*]. +. *Manually transfer* the mirror folder from the step above to the network of the disconnected mirror registry. +. In the disconnected environment, link:#_mirroring_from_disk[*mirror the images from disk*] to the target mirror registry and install the operator. + +[#_mirroring_to_disk] +===== Mirroring to disk + +Besides the prerequisites listed above, you will also need: + +*Prerequisites* + +* An active `oc registry` session to the `registry.redhat.io` Red Hat Ecosystem Catalog. See link:https://access.redhat.com/RegistryAuthentication[Red Hat Container Registry Authentication]. +* `opm`. See link:https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/cli_tools/opm-cli#olm-about-opm_cli-opm-install[Installing the opm CLI]. +* `podman 5.3+`. See link:https://podman.io/docs/installation[Podman Installation Instructions]. + **Procedure** -. Download and execute the mirroring script to install a custom Operator catalog and mirror the related images: `prepare-restricted-environment.sh` (link:https://github.com/redhat-developer/rhdh-operator/blob/main/.rhdh/scripts/prepare-restricted-environment.sh[source]) +. Download and execute the mirroring script to mirror the related images to disk: link:../scripts/prepare-restricted-environment.sh[`prepare-restricted-environment.sh`] + [source,console] ---- -$ # -$ # For GA releases -$ # - -$ # if you don't already have a target mirror registry -$ # and want the script to create one for you. -$ bash prepare-restricted-environment.sh \ - --prod_operator_index "registry.redhat.io/redhat/redhat-operator-index:v4.14" \ - --prod_operator_package_name "rhdh" \ - --prod_operator_bundle_name "rhdh-operator" \ - --prod_operator_version "v1.1.0" - -$ # or, if you already have a target mirror registry -$ bash prepare-restricted-environment.sh \ - --prod_operator_index "registry.redhat.io/redhat/redhat-operator-index:v4.14" \ - --prod_operator_package_name "rhdh" \ - --prod_operator_bundle_name "rhdh-operator" \ - --prod_operator_version "v1.1.0" \ - --use_existing_mirror_registry "" - -$ #---------------------------------------------------- - -$ # -$ # For CI Builds -$ # - -$ # if you don't already have a target mirror registry -$ # and want the script to create one for you. -$ bash prepare-restricted-environment.sh \ - --prod_operator_index "quay.io/rhdh/iib:latest-v4.14-x86_64" \ - --prod_operator_package_name "rhdh" \ - --prod_operator_bundle_name "rhdh-operator" \ - --prod_operator_version "v1.1.0" - -$ # or, if you already have a target mirror registry -$ bash prepare-restricted-environment.sh \ - --prod_operator_index "quay.io/rhdh/iib:latest-v4.14-x86_64" \ - --prod_operator_package_name "rhdh" \ - --prod_operator_bundle_name "rhdh-operator" \ - --prod_operator_version "v1.1.0" \ - --use_existing_mirror_registry "" +# +# For GA releases +# + +# NOTES: +# 1. Omit `--use-oc-mirror` if you don't want to use `oc-mirror`. `oc-mirror` makes sense only if targetting OpenShift. + +bash prepare-restricted-environment.sh \ + --to-dir /absolute/path/to/my/mirror/folder \ + [--use-oc-mirror true] + +#---------------------------------------------------- + +# +# For CI Builds +# + +# NOTES: +# 1. Omit `--use-oc-mirror` if you don't want to use `oc-mirror`. `oc-mirror` makes sense only if targetting OpenShift. +# 2. Specify `*` for `--filter-versions` to disable version filtering. It will include all channels of the rhdh package from the index image. + +bash prepare-restricted-environment.sh \ + --index-image "quay.io/rhdh/iib:next-v4.18-x86_64" \ + --to-dir /absolute/path/to/my/mirror/folder \ + [--filter-versions '*'] \ + [--use-oc-mirror true] ---- + -Be patient, the script can take several minutes to complete. -Once done, you can make sure your cluster is disconnected from the public internet and install the RHDH operator. -. Log in as an _administrator_ to the OpenShift web console. -. In the *Administrator* view of the OpenShift web console, go to *Operators* → *OperatorHub* and search for *Red Hat Developer Hub*. +Be patient, the script can take several minutes to complete. It will pull all the necessary images into the directory specified in `--to-dir`. + +. Now transfer the directory specified in `--to-dir` to your disconnected environment. + +[#_mirroring_from_disk] +===== Mirroring from disk + +This needs to be executed on a machine which has access to both the cluster and the target mirror registry in your fully disconnected environment. + +Besides the prerequisites listed above, you will also need on this machine: + +*Prerequisites* + +* `podman 5.3+`. See link:https://podman.io/docs/installation[Podman Installation Instructions]. + +**Procedure** + +NOTE: If you used `oc-mirror` to mirror to disk, you must also use `oc-mirror` when mirroring from disk, since `oc-mirror` uses a different folder layout. + +. Download and execute the mirroring script to install the RHDH Operator catalog and mirror the related images: link:../scripts/prepare-restricted-environment.sh[`prepare-restricted-environment.sh`] ++ +[source,console] +---- + +# NOTES: +# 1. Omit `--to-registry` if you are connected against an OpenShift cluster and want the script to expose the internal cluster registry and use that for mirroring. +# 2. Omit `--use-oc-mirror` if you don't want to use `oc-mirror`. `oc-mirror` makes sense only if targetting OpenShift. + +bash prepare-restricted-environment.sh \ + --from-dir /absolute/path/to/my/mirror/folder \ + [--to-registry my.registry.example.com/namespace] \ + [--use-oc-mirror true] +---- + -image::images/airgap/rhdh_catalog_operatorhub.png[RHDH CatalogSource from Operator Hub] -. Install the Red Hat Developer Hub Operator. For more info, see https://docs.openshift.com/container-platform/4.14/operators/admin/olm-adding-operators-to-cluster.html#olm-installing-from-operatorhub-using-web-console_olm-adding-operators-to-a-cluster[Installing from OperatorHub using the web console]. +Be patient, the script can take several minutes to complete. It will automatically install the RHDH Operator. + +*Verification* + +* On OpenShift, you should see the Red Hat Developer Hub Operator in the list of Installed Operators in the web console. +* In a vanilla Kubernetes cluster, you can check the list of Pods running in the `rhdh-operator` namespace: + -image::images/airgap/rhdh_operator_install_ok.png[RHDH Operator Successful installation] -. Create an OpenShift project to be used by your Backstage instance. +[source,console] +---- +kubectl -n rhdh-operator get pods +---- + +=== Deploying RHDH + +Once the Operator has been installed in your disconnected cluster, you can proceed to deploying an Operator-backed instance of RHDH. + +==== OpenShift + +*Procedure* + +. Create a namespace project to be used by your Backstage instance. For more information about creating a project in OpenShift, see the https://docs.openshift.com/container-platform/4.14/applications/projects/working-with-projects.html#creating-a-project-using-the-web-console_projects[Red Hat OpenShift documentation]. . Switch to the *Developer* perspective in your Red Hat OpenShift web console. . Click *+Add*. @@ -112,3 +211,33 @@ For more information about creating a project in OpenShift, see the https://docs + image::images/rhdh_from_operator.png[RHDH from Operator] . See link:openshift.adoc#_configurations_for_operator_backed_rhdh[Configurations for Operator-backed RHDH] for further details about configuring your RHDH instance. + +==== Kubernetes + +On a vanilla Kubernetes, you can create a Custom Resource (CR) using `kubectl`, like so: + +[source,yaml] +---- +cat </dev/null; then + errorf "Error: Required tool '$1' is not installed." + exit 1 + fi +} + +function usage() { + echo " +This script streamlines the installation of the Red Hat Developer Hub Operator in a disconnected OpenShift or Kubernetes cluster. +It supports partially disconnected as well as fully disconnected environments. +In a partially disconnected environment, the host from which this script is executed has access to the Internet and the Red Hat ecosystem catalog, +and can push the images directly to the mirror registry and the cluster. +In a fully disconnected environment however, everything needs to be mirrored to disk first, then transferred to the +disconnected environment (usually via a bastion host), from where we can connect to the mirror registry and the cluster. + +Usage: + $0 [OPTIONS] + +Options: + --index-image : Operator index image (default: registry.redhat.io/redhat/redhat-operator-index:v4.17) + --filter-versions : Comma-separated list of operator minor versions to keep in the catalog (default: 1.3,1.4). + Specify '*' to disable version filtering and include all channels and all versions. Useful for CI index images for example. + --to-registry : Mirror the images into the specified registry, assuming you are already logged into it. + If this is not set and --to-dir is not set, it will attempt to use the builtin OCP registry + if the target cluster is OCP. Otherwise, it will error out. + It also assumes you are logged into the target cluster as well. + --to-dir : Mirror images into the specified directory. Needs to be an absolute path. + This is useful if you are working in a fully disconnected environment and + you must manually transfer the images to your network. + From there, you will be able to re-run this script with '--from-dir' to push + the images to your private registry. + --from-dir : Load images from the specified directory. Needs to be an absolute path. + This is useful if you are working in a fully disconnected environment. + In this case, you would use '--to-dir' first to mirror images to a specified directory, + then transfer this dir over to your disconnected network. + From there, you will be able to re-run this script with '--from-dir' to push + the images to your private registry. + --install-operator : Install the RHDH operator right after creating the CatalogSource (default: true) + --extra-images : Comma-separated list of extra images to mirror + --use-oc-mirror : Whether to use the 'oc-mirror' tool (default: false). + This is the recommended way for mirroring on regular OpenShift clusters. + Bear in mind however that this relies on resources like ImageContentSourcePolicy, + which don't seem to work well on ROSA clusters or clusters with hosted control + planes (like HyperShift or Red Hat OpenShift on IBM Cloud). + --oc-mirror-path : Path to the oc-mirror binary (default: 'oc-mirror'). Expected to be in the system PATH. + --oc-mirror-flags : Additional flags to pass to all oc-mirror commands. + +Examples: + + # Install the Catalog Source by pushing the images to the internal OCP mirror registry, + # because it detected that it is connected to an OCP cluster. + $0 + + # Install the Catalog Source by pushing the images to the specified mirror registry, assuming the user is already logged into it. + $0 \\ + --to-registry registry.example.com + + # Extract all the images needed into the specified directory. + $0 \\ + --to-dir /path/to/my/dir + + # From a bastion host connected to the disconnected network, + # install the operator by using the images from the specified directory. + $0 \\ + --from-dir /path/to/my/dir \\ + --to-registry registry.example.com +" +} + +INDEX_IMAGE="registry.redhat.io/redhat/redhat-operator-index:v4.17" +OPERATOR_NAME="rhdh-operator" + +TO_REGISTRY="" +INSTALL_OPERATOR="true" +TO_DIR="" +FROM_DIR="" +FILTERED_VERSIONS=(1.3 1.4) +EXTRA_IMAGES=() +USE_OC_MIRROR="false" +OC_MIRROR_PATH="oc-mirror" +OC_MIRROR_FLAGS="" + +NO_VERSION_FILTER="false" +RELATED_IMAGES=() # example usage: # ./prepare-restricted-environment.sh \ -# --prod_operator_index "registry.redhat.io/redhat/redhat-operator-index:v4.14" \ -# --prod_operator_package_name "rhdh" \ -# --prod_operator_bundle_name "rhdh-operator" \ -# --prod_operator_version "v1.1.0" \ -# --helper_mirror_registry_storage "30Gi" \ -# --use_existing_mirror_registry "$MY_MIRROR_REGISTRY" -while [ $# -gt 0 ]; do - if [[ $1 == *"--"* ]]; then - param="${1/--/}" - declare "$param"="$2" - fi - shift +# [ --filter-versions "1.3,1.4" ] +# --from-dir /path/to/dir (to support mirroring from a bastion host) +# --to-dir /path/to/dir (to support exporting images to a dir, which can be transferred to the bastion host) +# --to-registry "$MY_MIRROR_REGISTRY" (either this or to-dir needs to specified, both can be specified) +# --install-operator "true" +# --use-oc-mirror "false" + +while [[ "$#" -gt 0 ]]; do + case $1 in + # Legacy options. Deprecated but kept for backward compatibility + '--prod_operator_index') INDEX_IMAGE="$2"; shift 1;; + '--prod_operator_package_name') debugf "--prod_operator_package_name ($2) is no longer used"; shift 1;; + '--prod_operator_bundle_name') debugf "--prod_operator_bundle_name ($2) is no longer used"; shift 1;; + '--helper_mirror_registry_storage') + debugf "--helper_mirror_registry_storage is no longer used. This script assumes you already have a mirror registry in your disconnected environment"; + shift 1;; + '--use_existing_mirror_registry') + debugf "--use_existing_mirror_registry is no longer used. This script assumes you already have a mirror registry in your disconnected environment"; + shift 1;; + '--prod_operator_version') + input="${2#v}" + IFS='.' read -ra parts <<< "$input" + length=${#parts[@]} + if [ $length -ge 2 ]; then + FILTERED_VERSIONS=(${parts[0]}.${parts[1]}) + else + FILTERED_VERSIONS=(${parts[*]}) + fi + debugf "FILTERED_VERSIONS=${FILTERED_VERSIONS[@]}" + shift 1;; + + # New options + '--index-image') INDEX_IMAGE="$2"; shift 1;; + '--filter-versions') + if [[ "$2" == "*" ]]; then + NO_VERSION_FILTER="true" + else + IFS=',' read -r -a FILTERED_VERSIONS <<< "$2" + fi + shift 1;; + '--extra-images') IFS=',' read -r -a EXTRA_IMAGES <<< "$2"; shift 1;; + '--to-registry') TO_REGISTRY="$2"; shift 1;; + '--to-dir') TO_DIR=$(realpath "$2"); shift 1;; + '--from-dir') FROM_DIR="$2"; shift 1;; + '--install-operator') INSTALL_OPERATOR="$2"; shift 1;; + '--use-oc-mirror') USE_OC_MIRROR="$2"; shift 1;; + '--oc-mirror-path') OC_MIRROR_PATH="$2"; shift 1;; + '--oc-mirror-flags') OC_MIRROR_FLAGS="$2"; shift 1;; + '-h'|'--help') usage; exit 0;; + *) errorf "Unknown parameter is used: $1."; usage; exit 1;; + esac + shift 1 done -# Display commands -# set -x +function is_openshift() { + set -euo pipefail + + oc get routes.route.openshift.io &> /dev/null || kubectl get routes.route.openshift.io &> /dev/null +} + +function detect_ocp_and_set_env_var() { + set -euo pipefail + + if [[ "${IS_OPENSHIFT}" = "" ]]; then + IS_OPENSHIFT=$(is_openshift && echo 'true' || echo 'false') + fi +} + +# Wrapper function to call kubectl or oc +function invoke_cluster_cli() { + set -euo pipefail + + local command=$1 + shift -# Operators -declare prod_operator_index="${prod_operator_index:?Must set --prod_operator_index: for OCP 4.12, use registry.redhat.io/redhat/redhat-operator-index:v4.12 or quay.io/rhdh/iib:latest-v4.14-x86_64}" -declare prod_operator_package_name="rhdh" -declare prod_operator_bundle_name="rhdh-operator" -declare prod_operator_version="${prod_operator_version:?Must set --prod_operator_version: for fast or fast-1.y channels, use v1.1.0, v1.1.1, etc.}" + detect_ocp_and_set_env_var + if [[ "${IS_OPENSHIFT}" = "true" ]]; then + if command -v oc &> /dev/null; then + oc "$command" "$@" + else + kubectl "$command" "$@" + fi + else + kubectl "$command" "$@" + fi +} -# Destination registry -declare my_operator_index_image_name_and_tag=${prod_operator_package_name}-index:${prod_operator_version} -declare helper_mirror_registry_storage=${helper_mirror_registry_storage:-"30Gi"} +########################################################################################## +# Script start +########################################################################################## -declare my_catalog=${prod_operator_package_name}-disconnected-install -declare k8s_resource_name=${my_catalog} +# Pre-checks +if [[ "${USE_OC_MIRROR}" = "true" ]]; then + if [[ "${OC_MIRROR_PATH}" == "oc-mirror" ]]; then + check_tool "oc-mirror" + else + if [ ! -f "${OC_MIRROR_PATH}" ] || [ ! -x "${OC_MIRROR_PATH}" ]; then + if ! command -v "$1" >/dev/null; then + errorf "oc-mirror binary not found or not executable: ${OC_MIRROR_PATH}" + exit 1 + fi + fi + debugf "Using oc-mirror path: ${OC_MIRROR_PATH}" + fi +fi -# Check we're logged into a cluster -if ! oc whoami > /dev/null 2>&1; then - errorf "Not logged into an OpenShift cluster" +if [[ -n "${FROM_DIR}" && -n "${TO_DIR}" ]]; then + errorf "--from-dir and --to-dir are mutually exclusive. Please specify only one of them." + exit 1 +fi +if [[ -n "${TO_REGISTRY}" && -n "${TO_DIR}" ]]; then + errorf "--to-registry and --to-dir are mutually exclusive. Please specify only one of them." + exit 1 +fi +if [[ -z "${TO_REGISTRY}" && -z "${TO_DIR}" ]]; then + # If we know that we are connected to OCP, let's use the internal OCP registry + isOcp=$(is_openshift && echo 'true' || echo 'false') + if [[ "$isOcp" != "true" ]]; then + if [[ -n "${FROM_DIR}" ]]; then + errorf "--to-registry is needed when --from-dir is specified." + else + errorf "Please specify either --to-registry or --to-dir (not both). Or log into your OCP cluster to automatically use its integrated image registry." + fi + exit 1 + fi + debugf "--to-registry not specified but detected an OCP cluster => will try to use the internal OCP cluster registry as mirror registry" + TO_REGISTRY="OCP_INTERNAL" +fi +if [[ -n "${FROM_DIR}" && -z "${TO_REGISTRY}" ]]; then + errorf "--to-registry is needed when --from-dir is specified." exit 1 fi -# log into your OCP cluster before running this or you'll get null values for OCP vars! -OCP_VER="$(oc version -o json | jq -r '.openshiftVersion' | sed -r -e "s#([0-9]+\.[0-9]+\.[0-9]+)-.+#\1#")" -OCP_VER_MAJOR="$(oc version -o json | jq -r '.openshiftVersion' | sed -r -e "s#([0-9]+)\..+#\1#")" -OCP_ARCH="$(oc version -o json | jq -r '.serverVersion.platform' | sed -r -e "s#linux/##")" -if [[ $OCP_ARCH == "amd64" ]]; then OCP_ARCH="x86_64"; fi - -function deploy_mirror_registry() { - echo "[INFO] Deploying mirror registry..." >&2 - local namespace="airgap-helper-ns" - local image="registry:2" - local username="registryuser" - local password=$(echo "$RANDOM" | base64 | head -c 20) - - if ! oc get namespace "${namespace}" &> /dev/null; then - echo " namespace ${namespace} does not exist - creating it..." >&2 - oc create namespace "${namespace}" >&2 + +if [[ -n "${TO_DIR}" ]]; then + mkdir -p "${TO_DIR}" + TMPDIR="${TO_DIR}" +else + TMPDIR=$(mktemp -d) + ## shellcheck disable=SC2064 + trap "rm -fr $TMPDIR || true" EXIT +fi +pushd "${TMPDIR}" > /dev/null +debugf ">>> WORKING DIR: $TMPDIR <<<" + +function merge_registry_auth() { + set -euo pipefail + + currentRegistryAuthFile="${REGISTRY_AUTH_FILE:-${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/containers/auth.json}" + debugf "currentRegistryAuthFile: $currentRegistryAuthFile" + if [ ! -f "${currentRegistryAuthFile}" ]; then + debugf "Missing registry auth file. Will proceed without any existing auth against the registry hosting the index image: $INDEX_IMAGE" + return + fi + # TODO(rm3l): Overriding XDG_RUNTIME_DIR so it can work with "oc-mirror v1", which does not work with REGISTRY_AUTH_FILE. + # Remove this when oc-mirror v2 is out of TP. + export XDG_RUNTIME_DIR=$(mktemp -d) + mkdir -p "${XDG_RUNTIME_DIR}/containers" + ## shellcheck disable=SC2064 + trap "rm -fr $XDG_RUNTIME_DIR || true" EXIT + # Using the current working dir, otherwise tools like 'skopeo login' will attempt to write to /run, which + # might be restricted in CI environments. + # This also ensures that the credentials don't conflict with any existing creds for the same registry + export REGISTRY_AUTH_FILE="${XDG_RUNTIME_DIR}/containers/auth.json" + debugf "REGISTRY_AUTH_FILE: $REGISTRY_AUTH_FILE" + + # Merge existing authentication from currentRegistryAuthFile into REGISTRY_AUTH_FILE + images=("${INDEX_IMAGE}") + if [[ -n "${TO_REGISTRY}" ]]; then + images+=("$(buildRegistryUrl)") + fi + registries=() + for img in "${images[@]}"; do + reg=$(echo "$img" | cut -d'/' -f1) + [[ " ${registries[*]} " =~ " $reg " ]] || registries+=("$reg") + done + tmpFile=$(mktemp) + ## shellcheck disable=SC2064 + trap "rm -f $tmpFile || true" EXIT + echo '{"auths": {' > "$tmpFile" + for reg in "${registries[@]}"; do + echo " \"$reg\": .auths.\"$reg\"," >> "$tmpFile" + done + sed -i '$ s/,$//' "$tmpFile" + echo '}}' >> "$tmpFile" + debugf "yq filter: $(cat "$tmpFile")" + yq -o=json "$(cat "$tmpFile")" "${currentRegistryAuthFile}" > "${REGISTRY_AUTH_FILE}" +} + +function ocp_prepare_internal_registry() { + set -euo pipefail + + debugf "Exposing cluster registry..." >&2 + internal_registry_url="image-registry.openshift-image-registry.svc:5000" + oc patch configs.imageregistry.operator.openshift.io/cluster --patch '{"spec":{"defaultRoute":true}}' --type=merge >&2 + # https://access.redhat.com/solutions/6022011 + oc patch configs.imageregistry.operator.openshift.io/cluster --patch '{"spec":{"disableRedirect":true}}' --type=merge >&2 + my_registry=$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}') + skopeo login -u kubeadmin -p "$(oc whoami -t)" --tls-verify=false "$my_registry" >&2 + podman login -u kubeadmin -p "$(oc whoami -t)" --tls-verify=false "$my_registry" >&2 + for ns in rhdh-operator openshift4 rhdh rhel9 oc-mirror; do + # To be able to push images under this scope in the internal image registry + if ! oc get namespace "$ns" &> /dev/null; then + oc create namespace "$ns" >&2 + fi + oc adm policy add-cluster-role-to-user system:image-signer system:serviceaccount:${ns}:default >&2 || true + done + for ns in rhdh-operator openshift-marketplace; do + if oc -n ${ns} get secret internal-reg-ext-auth-for-rhdh &> /dev/null; then + oc -n ${ns} delete secret internal-reg-ext-auth-for-rhdh >&2 + fi + oc -n ${ns} create secret docker-registry internal-reg-ext-auth-for-rhdh \ + --docker-server="${my_registry}" \ + --docker-username=kubeadmin \ + --docker-password="$(oc whoami -t)" \ + --docker-email="admin@internal-registry-ext.example.com" >&2 + if oc -n ${ns} get secret internal-reg-auth-for-rhdh &> /dev/null; then + oc -n ${ns} delete secret internal-reg-auth-for-rhdh >&2 fi + oc -n ${ns} create secret docker-registry internal-reg-auth-for-rhdh \ + --docker-server="${internal_registry_url}" \ + --docker-username=kubeadmin \ + --docker-password="$(oc whoami -t)" \ + --docker-email="admin@internal-registry.example.com" >&2 + oc adm policy add-cluster-role-to-user system:image-signer system:serviceaccount:${ns}:default >&2 || true + done + oc policy add-role-to-user system:image-puller system:serviceaccount:openshift-marketplace:default -n openshift-marketplace >&2 || true + oc policy add-role-to-user system:image-puller system:serviceaccount:rhdh-operator:default -n rhdh-operator >&2 || true + oc policy add-role-to-user system:image-puller system:serviceaccount:rhdh-operator:rhdh-operator -n rhdh-operator >&2 || true +} - registry_htpasswd=$(htpasswd -Bbn "${username}" "${password}") - echo " generating auth secret for mirror registry. FYI, those creds will be stored in a secret named 'airgap-registry-auth-creds' in ${namespace} ..." >&2 - cat <&2 -apiVersion: v1 -kind: Secret -type: Opaque -metadata: - name: airgap-registry-auth - namespace: "${namespace}" - labels: - app: airgap-registry -stringData: - htpasswd: "${registry_htpasswd}" +function buildRegistryUrl() { + set -euo pipefail + + detect_ocp_and_set_env_var + local input="${1:-external}" + if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" ]]; then + if [[ "${input}" == "internal" ]]; then + echo "image-registry.openshift-image-registry.svc:5000" + else + echo $(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}') + fi + else + echo "${TO_REGISTRY}" + fi +} + +function buildCatalogImageUrl() { + if [[ -n "$TO_REGISTRY" ]]; then + tag=${INDEX_IMAGE##*:}; [[ "$INDEX_IMAGE" == "$tag" ]] && tag="latest" + echo "$(buildRegistryUrl "${1:-external}")/${2:-rhdh/index}:${tag}" + else + echo "" + fi +} + +function render_index() { + set -euo pipefail + + mkdir -p "${TMPDIR}/rhdh/rhdh" + local_index_file="${TMPDIR}/rhdh/rhdh/render.yaml" + + debugf "Rendering index image $INDEX_IMAGE as a local file: $local_index_file..." + + prod_operator_package_name="rhdh" + prod_operator_name="${prod_operator_package_name}-operator" + debugf "Fetching metadata for the ${prod_operator_package_name} operator catalog channel, packages, and bundles." + + # Filtering out to keep only the elements related to RHDH and to the versions selected + if [[ "${NO_VERSION_FILTER}" == "true" ]]; then + opm render "${INDEX_IMAGE}" --output=yaml > "${local_index_file}" + else + chanFilterList="" + bundleFilterList="" + chanEntriesFilterList="" + for v in "${FILTERED_VERSIONS[@]}"; do + chanFilterList+='(.schema == "olm.channel" and .package == "'${prod_operator_package_name}'" and .name == "fast-'$v'") or ' + bundleFilterList+='(.schema == "olm.bundle" and .name == "'$prod_operator_name'.v'$v'*") or ' + chanEntriesFilterList+='.name == "'$prod_operator_name'.v'$v'*" or ' + done + chanFilterList="${chanFilterList%"or "}" + bundleFilterList="${bundleFilterList%"or "}" + chanEntriesFilterList="${chanEntriesFilterList%"or "}" + debugf "chanFilterList=$chanFilterList" + debugf "chanEntriesFilterList=$chanEntriesFilterList" + debugf "bundleFilterList=$bundleFilterList" + + opm render "${INDEX_IMAGE}" --output=yaml | \ + yq 'select( + (.schema == "olm.package" and .name == "'${prod_operator_package_name}'") + or + (.schema == "olm.channel" and .package == "'${prod_operator_package_name}'" and .name == "fast") + or + '"$chanFilterList"' + or + '"$bundleFilterList"' + )' | yq '.entries |= map(select('"$chanEntriesFilterList"'))' \ + > "${local_index_file}" + fi + + debugf "Got $(cat "${local_index_file}" | wc -l) lines of JSON from the index!" + + if [ ! -s "${local_index_file}" ]; then + errorf "[ERROR] 'opm render $INDEX_IMAGE' returned an empty output, which likely means that this index Image does not contain the rhdh operator." + return 1 + fi +} + +function extract_last_two_elements() { + local input="$1" + local IFS='/' + + read -ra parts <<< "$input" + + local length=${#parts[@]} + if [ $length -ge 2 ]; then + echo "${parts[-2]}/${parts[-1]}" + else + echo "${parts[*]}" + fi +} + +function mirror_extra_images() { + debugf "Extra images: ${EXTRA_IMAGES[@]}..." + for img in "${EXTRA_IMAGES[@]}"; do + if [[ "$img" == *"@sha256:"* ]]; then + imgDigest="${img##*@sha256:}" + imgDir="./extraImages/${img%@*}/sha256_$imgDigest" + lastTwo=$(extract_last_two_elements "${img%@*}") + targetImg="$(buildRegistryUrl)/${lastTwo}:$imgDigest" + elif [[ "$img" == *":"* ]]; then + imgDir="./extraImages/${img%:*}/tag_$imgTag" + imgTag="${img##*:}" + lastTwo=$(extract_last_two_elements "${img%:*}") + targetImg="$(buildRegistryUrl)/${lastTwo}:$imgTag" + else + imgDir="./extraImages/${img}/tag_latest" + lastTwo=$(extract_last_two_elements "${img}") + targetImg="$(buildRegistryUrl)/${lastTwo}:latest" + fi + + if [[ -n "$TO_REGISTRY" ]]; then + if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" ]]; then + # Create the corresponding project if it doesn't exist + projectNameForOcpReg=${lastTwo%%/*} + oc get namespace "${projectNameForOcpReg}" &>/dev/null || oc create namespace "${projectNameForOcpReg}" + fi + mirror_image_to_registry "$img" "$targetImg" + else + if [ ! -d "$imgDir" ]; then + mkdir -p "${imgDir}" + mirror_image_to_archive "$img" "$imgDir" + fi + fi + done +} + +function mirror_extra_images_from_dir() { + BASE_DIR="${FROM_DIR}/extraImages" + debugf "Extra images from ${BASE_DIR}..." + if [ -d "${BASE_DIR}" ]; then + # Iterate over all directories named "sha256_*" + find "$BASE_DIR" -type d -name "sha256_*" | while read -r sha256_dir; do + relative_path=${sha256_dir#"$BASE_DIR/"} + sha256_hash=${sha256_dir##*/sha256_} + parent_path=$(dirname "$relative_path") + debugf "parent_path: $parent_path" + lastTwo=$(extract_last_two_elements "${parent_path}") + extraImg="${lastTwo}:${sha256_hash}" + debugf "Extra-image: $extraImg" + if [[ -n "$TO_REGISTRY" ]]; then + if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" ]]; then + # Create the corresponding project if it doesn't exist + projectNameForOcpReg=${lastTwo%%/*} + oc get namespace "${projectNameForOcpReg}" &>/dev/null || oc create namespace "${projectNameForOcpReg}" + fi + targetImg="$(buildRegistryUrl)/${extraImg%@*}" + push_image_from_archive "$sha256_dir" "$targetImg" + fi + done + + # Iterate over all directories named "tag_*" + find "$BASE_DIR" -type d -name "tag_*" | while read -r tag_dir; do + relative_path=${tag_dir#"$BASE_DIR/"} + tag_hash=${tag_dir##*/tag_} + parent_path=$(dirname "$relative_path") + debugf "parent_path: $parent_path" + lastTwo=$(extract_last_two_elements "${parent_path}") + extraImg="${lastTwo}:${tag_hash}" + debugf "Extra-image: $extraImg" + if [[ -n "$TO_REGISTRY" ]]; then + if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" ]]; then + # Create the corresponding project if it doesn't exist + projectNameForOcpReg=${lastTwo%%/*} + oc get namespace "${projectNameForOcpReg}" &>/dev/null || oc create namespace "${projectNameForOcpReg}" + fi + targetImg="$(buildRegistryUrl)/${extraImg%:*}" + push_image_from_archive "$tag_dir" "$targetImg" + fi + done + fi +} + +function process_bundles() { + + for bundleImg in $(grep -E '^image: .*operator-bundle' "${TMPDIR}/rhdh/rhdh/render.yaml" | awk '{print $2}' | uniq); do + debugf "bundleImg=$bundleImg" + digest="${bundleImg##*@sha256:}" + if skopeo inspect "docker://$bundleImg" &> /dev/null; then + mkdir -p "bundles/$digest" + debugf "\t copying and unpacking image $bundleImg locally..." + if [ ! -d "./bundles/${digest}/src" ]; then + skopeo copy --remove-signatures "docker://$bundleImg" "oci:./bundles/${digest}/src:latest" + fi + if [ ! -d "./bundles/${digest}/unpacked" ]; then + umoci unpack --image "./bundles/${digest}/src:latest" "./bundles/${digest}/unpacked" --rootless + fi + + debugf "\t inspecting related images referenced in bundle image $bundleImg..." + for file in "./bundles/${digest}/unpacked/rootfs/manifests"/*; do + if [[ "$file" == *.clusterserviceversion.yaml || "$file" == *.csv.yaml ]]; then + debugf "\t Adding imagePullSecrets to the CSV file so we can pull from private registries" + yq eval ' + (.spec.install.spec.deployments[] | select(.name == "rhdh-operator").spec.template.spec) += + {"imagePullSecrets": [{"name": "internal-reg-auth-for-rhdh"},{"name": "internal-reg-ext-auth-for-rhdh"},{"name": "reg-pull-secret"}]} + ' -i "$file" + + all_related_images=() + debugf "\t finding related images in $file to mirror..." + images=$(grep -E 'image: ' "$file" | awk -F ': ' '{print $2}' | uniq) + if [[ -n "$images" ]]; then + all_related_images+=($images) + fi + # TODO(rm3l): we should use spec.relatedImages instead, but it seems to be incomplete in some bundles + related_images=$(yq '.spec.install.spec.deployments[].spec.template.spec.containers[].env[] | select(.name | test("^RELATED_IMAGE_")).value' "$file" || true) + if [[ -n "$related_images" ]]; then + all_related_images+=($related_images) + fi + for relatedImage in "${all_related_images[@]}"; do + relatedImageDigest="${relatedImage##*@sha256:}" + imgDir="./images/${relatedImage%@*}/sha256_$relatedImageDigest" + if [[ -n "$TO_REGISTRY" ]]; then + targetImg="$(buildRegistryUrl)/$(extract_last_two_elements "${relatedImage%@*}"):$relatedImageDigest" + internalTargetImg="$(buildRegistryUrl "internal")/$(extract_last_two_elements "${relatedImage%@*}"):$relatedImageDigest" + mirror_image_to_registry "$relatedImage" "$targetImg" + debugf "replacing $relatedImage in file '${file}' => $internalTargetImg" + sed -i 's#'$relatedImage'#'$internalTargetImg'#g' "$file" + else + if [ ! -d "$imgDir" ]; then + mkdir -p "${imgDir}" + mirror_image_to_archive "$relatedImage" "$imgDir" + fi + fi + done + fi + done + + if [[ -n "$TO_REGISTRY" ]]; then + # repack the image with the changes + debugf "\t Repacking image ./bundles/${digest}/src => ./bundles/${digest}/unpacked..." + umoci repack --image "./bundles/${digest}/src:latest" "./bundles/${digest}/unpacked" + + # Push the bundle to the mirror registry + newBundleImage="$(buildRegistryUrl)/$(extract_last_two_elements "${bundleImg%@*}"):${digest}" + newBundleImageInternal="$(buildRegistryUrl "internal")/$(extract_last_two_elements "${bundleImg%@*}"):${digest}" + debugf "\t Pushing updated bundle image: ./bundles/${digest}/src => ${newBundleImage}..." + skopeo copy --remove-signatures --dest-tls-verify=false "oci:./bundles/${digest}/src:latest" "docker://${newBundleImage}" + + sed -i "s#${bundleImg}#${newBundleImageInternal}#g" "./rhdh/rhdh/render.yaml" + fi + fi + done + + if [ ! -f "rhdh/rhdh.Dockerfile" ]; then + debugf "\t Regenerating Dockerfile so the index can be rebuilt..." + opm generate dockerfile rhdh/rhdh + + if [[ -n "$TO_REGISTRY" ]]; then + infof "Building the catalog image locally." + pushd "rhdh" + my_operator_index="$(buildCatalogImageUrl)" + podman build -t "$my_operator_index" -f "./rhdh.Dockerfile" --no-cache . + + infof "Deploying your catalog image to the $my_operator_index registry." + skopeo copy --remove-signatures --src-tls-verify=false --dest-tls-verify=false --all "containers-storage:$my_operator_index" "docker://$my_operator_index" + popd + fi + fi +} + +function process_bundles_from_dir() { + + if [ ! -f "${FROM_DIR}/rhdh/rhdh.Dockerfile" ]; then + errorf "Missing ${FROM_DIR}/rhdh/rhdh.Dockerfile file. I don't known how to rebuild the index image." + return 1 + fi + + for d in bundles rhdh; do + cp -r "${FROM_DIR}/${d}" "${TMPDIR}/${d}" + done + + for bundleImg in $(grep -E '^image: .*operator-bundle' "${FROM_DIR}/rhdh/rhdh/render.yaml" | awk '{print $2}' | uniq); do + debugf "bundleImg=$bundleImg" + digest="${bundleImg##*@sha256:}" + if [ ! -d "${FROM_DIR}/bundles/${digest}/src" ]; then + warnf "missing src image for bundle digest: ${FROM_DIR}/bundles/${digest}/src" + continue + fi + if [ ! -d "${FROM_DIR}/bundles/${digest}/unpacked" ]; then + warnf "missing unpacked image for bundle digest: ${FROM_DIR}/bundles/${digest}/unpacked" + continue + fi + + debugf "Handling bundle image from ${FROM_DIR}/bundles/${digest}..." + + debugf "\t inspecting related images referenced in bundle image $bundleImg..." + for file in "${TMPDIR}/bundles/${digest}/unpacked/rootfs/manifests"/*; do + if [[ "$file" == *.clusterserviceversion.yaml || "$file" == *.csv.yaml ]]; then + all_related_images=() + debugf "\t finding related images in $file to mirror..." + images=$(grep -E 'image: ' "$file" | awk -F ': ' '{print $2}' | uniq) + if [[ -n "$images" ]]; then + all_related_images+=($images) + fi + # TODO(rm3l): we should use spec.relatedImages instead, but it seems to be incomplete in some bundles + related_images=$(yq '.spec.install.spec.deployments[].spec.template.spec.containers[].env[] | select(.name | test("^RELATED_IMAGE_")).value' "$file" || true) + if [[ -n "$related_images" ]]; then + all_related_images+=($related_images) + fi + for relatedImage in "${all_related_images[@]}"; do + relatedImageDigest="${relatedImage##*@sha256:}" + imgDir="${FROM_DIR}/images/${relatedImage%@*}/sha256_$relatedImageDigest" + if [ ! -d "$imgDir" ]; then + warnf "Skipping related image $relatedImage not found mirrored in dir: $FROM_DIR/images" + continue + fi + if [[ -n "$TO_REGISTRY" ]]; then + targetImg="$(buildRegistryUrl)/$(extract_last_two_elements "${relatedImage%@*}"):$relatedImageDigest" + targetImgInternal="$(buildRegistryUrl "internal")/$(extract_last_two_elements "${relatedImage%@*}"):$relatedImageDigest" + push_image_from_archive "$imgDir" "$targetImg" + debugf "replacing $relatedImage in file '${file}' => $targetImgInternal" + sed -i 's#'$relatedImage'#'$targetImgInternal'#g' "$file" + fi + done + fi + done + + if [[ -n "$TO_REGISTRY" ]]; then + # repack the image with the changes + debugf "\t Repacking image ./bundles/${digest}/src => ./bundles/${digest}/unpacked..." + umoci repack --image "${TMPDIR}/bundles/${digest}/src:latest" "${TMPDIR}/bundles/${digest}/unpacked" + + # Push the bundle to the mirror registry + newBundleImage="$(buildRegistryUrl)/$(extract_last_two_elements "${bundleImg%@*}"):${digest}" + newBundleImageInternal="$(buildRegistryUrl "internal")/$(extract_last_two_elements "${bundleImg%@*}"):${digest}" + debugf "\t Pushing updated bundle image: ./bundles/${digest}/src => ${newBundleImage}..." + skopeo copy --preserve-digests --remove-signatures --dest-tls-verify=false "oci:${TMPDIR}/bundles/${digest}/src:latest" "docker://${newBundleImage}" + + sed -i "s#${bundleImg}#${newBundleImageInternal}#g" "${TMPDIR}/rhdh/rhdh/render.yaml" + fi + done + + if [[ -n "$TO_REGISTRY" ]]; then + pushd "${TMPDIR}/rhdh" + my_operator_index="$(buildCatalogImageUrl)" + debugf "Building the catalog image locally: $my_operator_index" + podman build -t "$my_operator_index" -f "./rhdh.Dockerfile" --no-cache . + + debugf "Deploying your catalog image to the $my_operator_index registry." + skopeo copy --preserve-digests --remove-signatures --src-tls-verify=false --dest-tls-verify=false --all "containers-storage:$my_operator_index" "docker://$my_operator_index" + popd + fi +} + +function mirror_image_to_registry() { + local src_image=$1 + local dest_image=$2 + echo "Mirroring $src_image to $dest_image..." + skopeo copy --preserve-digests --remove-signatures --all --dest-tls-verify=false docker://"$src_image" docker://"$dest_image" +} + +function mirror_image_to_archive() { + local src_image=$1 + local archive_path=$2 + debugf "Saving $src_image to $archive_path..." + skopeo copy --preserve-digests --remove-signatures --all --preserve-digests --dest-tls-verify=false docker://"$src_image" dir:"$archive_path" +} + +function push_image_from_archive() { + local archive_path=$1 + local dest_image=$2 + echo "Pushing $archive_path to $dest_image..." + skopeo copy --preserve-digests --remove-signatures --all --dest-tls-verify=false dir:"$archive_path" docker://"$dest_image" +} + +check_tool "yq" +check_tool "umoci" +check_tool "skopeo" +if [[ -n "$TO_REGISTRY" ]]; then + check_tool "podman" +fi + +if [[ -n "${TO_DIR}" ]]; then + cp -f "${SCRIPT_PATH}" "${TO_DIR}/install.sh" +fi + +detect_ocp_and_set_env_var +if [[ "${IS_OPENSHIFT}" = "true" && "${TO_REGISTRY}" = "OCP_INTERNAL" ]]; then + ocp_prepare_internal_registry +fi + +merge_registry_auth + +manifestsTargetDir="${TMPDIR}" +if [[ -n "${FROM_DIR}" ]]; then + manifestsTargetDir="${FROM_DIR}" +fi + +if [[ "${USE_OC_MIRROR}" = "true" ]]; then + # TODO(rm3l): oc-mirror v1 always loads the docker creds first: + # https://github.com/openshift/oc-mirror/blob/main/pkg/image/credentials.go + # But we want to use our own credentials file, which is not possible until oc-mirror v2 (currently tech preview) + if [ -f ~/.docker/config.json ]; then + debugf "Temporarily moving ~/.docker/config.json to ~/.docker/config.json.bak, so as to work with oc-mirror v1" + mv -f ~/.docker/config.json ~/.docker/config.json.bak || true + trap "mv -f ~/.docker/config.json.bak ~/.docker/config.json || true" EXIT + fi + + NAMESPACE_CATALOGSOURCE="openshift-marketplace" + ocMirrorLogFile="${TMPDIR}/oc-mirror.log.txt" + if [[ -z "${FROM_DIR}" ]]; then + # Direct to registry + cat < "${TMPDIR}/imageset-config.yaml" +apiVersion: mirror.openshift.io/v1alpha2 +kind: ImageSetConfiguration +storageConfig: + local: + # Do not delete or modify metadata generated by the oc-mirror plugin, + # use the same storage backend every time run the oc-mirror plugin for the same mirror + path: ./metadata +mirror: + operators: + - catalog: ${INDEX_IMAGE} + full: false + targetCatalog: rhdh-catalog + packages: + - name: rhdh EOF - cat <&2 -apiVersion: v1 -kind: Secret -type: Opaque -metadata: - name: airgap-registry-auth-creds - namespace: "${namespace}" - labels: - app: airgap-registry -stringData: - username: "${username}" - password: "${password}" + if [[ "${NO_VERSION_FILTER}" != "true" ]]; then + cat <> "${TMPDIR}/imageset-config.yaml" + channels: + - name: fast +EOF + for v in "${FILTERED_VERSIONS[@]}"; do + cat <> "${TMPDIR}/imageset-config.yaml" + - name: fast-${v} EOF + done - if [ -z "$storage_class" ]; then - # use default storage class - storage_class=$(oc get storageclasses -o=jsonpath='{.items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")].metadata.name}') fi - echo " creating PVC for mirror registry, using ${storage_class} as storage class: persistentvolumeclaim/airgap-registry-storage ..." >&2 - cat <&2 -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: airgap-registry-storage - namespace: "${namespace}" -spec: - resources: - requests: - storage: "${helper_mirror_registry_storage}" - storageClassName: "${storage_class}" - accessModes: - - ReadWriteOnce + nbExtraImgs=${#EXTRA_IMAGES[@]} + if [ $nbExtraImgs -ge 1 ]; then + cat <> "${TMPDIR}/imageset-config.yaml" + additionalImages: +EOF + for extraImg in "${EXTRA_IMAGES[@]}"; do + cat <> "${TMPDIR}/imageset-config.yaml" + - name: "$extraImg" EOF + done + fi - echo " creating mirror registry Deployment: deployment/airgap-registry ..." >&2 - # Replacing because password might have changed if we run the script a second time - cat <&2 -apiVersion: apps/v1 -kind: Deployment -metadata: - name: airgap-registry - namespace: "${namespace}" - labels: - app: airgap-registry -spec: - replicas: 1 - selector: - matchLabels: - app: airgap-registry - template: - metadata: - labels: - app: airgap-registry - spec: - # ----------------------------------------------------------------------- - containers: - - image: "${image}" - name: airgap-registry - imagePullPolicy: IfNotPresent - env: - - name: REGISTRY_AUTH - value: "htpasswd" - - name: REGISTRY_AUTH_HTPASSWD_REALM - value: "RHDH Private Registry" - - name: REGISTRY_AUTH_HTPASSWD_PATH - value: "/auth/htpasswd" - - name: REGISTRY_STORAGE_DELETE_ENABLED - value: "true" - ports: - - containerPort: 5000 - volumeMounts: - - name: registry-vol - mountPath: /var/lib/registry - - name: auth-vol - mountPath: "/auth" - readOnly: true - # ----------------------------------------------------------------------- - volumes: - - name: registry-vol - persistentVolumeClaim: - claimName: airgap-registry-storage - - name: auth-vol - secret: - secretName: airgap-registry-auth + if [[ -n "${TO_DIR}" ]]; then + "${OC_MIRROR_PATH}" \ + --config="${TMPDIR}/imageset-config.yaml" \ + file://"${TO_DIR}" \ + --skip-missing \ + --dest-skip-tls \ + --continue-on-error \ + --max-nested-paths=1 \ + $OC_MIRROR_FLAGS \ + | tee "${ocMirrorLogFile}" + if [[ "${TO_DIR}" != "${TMPDIR}" ]]; then + cp -f "${TMPDIR}/imageset-config.yaml" "${TO_DIR}/imageset-config.yaml" + fi + # targetCatalog needs to exist in the target registry. Copying a fake image.. + mirror_image_to_archive "registry.redhat.io/ubi9/ubi:latest" "${TO_DIR}/rhdh-catalog" + fi + if [[ -n "$TO_REGISTRY" ]]; then + registryUrl=$(buildRegistryUrl) + if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then + registryUrl+="/oc-mirror" + fi + # targetCatalog needs to exist in the target registry. Copying a fake image.. + catalog_reg_path="rhdh-catalog" + if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then + catalog_reg_path="oc-mirror/rhdh-catalog" + fi + my_operator_index="$(buildCatalogImageUrl external "${catalog_reg_path}")" + mirror_image_to_registry "registry.redhat.io/ubi9/ubi:latest" "${my_operator_index}-tmp" + + "${OC_MIRROR_PATH}" \ + --config="${TMPDIR}/imageset-config.yaml" \ + "docker://${registryUrl}" \ + --skip-missing \ + --dest-skip-tls \ + --continue-on-error \ + --max-nested-paths=2 \ + $OC_MIRROR_FLAGS \ + | tee "${ocMirrorLogFile}" + fi + + else + # from dir + if [ ! -d "${FROM_DIR}" ]; then + errorf "Directory not found: ${FROM_DIR}" + exit 1 + fi + if [[ -n "${TO_REGISTRY}" ]]; then + registryUrl=$(buildRegistryUrl) + if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then + registryUrl+="/oc-mirror" + fi + + # Rendering index, so as to manually build and push the targetCatalog (defined in the imageset). + # The target catalog needs to exist in the target registry. + catalog_reg_path="rhdh-catalog" + if [[ "${TO_REGISTRY}" == "OCP_INTERNAL" ]]; then + catalog_reg_path="oc-mirror/rhdh-catalog" + fi + my_operator_index="$(buildCatalogImageUrl external "${catalog_reg_path}")" + push_image_from_archive "${FROM_DIR}/rhdh-catalog" "${my_operator_index}-tmp" + + "${OC_MIRROR_PATH}" \ + --config="${FROM_DIR}/imageset-config.yaml" \ + --from "${FROM_DIR}" \ + "docker://${registryUrl}" \ + --skip-missing \ + --dest-skip-tls \ + --continue-on-error \ + $OC_MIRROR_FLAGS \ + | tee "${ocMirrorLogFile}" + fi + fi + + if [ -f "${ocMirrorLogFile}" ]; then + # The xargs here is to trim whitespaces + catalogSourceLocation=$(sed -n -e 's/^Writing CatalogSource manifests to \(.*\)$/\1/p' "${ocMirrorLogFile}" |xargs) + icspLocation=$(sed -n -e 's/^Writing ICSP manifests to \(.*\)$/\1/p' "${ocMirrorLogFile}" |xargs) + if [[ -n "${icspLocation}" ]]; then + debugf "ICSP parent location: ${TMPDIR}/${icspLocation}" + if [[ -n "${TO_REGISTRY}" ]]; then + invoke_cluster_cli apply -f ${TMPDIR}/${icspLocation}/imageContentSourcePolicy.yaml + fi + fi + if [[ -n "${catalogSourceLocation}" ]]; then + # Replace some metadata and add the default list of secrets + debugf "catalogSource parent location: ${TMPDIR}/${catalogSourceLocation}" + yq -i '.metadata.name = "rhdh-catalog"' -i ${TMPDIR}/${catalogSourceLocation}/catalogSource-*.yaml + yq -i '.spec.displayName = "Red Hat Developer Hub Catalog (Airgapped)"' -i ${TMPDIR}/${catalogSourceLocation}/catalogSource-*.yaml + yq -i '.spec.secrets = (.spec.secrets // []) + ["internal-reg-auth-for-rhdh", "internal-reg-ext-auth-for-rhdh", "reg-pull-secret"]' \ + ${TMPDIR}/${catalogSourceLocation}/catalogSource-*.yaml + if [[ -n "${TO_REGISTRY}" ]]; then + invoke_cluster_cli apply -f ${TMPDIR}/${catalogSourceLocation}/catalogSource-*.yaml + fi + fi + fi +else + if [[ -z "${FROM_DIR}" ]]; then + render_index + process_bundles + else + process_bundles_from_dir + mirror_extra_images_from_dir + fi + mirror_extra_images + + # create OLM resources + manifestsTargetDir="${TMPDIR}" + if [[ -n "${FROM_DIR}" ]]; then + manifestsTargetDir="${FROM_DIR}" + fi + + NAMESPACE_CATALOGSOURCE='$NAMESPACE_CATALOGSOURCE' + my_operator_index='$CATALOG_IMAGE' + if [[ -n "${TO_REGISTRY}" ]]; then + # It assumes that the user is also connected to a cluster + detect_ocp_and_set_env_var + if [[ "${IS_OPENSHIFT}" = "true" ]]; then + debugf "Detected an OpenShift cluster" + if ! command -v oc &> /dev/null; then + errorf "Please install oc 4.10+ from an RPM or https://mirror.openshift.com/pub/openshift-v4/clients/ocp/" + exit 1 + fi + # Check we're logged into a cluster + if ! oc whoami &> /dev/null; then + errorf "Not logged into an OpenShift cluster" + exit 1 + fi + else + if ! command -v oc &> /dev/null && ! command -v kubectl &> /dev/null; then + errorf "Please install kubectl or oc 4.10+ (from an RPM or https://mirror.openshift.com/pub/openshift-v4/clients/ocp/)" + exit 1 + fi + debugf "Falling back to a standard K8s cluster" + # Check that OLM is installed + if ! invoke_cluster_cli get crd catalogsources.operators.coreos.com &> /dev/null; then + errorf " + OLM not installed (CatalogSource CRD not found) or you don't have enough permissions. + Check that you are correctly logged into the cluster and that OLM is installed. + See https://olm.operatorframework.io/docs/getting-started/#installing-olm-in-your-cluster to install OLM." + exit 1 + fi + fi + + if [[ "${IS_OPENSHIFT}" = "true" ]]; then + NAMESPACE_CATALOGSOURCE="openshift-marketplace" + else + NAMESPACE_CATALOGSOURCE="olm" + fi + my_operator_index="$(buildCatalogImageUrl "internal")" + fi + + cat < "${manifestsTargetDir}/catalogSource.yaml" + apiVersion: operators.coreos.com/v1alpha1 + kind: CatalogSource + metadata: + name: rhdh-catalog + namespace: ${NAMESPACE_CATALOGSOURCE} + spec: + sourceType: grpc + image: ${my_operator_index} + secrets: + - internal-reg-auth-for-rhdh + - internal-reg-ext-auth-for-rhdh + # Create this image pull secret if your mirror registry requires auth + - reg-pull-secret + publisher: "Red Hat" + displayName: "Red Hat Developer Hub (Airgapped)" +EOF + + if [[ -n "${TO_REGISTRY}" ]]; then + # IDMS will only work on regular OCP clusters. It doesn't work on ROSA or clusters with hosted control planes like on IBM Cloud. + registry_url_internal=$(buildRegistryUrl) + cat < "${manifestsTargetDir}/imageDigestMirrorSet.yaml" + apiVersion: config.openshift.io/v1 + kind: ImageDigestMirrorSet + metadata: + name: rhdh-idms + spec: + imageDigestMirrors: + - mirrors: + - ${registry_url_internal}/rhel9/postgresql-15 + source: registry.redhat.io/rhel9/postgresql-15 + - mirrors: + - ${registry_url_internal}/rhdh + source: registry.redhat.io/rhdh + - mirrors: + - ${registry_url_internal}/openshift4/ose-kube-rbac-proxy + source: registry.redhat.io/openshift4/ose-kube-rbac-proxy +EOF + # Also include mirrors to extra-images + if [[ -n "${FROM_DIR}" ]]; then + BASE_DIR="${FROM_DIR}/extraImages" + if [ -d "${BASE_DIR}" ]; then + # Iterate over all directories named "sha256_*" + find "$BASE_DIR" -type d -name "sha256_*" | while read -r sha256_dir; do + relative_path=${sha256_dir#"$BASE_DIR/"} + sha256_hash=${sha256_dir##*/sha256_} + parent_path=$(dirname "$relative_path") + extraImg="${parent_path}" + targetImg="${extraImg%@*}" + targetImgLastTwo=$(extract_last_two_elements "$targetImg") + cat <> "${manifestsTargetDir}/imageDigestMirrorSet.yaml" + - mirrors: + - ${registry_url_internal}/${targetImgLastTwo} + source: ${targetImg} +EOF + done + + # Iterate over all directories named "tag_*" + find "$BASE_DIR" -type d -name "tag_*" | while read -r tag_dir; do + relative_path=${tag_dir#"$BASE_DIR/"} + tag_hash=${tag_dir##*/tag_} + parent_path=$(dirname "$relative_path") + extraImg="${parent_path}" + targetImg="${extraImg%:*}" + targetImgLastTwo=$(extract_last_two_elements "$targetImg") + cat <> "${manifestsTargetDir}/imageDigestMirrorSet.yaml" + - mirrors: + - ${registry_url_internal}/${targetImgLastTwo} + source: ${targetImg} +EOF + done + fi + fi + # Iterate from the --extra-images passed on the CLI + debugf "Extra images from CLI: ${EXTRA_IMAGES[@]}..." + for img in "${EXTRA_IMAGES[@]}"; do + if [[ "$img" == *"@sha256:"* ]]; then + targetImg="${img%@*}" + elif [[ "$img" == *":"* ]]; then + targetImg="${img%:*}" + else + targetImg="${img}" + fi + targetImgLastTwo=$(extract_last_two_elements "$targetImg") + cat <> "${manifestsTargetDir}/imageDigestMirrorSet.yaml" + - mirrors: + - ${registry_url_internal}/${targetImgLastTwo} + source: ${targetImg} EOF + done + + # Create the IDMS (OCP-specific) and CatalogSource + if [[ "${IS_OPENSHIFT}" = "true" ]]; then + invoke_cluster_cli apply -f "${manifestsTargetDir}/imageDigestMirrorSet.yaml" + fi + debugf "Adding the internal cluster creds as pull secrets to be able to pull images from this internal registry by default" + invoke_cluster_cli apply -f "${manifestsTargetDir}/catalogSource.yaml" + fi +fi - echo " creating mirror registry Service: service/airgap-registry ..." >&2 - cat <&2 +if [[ -n "${TO_REGISTRY}" && "${IS_OPENSHIFT}" = "true" ]]; then + infof "Disabling the default Red Hat Ecosystem Catalog." + invoke_cluster_cli patch OperatorHub cluster --type json \ + --patch '[{"op": "add", "path": "/spec/disableAllDefaultSources", "value": true}]' +fi + +cat < "${manifestsTargetDir}/namespace.yaml" apiVersion: v1 -kind: Service +kind: Namespace metadata: - name: airgap-registry - namespace: "${namespace}" - labels: - app: airgap-registry -spec: - type: ClusterIP - ports: - - port: 5000 - protocol: TCP - targetPort: 5000 - selector: - app: airgap-registry + name: ${NAMESPACE_OPERATOR} EOF - echo " creating Route to access mirror registry: route/airgap-registry ..." >&2 - oc -n "${namespace}" create route edge --service=airgap-registry --insecure-policy=Redirect --dry-run=client -o yaml \ - | oc -n "${namespace}" apply -f - >&2 - - local registry_url=$(oc get route airgap-registry -n "${namespace}" --template='{{ .spec.host }}') - echo "... done. Mirror registry should now be reachable at: ${registry_url} ..." >&2 - - # Wait until url is ready - echo "[INFO] Waiting for mirror registry to be ready and reachable ..." >&2 - curl --insecure -IL "${registry_url}" --retry 100 --retry-all-errors --retry-max-time 900 --fail &> /tmp/"${registry_url}.log" >&2 - - echo "[INFO] Log into mirror registry to be able to push images to it..." >&2 - podman login -u="${username}" -p="${password}" "${registry_url}" --tls-verify=false >&2 - - echo "[INFO] Marking mirror registry as insecure in the cluster ..." >&2 - oc patch image.config.openshift.io/cluster --patch '{"spec":{"registrySources":{"insecureRegistries":["'${registry_url}'"]}}}' --type=merge >&2 - - echo "[INFO] Adding mirror registry creds to cluster global pull secret ..." >&2 - echo " downloading global pull secret from the cluster ..." >&2 - oc get secret/pull-secret -n openshift-config --template='{{index .data ".dockerconfigjson" | base64decode}}' > /tmp/my-global-pull-secret-for-mirror-reg.yaml - echo " log into mirror registry and store creds into the pull secret downloaded..." >&2 - oc registry login \ - --insecure=true \ - --registry="${registry_url}" \ - --auth-basic="${username}:${password}" \ - --to=/tmp/my-global-pull-secret-for-mirror-reg.yaml \ - >&2 - echo " writing updated pull secret into the cluster ..." >&2 - oc set data secret/pull-secret -n openshift-config --from-file=.dockerconfigjson=/tmp/my-global-pull-secret-for-mirror-reg.yaml >&2 - - # Need to mirror OCP release images, otherwise ImagePullBackOff when installing the operator after disconnecting the cluster: - # unable to pull quay.io/openshift-release-dev/ocp-v4.0-art-dev@... - echo "[INFO] Mirroring OCP release images ..." >&2 - local ocp_product_repo='openshift-release-dev' - local ocp_release_name="ocp-release" - local ocp_local_repo="ocp/openshift" - oc adm release mirror -a /tmp/my-global-pull-secret-for-mirror-reg.yaml \ - --from="quay.io/${ocp_product_repo}/${ocp_release_name}:${OCP_VER}-${OCP_ARCH}" \ - --to="${registry_url}/${ocp_local_repo}" \ - --to-release-image="${registry_url}/${ocp_local_repo}:${OCP_VER}-${OCP_ARCH}" \ - --insecure=true > /tmp/oc-adm-release-mirror__mirror-registry.out - echo " creating ImageContentSourcePolicy for OCP release images: imagecontentsourcepolicy/ocp-release ..." >&2 - cat <&2 -apiVersion: operator.openshift.io/v1alpha1 -kind: ImageContentSourcePolicy +cat < "${manifestsTargetDir}/operatorGroup.yaml" +apiVersion: operators.coreos.com/v1 +kind: OperatorGroup metadata: - name: ocp-release - labels: - app: airgap-registry + name: rhdh-operator-group + namespace: ${NAMESPACE_OPERATOR} +EOF + +cat < "${manifestsTargetDir}/subscription.yaml" +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + name: rhdh-operator + namespace: ${NAMESPACE_OPERATOR} spec: - repositoryDigestMirrors: - - mirrors: - - "${registry_url}/${ocp_local_repo}" - source: quay.io/openshift-release-dev/ocp-release - - mirrors: - - "${registry_url}/${ocp_local_repo}" - source: "quay.io/openshift-release-dev/ocp-v${OCP_VER_MAJOR}.0-art-dev" + channel: fast + installPlanApproval: Automatic + name: rhdh + source: rhdh-catalog + sourceNamespace: ${NAMESPACE_CATALOGSOURCE} EOF - echo "[INFO] Cleaning up temporary files ..." >&2 - rm -f /tmp/my-global-pull-secret-for-mirror-reg.yaml /tmp/oc-adm-release-mirror__mirror-registry.out >&2 +if [[ "$INSTALL_OPERATOR" != "true" ]]; then + echo + echo "Done. " + if [[ -n "${TO_DIR}" ]]; then + echo " +${TO_DIR} should now contain all the images and resources needed to install the Red Hat Developer Hub operator. Next steps: - echo "[INFO] Mirror registry should be ready: ${registry_url}" >&2 - echo "${registry_url}" -} +1. Transfer ${TO_DIR} over to your disconnected environment +2. In your disconnected environment, run the 'install.sh' script (located in your export dir) with the '--from-dir' and '--to-registry' options, like so: -declare my_registry="${use_existing_mirror_registry}" -if [ -z "${my_registry}" ]; then - my_registry=$(deploy_mirror_registry) -fi +# Make sure you are connected to the mirror registry and the target cluster +/path/to/export-dir/install.sh \ + --from-dir /path/to/export-dir \ + --to-registry \$mirror_registry_url \ + --install-operator + " + fi + if [[ -n "${TO_REGISTRY}" ]]; then + if [[ "${IS_OPENSHIFT}" = "true" ]]; then + echo "Now log into the OCP web console as an admin, then go to Operators > OperatorHub, search for Red Hat Developer Hub, and install the Red Hat Developer Hub Operator." + else + echo "To install the operator, you will need to create an OperatorGroup and a Subscription. You can do so with the following commands: -declare my_operator_index="${my_registry}/${prod_operator_package_name}/${my_operator_index_image_name_and_tag}" - -# Create local directory -mkdir -p "${my_catalog}/${prod_operator_package_name}" - -echo "[INFO] Fetching metadata for the ${prod_operator_package_name} operator catalog channel, packages, and bundles." -opm render "${prod_operator_index}" \ - | jq "select \ - (\ - (.schema == \"olm.bundle\" and .name == \"${prod_operator_bundle_name}.${prod_operator_version}\") or \ - (.schema == \"olm.package\" and .name == \"${prod_operator_package_name}\") or \ - (.schema == \"olm.channel\" and .package == \"${prod_operator_package_name}\") \ - )" \ - | jq "select \ - (.schema == \"olm.channel\" and .package == \"${prod_operator_package_name}\").entries \ - |= [{name: \"${prod_operator_bundle_name}.${prod_operator_version}\"}]" \ - > "${my_catalog}/${prod_operator_package_name}/render.json" - -echo "[DEBUG] Got $(cat "${my_catalog}/${prod_operator_package_name}/render.json" | wc -l) lines of JSON from the index!" -# echo "[DEBUG] Got this from the index: -# ======" -# cat "${my_catalog}/${prod_operator_package_name}/render.json" -# echo "======" - -echo "[INFO] Creating the catalog dockerfile." -if [ -f "${my_catalog}.Dockerfile" ]; then - rm -f "${my_catalog}.Dockerfile" -fi -opm generate dockerfile "./${my_catalog}" - -echo "[INFO] Building the catalog image locally." -podman build -t "${my_operator_index}" -f "./${my_catalog}.Dockerfile" --no-cache . - -echo "[INFO] Disabling the default Red Hat Ecosystem Catalog." -oc patch OperatorHub cluster --type json \ - --patch '[{"op": "add", "path": "/spec/disableAllDefaultSources", "value": true}]' - -echo "[INFO] Deploying your catalog image to the $my_operator_index registry." -skopeo copy --src-tls-verify=false --dest-tls-verify=false --all "containers-storage:$my_operator_index" "docker://$my_operator_index" - -echo "[INFO] Removing index image from mappings.txt to prepare mirroring." -oc adm catalog mirror "$my_operator_index" "$my_registry" --insecure --manifests-only | tee catalog_mirror.log -MANIFESTS_FOLDER=$(sed -n -e 's/^wrote mirroring manifests to \(.*\)$/\1/p' catalog_mirror.log |xargs) # The xargs here is to trim whitespaces -sed -i -e "/${my_operator_index_image_name_and_tag}/d" "${MANIFESTS_FOLDER}/mapping.txt" -cat "${MANIFESTS_FOLDER}/mapping.txt" - -echo "[INFO] Mirroring related images to the $my_registry registry." -# oc image mirror --insecure=true -f "${MANIFESTS_FOLDER}/mapping.txt" -while IFS= read -r line -do - public_image=$(echo "${line}" | cut -d '=' -f1) - if [[ "$prod_operator_index" != registry.redhat.io/redhat/redhat-operator-index* ]] && [[ "$public_image" == registry.redhat.io/rhdh/* ]]; then - if ! skopeo inspect "docker://$public_image" &> /dev/null; then - # likely CI build, which is not public yet - echo " Replacing non-public CI image $public_image ..." - public_image=${public_image/registry.redhat.io\/rhdh/quay.io\/rhdh} - echo " => $public_image" + kubectl -n ${NAMESPACE_OPERATOR} apply -f ${manifestsTargetDir}/namespace.yaml + kubectl -n ${NAMESPACE_OPERATOR} apply -f ${manifestsTargetDir}/operatorGroup.yaml + kubectl -n ${NAMESPACE_OPERATOR} apply -f ${manifestsTargetDir}/subscription.yaml + " fi fi - private_image=$(echo "${line}" | cut -d '=' -f2) - echo "[INFO] Mirroring ${public_image}" - skopeo copy --dest-tls-verify=false --preserve-digests --all "docker://$public_image" "docker://$private_image" -done < "${MANIFESTS_FOLDER}/mapping.txt" - -echo "[INFO] Creating CatalogSource and ImageContentSourcePolicy" -# shellcheck disable=SC2002 -cat "${MANIFESTS_FOLDER}/catalogSource.yaml" | sed 's|name: .*|name: '${k8s_resource_name}'|' | oc apply -f - -# shellcheck disable=SC2002 -cat "${MANIFESTS_FOLDER}/imageContentSourcePolicy.yaml" | sed 's|name: .*|name: '${k8s_resource_name}'|' | oc apply -f - - -echo "[INFO] Catalog $my_operator_index deployed to the $my_registry registry." + exit 0 +fi + +if [[ -n "${TO_REGISTRY}" ]]; then + + # Install the operator + for manifest in namespace operatorGroup subscription; do + invoke_cluster_cli apply -f "${manifestsTargetDir}/${manifest}.yaml" + done + + invoke_cluster_cli -n ${NAMESPACE_OPERATOR} patch serviceaccount default \ + -p '{"imagePullSecrets": [{"name": "internal-reg-auth-for-rhdh"},{"name": "internal-reg-ext-auth-for-rhdh"},{"name": "reg-pull-secret"}]}' + + if [[ "${IS_OPENSHIFT}" = "true" ]]; then + OCP_CONSOLE_ROUTE_HOST=$(invoke_cluster_cli get route console -n openshift-console -o=jsonpath='{.spec.host}') + CLUSTER_ROUTER_BASE=$(invoke_cluster_cli get ingress.config.openshift.io/cluster '-o=jsonpath={.spec.domain}') + echo -n " + + To install, go to: + https://${OCP_CONSOLE_ROUTE_HOST}/catalog/ns/${NAMESPACE_OPERATOR}?catalogType=OperatorBackedService + + Or " + else + echo -n " + + To install on Kubernetes: " + fi + + CLI_TOOL="kubectl" + if [[ "${IS_OPENSHIFT}" = "true" ]]; then + CLI_TOOL="oc" + fi + CR_EXAMPLE=" + cat <