diff --git a/.github/workflows/install-integration-tests-operators-installer.yaml b/.github/workflows/install-integration-tests-operators-installer.yaml new file mode 100644 index 00000000..86836970 --- /dev/null +++ b/.github/workflows/install-integration-tests-operators-installer.yaml @@ -0,0 +1,101 @@ +--- +# these integration tests need to be per operator since they don't do clean up +# +# NOTE: can't use chart-testing because `ct` does not allow for a fixed release so you can't run two different tests that affect the same resources + +name: Install Integration Test - operators-installer + +on: + pull_request: + paths: + - .github/** + - _test/charts-integration-tests/operators-installer/** + - charts/operators-installer/** + +# Declare default permissions as read only. +permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + install-integration-test: + runs-on: ubuntu-latest + env: + # renovate: datasource=github-releases depName=helm/helm + HELM_VERSION: v3.15.0 + # renovate: datasource=github-tags depName=python/cpython + PYTHON_VERSION: v3.12.3 + # renovate: datasource=github-releases depName=kubernetes-sigs/kind + KIND_VERSION: v0.23.0 + # renovate: datasource=github-releases depName=operator-framework/operator-lifecycle-manager + OLM_VERSION: v0.28.0 + steps: + - name: Checkout 🛎️ + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4 + with: + fetch-depth: 0 + + - name: Setup Helm 🧰 + uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4 + with: + version: ${{ env.HELM_VERSION }} + + - name: Setup Python 🐍 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Setup kind cluster 🧰 + uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0 + with: + version: ${{ env.KIND_VERSION }} + + # for helm charts we are testing that require installing operators + - name: Setup kind cluster - Install OLM 🧰 + run: | + curl -L https://github.com/operator-framework/operator-lifecycle-manager/releases/download/${OLM_VERSION}/install.sh -o install.sh + chmod +x install.sh + ./install.sh ${OLM_VERSION} + + # for helm charts we are testing that require ingress + - name: Setup kind cluster - Install ingress controller 🧰 + run: | + helm repo add haproxy-ingress https://haproxy-ingress.github.io/charts + helm install haproxy-ingress haproxy-ingress/haproxy-ingress \ + --create-namespace --namespace=ingress-controller \ + --set controller.hostNetwork=true + kubectl apply -f - <`false` to create manual InstallPlan approval resources as part of normal install

The hook method is nice to not have lingering resources needed for the manual InstallPlan approval but has the downside that no CustomResources using CustomResourceDefinitions installed by the operator can be used in the same chart because the operator InstallPlan wont be approved, and therefor the operator wont be installed, until the post-install,post-upgrade phase which means you will never get to that phase because your CustomResources wont be able to apply because the Operator isn't installed.

This is is ultimately a trade off between cleaning up these resources or being able to install and configure the operator in the same helm chart that has a dependency on this helm chart. +| installRequiredPythonLibraries | `true` | No | If `true`, install the required Python libraries (openshift-client, semver==2.13.0) dynamically from the given `pythonIndexURL` and `pythonExtraIndexURL` into the `installPlanApproverAndVerifyJobsImage` at run time +| pythonIndexURL | https://pypi.org/simple/ | No | If `installRequiredPythonLibraries` is `true` then use this python index to pull required libraries +| pythonExtraIndexURL | https://pypi.org/simple/ | No | If `installRequiredPythonLibraries` is `true` then use this python extra index to pull required library dependencies | commonLabels | `{}` | No | Common labels to add to all chart created resources. Implements the same idea from Kustomize for this chart. | global.commonLabels | `{}` | No | Common labels coming from global values to add to all chart created resources. Implements the same idea from Kustomize for this chart. ## Warnings -### Can not install / upgrade different operators in same namespace independently +### Disconnected Use +If wanting use this chart in a disconnected environment you need to either: + +#### Option 1: local python index +Set the `pythonIndexURL` and `pythonExtraIndexURL` values to a local disconnected python index that minimally includes (and their dependencies): +* openshift-client +* semver==2.13.0 + +#### Option 2: custom `installPlanApproverAndVerifyJobsImage` with required dependencies +Build a custom container image with: +* binary - `oc` +* python lib - `openshift-client` +* python lib - `semver==2.13.0` +Suggestion is to build such an image on top of the latest `registry.redhat.io/openshift4/ose-cli` image + +Then provide that custom image to `installPlanApproverAndVerifyJobsImage` and set `installRequiredPythonLibraries` to false. + +### Can not install / upgrade different operators in same namespace independently As documented in [How can Operators be updated independently from each other?](https://access.redhat.com/solutions/6389681) when more then one operator install or update is pending in the same namespace the Operator Lifecycle Manager (OLM) will combine those installs/updates into a single InstallPlan and there is no way to separate them. Therefor if you use this helm chart in namespace ZZZ to install operator A at v1.0 and it has a pending update to v1.1 and then update the configuration to also install operator B at v42.0 in namespace ZZZ the ZZZ v42.0 InstallPlan and the A v1.1 InstallPlan will get merged (by OLM) and this helm chart will then approve that InstallPlan as it will match on the ZZZ v42.0 pending install, which will incidentally install the A v1.1 update. There is no way for this or any helm chart, automation, or even click ops to prevent this, as documented in [How can Operators be updated independently from each other?](https://access.redhat.com/solutions/6389681) this is currently considered "a feature of OLM". diff --git a/charts/operators-installer/_integration-tests/test-install-operator-0-automatic-intermediate-manual-upgrades-values.yaml b/charts/operators-installer/_integration-tests/test-install-operator-0-automatic-intermediate-manual-upgrades-values.yaml new file mode 100644 index 00000000..2244fec2 --- /dev/null +++ b/charts/operators-installer/_integration-tests/test-install-operator-0-automatic-intermediate-manual-upgrades-values.yaml @@ -0,0 +1,24 @@ +approveManualInstallPlanViaHook: true + +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 + +operatorGroups: +- name: argocd-operator + createNamespace: true + targetOwnNamespace: false + otherTargetNamespaces: + +operators: +- name: argocd-operator + channel: alpha + csv: argocd-operator.v0.8.0 + installPlanApproval: Manual + source: operatorhubio-catalog + sourceNamespace: olm + namespace: argocd-operator + installPlanVerifierActiveDeadlineSeconds: 1200 + automaticIntermediateManualUpgrades: true + config: + env: + - name: DISABLE_DEFAULT_ARGOCD_INSTANCE + value: "true" diff --git a/charts/operators-installer/_integration-tests/test-install-operator-1-automatic-intermediate-manual-upgrades-values.yaml b/charts/operators-installer/_integration-tests/test-install-operator-1-automatic-intermediate-manual-upgrades-values.yaml new file mode 100644 index 00000000..bc6c55b0 --- /dev/null +++ b/charts/operators-installer/_integration-tests/test-install-operator-1-automatic-intermediate-manual-upgrades-values.yaml @@ -0,0 +1,24 @@ +approveManualInstallPlanViaHook: true + +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 + +operatorGroups: +- name: argocd-operator + createNamespace: true + targetOwnNamespace: false + otherTargetNamespaces: + +operators: +- name: argocd-operator + channel: alpha + csv: argocd-operator.v0.10.1 + installPlanApproval: Manual + source: operatorhubio-catalog + sourceNamespace: olm + namespace: argocd-operator + installPlanVerifierActiveDeadlineSeconds: 1200 + automaticIntermediateManualUpgrades: true + config: + env: + - name: DISABLE_DEFAULT_ARGOCD_INSTANCE + value: "true" diff --git a/charts/operators-installer/_scripts/installplan-approver.py b/charts/operators-installer/_scripts/installplan-approver.py new file mode 100644 index 00000000..b78c3596 --- /dev/null +++ b/charts/operators-installer/_scripts/installplan-approver.py @@ -0,0 +1,62 @@ +#!/usr/bin/python + +import os +import sys +import installplan_utils + +NAMESPACE_NAME = os.getenv("NAMESPACE") or installplan_utils.error_and_exit( + "env is missing expected value: NAMESPACE", 2 +) +SUBSCRIPTION_NAME = os.getenv("SUBSCRIPTION") or installplan_utils.error_and_exit( + "env is missing expected value: SUBSCRIPTION", 2 +) +CSV = os.getenv("CSV") or installplan_utils.error_and_exit( + "env is missing expected value: CSV", 2 +) + +print() +print("********************************************************************") +print("* START InstallPlan approver") +print(f"*\t- NAMESPACE_NAME: {NAMESPACE_NAME}") +print(f"*\t- SUBSCRIPTION_NAME: {SUBSCRIPTION_NAME}") +print(f"*\t- CSV: {CSV}") +print("********************************************************************") + +# find the subscription uid +print() +print(f"Get Subscription ({SUBSCRIPTION_NAME}) UID") +subscription_uid = installplan_utils.get_subscription_uid(SUBSCRIPTION_NAME) +print(f"\t- Subscription ({SUBSCRIPTION_NAME}) UID: {subscription_uid}") + +# if found subscription uid find InstallPlan for given CSV with owner of the given subscription +# else error +if subscription_uid: + # find the InstallPlan that has expected owner subscription id and expected target CSV name + # NOTE: if more then one InstallPlan matches, choose the first one + print( + f"Find InstallPlan in Namespace ({NAMESPACE_NAME}) for CSV ({CSV}) with Subscription (${subscription_uid}) owner" + ) + target_installplan = installplan_utils.get_installplan( + NAMESPACE_NAME, CSV, subscription_uid + ) + + # if found target InstallPlan, approve it, and success exit + # else fail + if target_installplan: + print(f"\t- Found InstallPlan: {target_installplan.model.metadata.name}") + installplan_utils.approve_installplan(target_installplan) + sys.exit(0) + else: + print() + print( + f"ERROR: Could not find next InstallPlan to reach CSV ${CSV}) with Subscription ({SUBSCRIPTION_NAME}) ({subscription_uid}) owner." + + "\nThis can happen if InstallPlan isn't created yet or no valid upgrade path between current CSV and target CSV." + + "\nTry again." + ) + sys.exit(1) +else: + print() + print( + f"ERROR: Failed to get Subscription ({SUBSCRIPTION_NAME}) UID. This really shouldn't happen." + ) + sys.exit(1) diff --git a/charts/operators-installer/_scripts/installplan-incremental-approver.py b/charts/operators-installer/_scripts/installplan-incremental-approver.py new file mode 100644 index 00000000..5cbb5d21 --- /dev/null +++ b/charts/operators-installer/_scripts/installplan-incremental-approver.py @@ -0,0 +1,131 @@ +#!/usr/bin/python + +import os +import sys +import installplan_utils +import time + +NAMESPACE_NAME = os.getenv("NAMESPACE") or installplan_utils.error_and_exit( + "env is missing expected value: NAMESPACE", 2 +) +SUBSCRIPTION_NAME = os.getenv("SUBSCRIPTION") or installplan_utils.error_and_exit( + "env is missing expected value: SUBSCRIPTION", 2 +) +CSV = os.getenv("CSV") or installplan_utils.error_and_exit( + "env is missing expected value: CSV", 2 +) +INCREMENTAL_INSTALL_BACKOFF_LIMIT = ( + int(os.getenv("INCREMENTAL_INSTALL_BACKOFF_LIMIT")) or 10 +) +INCREMENTAL_INSTALL_DELAY_INCREMENT = ( + int(os.getenv("INCREMENTAL_INSTALL_DELAY_INCREMENT")) or 5 +) + + +print() +print("********************************************************************") +print("* START InstallPlan approver including intermediates") +print(f"*\t- NAMESPACE_NAME: {NAMESPACE_NAME}") +print(f"*\t- SUBSCRIPTION_NAME: {SUBSCRIPTION_NAME}") +print(f"*\t- CSV: {CSV}") +print(f"*\t- INCREMENTAL_INSTALL_BACKOFF_LIMIT: {INCREMENTAL_INSTALL_BACKOFF_LIMIT}") +print( + f"*\t- INCREMENTAL_INSTALL_DELAY_INCREMENT: {INCREMENTAL_INSTALL_DELAY_INCREMENT}" +) +print("********************************************************************") + +# find the subscription uid +print() +print(f"Get Subscription ({SUBSCRIPTION_NAME}) UID") +subscription_uid = installplan_utils.get_subscription_uid(SUBSCRIPTION_NAME) +print(f"\t- Subscription ({SUBSCRIPTION_NAME}) UID: {subscription_uid}") + +# if found subscription uid incrementally find, approve, and verify the next InstallPlan for given CSV with owner of the given subscription +# until reaching the desired Subscription CSV +# else error +if subscription_uid: + print( + f"Find and approve every InstallPlan between currently installed CSV and target CSV ({CSV})" + ) + target_installplan = None + approved_install_plans = [] + attempt = 0 + while (target_installplan is None) or ( + CSV not in target_installplan.model.spec.clusterServiceVersionNames + ): + # find the next InstallPlan that has expected owner subscription id and expected target CSV name + print( + f"\nFind next InstallPlan in Namespace ({NAMESPACE_NAME}) for CSV ({CSV}) with Subscription (${subscription_uid}) owner" + ) + target_installplan = installplan_utils.get_next_installplan( + NAMESPACE_NAME, CSV, subscription_uid + ) + + # if found next InstallPlan, approve it + # else fail + if target_installplan: + # if target install plan is not one we have already approved, then approve it + # else wait and loop for next InstallPlan that installs a CSV less then or equal to our target CSV + if target_installplan.model.metadata.name not in map( + lambda approved_install_plan: approved_install_plan.model.metadata.name, + approved_install_plans, + ): + attempt = 0 + print( + f"\t- Found next InstallPlan: {target_installplan.model.metadata.name}" + ) + approved_install_plans.append(target_installplan) + installplan_utils.approve_installplan(target_installplan) + installplan_installed = ( + installplan_utils.verify_installplan_and_csv_installed( + target_installplan, + INCREMENTAL_INSTALL_BACKOFF_LIMIT, + INCREMENTAL_INSTALL_DELAY_INCREMENT, + ) + ) + else: + attempt += 1 + # let everyone know what InstallPlans are currently available + print("\t- Currently available InstallPlans:") + for available_installplan in installplan_utils.get_all_installplans( + NAMESPACE_NAME + ): + print( + f"\t\t-- {available_installplan.model.metadata.name} - {available_installplan.model.spec.clusterServiceVersionNames}: {available_installplan.model.status.phase}" + ) + + # if attempts left, do another loop waiting for target InstallPlan to appear + if attempt <= INCREMENTAL_INSTALL_BACKOFF_LIMIT: + delay = attempt * INCREMENTAL_INSTALL_DELAY_INCREMENT + print( + f"\t- Attempt ({attempt} of {INCREMENTAL_INSTALL_BACKOFF_LIMIT}) waiting ({delay} seconds) for next unapproved InstallPlan including target CSV less then or equal to be created. Target CSV: {CSV}" + ) + sys.stdout.flush() + time.sleep(delay) + else: + installplan_utils.error_and_exit( + f"Timed out waiting for next unapproved InstallPlan including target CSV less then or equal to be created. Target CSV: {CSV}", + 1, + ) + else: + installplan_utils.error_and_exit( + f"Could not find next InstallPlan to reach CSV ${CSV}) with Subscription ({SUBSCRIPTION_NAME}) ({subscription_uid}) owner." + + "\nThis can happen if InstallPlan isn't created yet or no valid upgrade path between current CSV and target CSV." + + "\nTry again.", + 1, + ) + + # report success + print() + print( + f"Successfully installed target CSV ({CSV}) installed, approved intermediate InstallPlans include:" + ) + for approved_install_plan in approved_install_plans: + print( + f"\t- {approved_install_plan.model.metadata.name}: {approved_install_plan.model.spec.clusterServiceVersionNames}" + ) + sys.exit(0) +else: + installplan_utils.error_and_exit( + f"Failed to get Subscription ({SUBSCRIPTION_NAME}) UID. This really shouldn't happen." + ) diff --git a/charts/operators-installer/_scripts/installplan-verifier.py b/charts/operators-installer/_scripts/installplan-verifier.py new file mode 100644 index 00000000..7ea49a39 --- /dev/null +++ b/charts/operators-installer/_scripts/installplan-verifier.py @@ -0,0 +1,79 @@ +#!/usr/bin/python + +import os +import sys +import installplan_utils + +NAMESPACE_NAME = os.getenv("NAMESPACE") or installplan_utils.error_and_exit( + "env is missing expected value: NAMESPACE", 2 +) +SUBSCRIPTION_NAME = os.getenv("SUBSCRIPTION") or installplan_utils.error_and_exit( + "env is missing expected value: SUBSCRIPTION", 2 +) +CSV = os.getenv("CSV") or installplan_utils.error_and_exit( + "env is missing expected value: CSV", 2 +) + +print() +print("********************************************************************") +print("* START InstallPlan Approver *") +print(f"*\t- NAMESPACE_NAME: {NAMESPACE_NAME}") +print(f"*\t- SUBSCRIPTION_NAME: {SUBSCRIPTION_NAME}") +print(f"*\t- CSV: {CSV}") +print("********************************************************************") + + +# find the subscription uid +print() +print(f"Get Subscription ({SUBSCRIPTION_NAME}) UID") +subscription_uid = installplan_utils.get_subscription_uid(SUBSCRIPTION_NAME) +print(f"\t- Subscription ({SUBSCRIPTION_NAME}) UID: {subscription_uid}") + +# if found subscription uid find InstallPlan for given CSV with owner of the given subscription +# else error +if subscription_uid: + # find the InstallPlan that has expected owner subscription id and expected target CSV name + # NOTE: if more then one InstallPlan matches, choose the first one + print( + f"\tFind InstallPlan in Namespace ({NAMESPACE_NAME}) for CSV ({CSV}) with Subscription (${subscription_uid}) owner" + ) + target_installplan = installplan_utils.get_installplan( + NAMESPACE_NAME, CSV, subscription_uid + ) + + # if found target InstallPlan, check if its installed + # else fail + if target_installplan: + print(f"\t- Found InstallPlan: {target_installplan.model.metadata.name}") + + installplan_installed = installplan_utils.verify_installplan_and_csv_installed( + target_installplan, + 1, + 1, + ) + if installplan_installed: + print() + print( + f"InstallPlan ({target_installplan.model.metadata.name}) installation verified" + ) + sys.exit(0) + else: + print() + print( + f"InstallPlan ({target_installplan.model.metadata.name}) not yet installed. Suggest retry verification." + ) + sys.exit(1) + else: + print() + print( + f"ERROR: Could not find next InstallPlan to reach CSV ${CSV}) with Subscription ({SUBSCRIPTION_NAME}) ({subscription_uid}) owner." + + "\nThis can happen if InstallPlan isn't created yet or no valid upgrade path between current CSV and target CSV." + + "\nTry again." + ) + sys.exit(1) +else: + print() + print( + f"ERROR: Failed to get Subscription ({SUBSCRIPTION_NAME}) UID. This really shouldn't happen." + ) + sys.exit(1) diff --git a/charts/operators-installer/_scripts/installplan_utils.py b/charts/operators-installer/_scripts/installplan_utils.py new file mode 100644 index 00000000..26b5b0f9 --- /dev/null +++ b/charts/operators-installer/_scripts/installplan_utils.py @@ -0,0 +1,210 @@ +#!/usr/bin/python + +import openshift_client as oc +import semver # assumes semver 2.13 because thats whats supported on python3.6 which is what is supported by oc tools image +import re +import time +import sys + + +def get_subscription(subscription_name: str): + subscriptions_selector = oc.selector( + [f"subscriptions.operators.coreos.com/{subscription_name}"] + ) + subscription = None + if subscriptions_selector.objects(): + subscription = subscriptions_selector.objects()[0] + + return subscription + + +def get_subscription_uid(subscription_name: str): + """Get the Subscription UID for the given Subscription name""" + subscription = get_subscription(subscription_name) + if subscription: + subscription_uid = subscription.model.metadata.uid + return subscription_uid + + +def get_csv(csv_name: str): + """Get the ClusterServiceVersion object for a given CSV name""" + csvs_selector = oc.selector( + [f"clusterserviceversions.operators.coreos.com/{csv_name}"] + ) + csv = None + if csvs_selector.objects(): + csv = csvs_selector.objects()[0] + + return csv + + +def get_all_installplans( + namespace_name: str, +): + """Get all InstallPlans in a given Namespace""" + installplans_selector = oc.selector(["installplan.operators.coreos.com"]) + return installplans_selector.objects() + + +def get_installplan( + namespace_name: str, + csv_name: str, + subscription_uid: str, +): + """Get InstallPlan in a given Namespace, with a given target ClusterServiceVersion name, and a given Subscription owner""" + with oc.project(namespace_name): + # find the InstallPlan that has expected owner subscription id and expected target CSV name + # NOTE: if more then one InstallPlan matches, choose the first one + installplans = get_all_installplans(namespace_name) + target_installplan = None + for installplan in installplans: + installplan_owner_uids = map( + lambda owner_reference: owner_reference.uid, + installplan.model.metadata.ownerReferences, + ) + if (csv_name in installplan.model.spec.clusterServiceVersionNames) and ( + subscription_uid in installplan_owner_uids + ): + target_installplan = installplan + break + + return target_installplan + + +def get_next_installplan( + namespace_name: str, + csv_name: str, + subscription_uid: str, +): + """Find the next InstallPlan that installs a ClusterServiceVersion that is less then or equal to the given ClusterServiceVersion name and owned by the given Subscription""" + target_csv_name_semver = get_csv_semver(csv_name) + with oc.project(namespace_name): + # search each InstallPlan for the one that is owned by given subscription and has the highest CSV less then or equal to our target csv + installplans_selector = oc.selector(["installplan.operators.coreos.com"]) + latest_installplan = None + latest_installplan_csv_semver = None + for installplan in installplans_selector.objects(): + installplan_owner_uids = map( + lambda owner_reference: owner_reference.uid, + installplan.model.metadata.ownerReferences, + ) + # if InstallPlan owned by given subscription search its CSVs + if subscription_uid in installplan_owner_uids: + # searching InstallPlan's CSVs for one that is less then or equal to our target CSV and greater then any already found valid InstallPlan + for ( + installplan_csv + ) in installplan.model.spec.clusterServiceVersionNames: + installplan_csv_semver = get_csv_semver(installplan_csv) + # if pending InstallPlan CSV semver is less then or equal to target subscription CSV semver and greater then last found valid pending InstallPlan + if (installplan_csv_semver <= target_csv_name_semver) and ( + latest_installplan_csv_semver is None + or installplan_csv_semver > latest_installplan_csv_semver + ): + latest_installplan = installplan + latest_installplan_csv_semver = installplan_csv_semver + + return latest_installplan + + +def approve_installplan(installplan: oc.APIObject): + """Approves a given install plan""" + installplan_name = installplan.model.metadata.name + print( + "Approve InstallPlan:" + + f"\n\t- name: {installplan_name}" + + f"\n\t- CSVs: {installplan.model.spec.clusterServiceVersionNames}" + + f"\n\t- approval state: {installplan.model.spec.approved}" + ) + # if already approved, just notify + # else approve + if installplan.model.spec.approved: + print( + f"\t- InstallPlan ({installplan_name}) is already approved, nothing to do." + ) + else: + print(f"\t- Approving InstallPlan ({installplan_name})") + installplan.modify_and_apply(installplan_approve_modify_and_apply, 4) + print(f"\t- Approved InstallPlan ({installplan_name})") + + +def installplan_approve_modify_and_apply(installplan: oc.APIObject): + """Helper function for oc.APIObject.modify_and_apply to approve an InstallPlan""" + installplan.model.spec.approved = True + + +def get_csv_semver( + csv_name: str, +): + """Get the semver section of a given ClusterServiceVersion name""" + csv_version_regex_pattern = re.compile("[^\.]*.v?(.*)") + csv_version = csv_version_regex_pattern.match(csv_name).group(1) + return semver.VersionInfo.parse(csv_version) + + +def verify_installplan_and_csv_installed( + installplan: oc.APIObject, + backoffLimit: int = 1, + delay_increment: int = 2, +): + """Wait until a given InstallPlan and its associated ClusterServiceVersions are installed""" + installplan_name = installplan.model.metadata.name + print( + "Verify InstallPlan and CSV installed:" + + f"\n\t- InstallPlan name: {installplan_name}" + + f"\n\t- InstallPlan CSVs: {installplan.model.spec.clusterServiceVersionNames}" + ) + installplan_installed = False + csvs_installed = False + for attempt in range(backoffLimit): + # progressively wait longer with each attempt + time.sleep(attempt * delay_increment) + + if not installplan_installed: + # refresh and check if installed + installplan.refresh() + installplan_installed = installplan.model.status.conditions.can_match( + { + "type": "Installed", + "status": "True", + } + ) + if installplan_installed: + print( + f"\t- Verify InstallPlan ({installplan_name}) installed attempt ({attempt} of {backoffLimit}) success, current phase: {installplan.model.status.phase}" + ) + else: + print( + f"\t- Verify InstallPlan ({installplan_name}) installed attempt ({attempt} of {backoffLimit}) failed, current phase: {installplan.model.status.phase}" + ) + + if not csvs_installed: + for csv_name in installplan.model.spec.clusterServiceVersionNames: + # get the CSV and check if installed + csv = get_csv(csv_name) + csvs_installed = csv.model.status.conditions.can_match( + { + "reason": "InstallSucceeded", + "phase": "Succeeded", + } + ) + if csvs_installed: + print( + f"\t- Verify CSV ({csv_name}) installed attempt ({attempt} of {backoffLimit}) success, current phase: {csv.model.status.phase}" + ) + else: + print( + f"\t- Verify CSV ({csv_name}) installed attempt ({attempt} of {backoffLimit}) failed, current phase: {csv.model.status.phase}" + ) + + sys.stdout.flush() + if installplan_installed and csvs_installed: + break + + return installplan_installed and csvs_installed + + +def error_and_exit(message: str, code: int): + """Print and error and exit""" + print() + print(f"ERROR: {message}") + sys.exit(code) diff --git a/charts/operators-installer/ci/test-install-multiple-operators-in-same-namespaces-approve-via-helm-hook-values.yaml b/charts/operators-installer/ci/test-install-multiple-operators-in-different-namespaces-approve-via-helm-hook-values.yaml similarity index 79% rename from charts/operators-installer/ci/test-install-multiple-operators-in-same-namespaces-approve-via-helm-hook-values.yaml rename to charts/operators-installer/ci/test-install-multiple-operators-in-different-namespaces-approve-via-helm-hook-values.yaml index dc122597..7374bd06 100644 --- a/charts/operators-installer/ci/test-install-multiple-operators-in-same-namespaces-approve-via-helm-hook-values.yaml +++ b/charts/operators-installer/ci/test-install-multiple-operators-in-different-namespaces-approve-via-helm-hook-values.yaml @@ -1,9 +1,9 @@ approveManualInstallPlanViaHook: true -installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.12 +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 operatorGroups: -- name: external-secrets-operator3 +- name: external-secrets-operator-3 createNamespace: true targetOwnNamespace: false otherTargetNamespaces: @@ -13,20 +13,20 @@ operatorGroups: otherTargetNamespaces: operators: -- channel: stable +- name: external-secrets-operator + channel: stable + csv: external-secrets-operator.v0.8.1 installPlanApproval: Manual - name: external-secrets-operator source: operatorhubio-catalog sourceNamespace: olm - csv: external-secrets-operator.v0.8.1 - namespace: external-secrets-operator3 + namespace: external-secrets-operator-3 installPlanVerifierActiveDeadlineSeconds: 1200 -- channel: alpha +- name: argocd-operator + channel: alpha + csv: argocd-operator.v0.6.0 installPlanApproval: Manual - name: argocd-operator source: operatorhubio-catalog sourceNamespace: olm - csv: argocd-operator.v0.6.0 namespace: argocd-operator installPlanVerifierActiveDeadlineSeconds: 1200 commonLabels: diff --git a/charts/operators-installer/ci/test-install-multiple-operators-in-different-namespace-approve-via-helm-hook-values.yaml b/charts/operators-installer/ci/test-install-multiple-operators-in-same-namespace-approve-via-helm-hook-values.yaml similarity index 98% rename from charts/operators-installer/ci/test-install-multiple-operators-in-different-namespace-approve-via-helm-hook-values.yaml rename to charts/operators-installer/ci/test-install-multiple-operators-in-same-namespace-approve-via-helm-hook-values.yaml index 1296d858..bd62c887 100644 --- a/charts/operators-installer/ci/test-install-multiple-operators-in-different-namespace-approve-via-helm-hook-values.yaml +++ b/charts/operators-installer/ci/test-install-multiple-operators-in-same-namespace-approve-via-helm-hook-values.yaml @@ -1,6 +1,6 @@ approveManualInstallPlanViaHook: true -installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.12 +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 operatorGroups: community: diff --git a/charts/operators-installer/ci/test-install-old-operator-approve-not-via-helm-hook-values.yaml b/charts/operators-installer/ci/test-install-old-operator-approve-not-via-helm-hook-values.yaml index 63699dff..98b19db7 100644 --- a/charts/operators-installer/ci/test-install-old-operator-approve-not-via-helm-hook-values.yaml +++ b/charts/operators-installer/ci/test-install-old-operator-approve-not-via-helm-hook-values.yaml @@ -1,9 +1,9 @@ approveManualInstallPlanViaHook: false -installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.12 +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 operatorGroups: -- name: external-secrets-operator2 +- name: external-secrets-operator-2 createNamespace: true targetOwnNamespace: false otherTargetNamespaces: @@ -15,7 +15,7 @@ operators: source: operatorhubio-catalog sourceNamespace: olm csv: external-secrets-operator.v0.8.1 - namespace: external-secrets-operator2 + namespace: external-secrets-operator-2 installPlanVerifierActiveDeadlineSeconds: 1200 commonLabels: test-label: xyz123 diff --git a/charts/operators-installer/ci/test-install-old-operator-approve-via-helm-hook-values.yaml b/charts/operators-installer/ci/test-install-old-operator-approve-via-helm-hook-values.yaml index de97a08e..2a4836b8 100644 --- a/charts/operators-installer/ci/test-install-old-operator-approve-via-helm-hook-values.yaml +++ b/charts/operators-installer/ci/test-install-old-operator-approve-via-helm-hook-values.yaml @@ -1,9 +1,9 @@ approveManualInstallPlanViaHook: true -installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.12 +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 operatorGroups: -- name: external-secrets-operator1 +- name: external-secrets-operator-1 createNamespace: true targetOwnNamespace: false otherTargetNamespaces: @@ -15,7 +15,7 @@ operators: source: operatorhubio-catalog sourceNamespace: olm csv: external-secrets-operator.v0.8.1 - namespace: external-secrets-operator1 + namespace: external-secrets-operator-1 installPlanVerifierActiveDeadlineSeconds: 1200 commonLabels: test-label: xyz123 diff --git a/charts/operators-installer/ci/test-install-operator-first-time-with-automatic-intermediate-manual-upgrades-values.yaml b/charts/operators-installer/ci/test-install-operator-first-time-with-automatic-intermediate-manual-upgrades-values.yaml new file mode 100644 index 00000000..81966578 --- /dev/null +++ b/charts/operators-installer/ci/test-install-operator-first-time-with-automatic-intermediate-manual-upgrades-values.yaml @@ -0,0 +1,28 @@ +# NOTE: +# this doesn't REALLY do a hard core test of the `automaticIntermediateManualUpgrades` option +# because the operator is not already installed, but it does at least test some of the code path + +approveManualInstallPlanViaHook: true + +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 + +operatorGroups: +- name: argocd-operator-1 + createNamespace: true + targetOwnNamespace: true + otherTargetNamespaces: + +operators: +- name: argocd-operator + channel: alpha + csv: argocd-operator.v0.6.0 + installPlanApproval: Manual + source: operatorhubio-catalog + sourceNamespace: olm + namespace: argocd-operator-1 + installPlanVerifierActiveDeadlineSeconds: 1200 + automaticIntermediateManualUpgrades: true + config: + env: + - name: DISABLE_DEFAULT_ARGOCD_INSTANCE + value: "true" diff --git a/charts/operators-installer/ci/test-install-operator-subscription-with-config-values.yaml b/charts/operators-installer/ci/test-install-operator-subscription-with-config-values.yaml new file mode 100644 index 00000000..050b14e2 --- /dev/null +++ b/charts/operators-installer/ci/test-install-operator-subscription-with-config-values.yaml @@ -0,0 +1,23 @@ +approveManualInstallPlanViaHook: true + +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 + +operatorGroups: +- name: argocd-operator-2 + createNamespace: true + targetOwnNamespace: true + otherTargetNamespaces: + +operators: +- name: argocd-operator + channel: alpha + csv: argocd-operator.v0.6.0 + installPlanApproval: Manual + source: operatorhubio-catalog + sourceNamespace: olm + namespace: argocd-operator-2 + installPlanVerifierActiveDeadlineSeconds: 1200 + config: + env: + - name: DISABLE_DEFAULT_ARGOCD_INSTANCE + value: "true" diff --git a/charts/operators-installer/ci/test-install-operator-subscription-with-config.yaml b/charts/operators-installer/ci/test-install-operator-subscription-with-config.yaml deleted file mode 100644 index 90804b39..00000000 --- a/charts/operators-installer/ci/test-install-operator-subscription-with-config.yaml +++ /dev/null @@ -1,20 +0,0 @@ -approveManualInstallPlanViaHook: true - -installPlanApproverAndVerifyJobsImage: registry.redhat.io/openshift4/ose-cli:v4.12 - -operators: -- channel: gitops-1.10 - installPlanApproval: Manual - name: openshift-gitops-operator - source: redhat-operators - sourceNamespace: openshift-marketplace - csv: openshift-gitops-operator.v1.10.1 - namespace: openshift-gitops-operator - config: - env: - - name: DISABLE_DEFAULT_ARGOCD_INSTANCE - value: "true" - -operatorGroups: -- name: openshift-gitops-operator - createNamespace: openshift-gitops-operator diff --git a/charts/operators-installer/ci/test-install-operator-with-channel-number-values.yaml b/charts/operators-installer/ci/test-install-operator-with-channel-number-values.yaml new file mode 100644 index 00000000..a623b5d0 --- /dev/null +++ b/charts/operators-installer/ci/test-install-operator-with-channel-number-values.yaml @@ -0,0 +1,19 @@ +approveManualInstallPlanViaHook: true + +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 + +operatorGroups: +- name: aqua-operator + createNamespace: true + targetOwnNamespace: true + otherTargetNamespaces: + +operators: +- name: aqua + channel: 2022.4.0 + csv: aqua-operator.2022.4.14 + installPlanApproval: Manual + source: operatorhubio-catalog + sourceNamespace: olm + namespace: aqua-operator + installPlanVerifierActiveDeadlineSeconds: 1200 diff --git a/charts/operators-installer/ci/test-install-operator-with-channel-number.yaml b/charts/operators-installer/ci/test-install-operator-with-channel-number.yaml deleted file mode 100644 index 944b88b7..00000000 --- a/charts/operators-installer/ci/test-install-operator-with-channel-number.yaml +++ /dev/null @@ -1,26 +0,0 @@ -approveManualInstallPlanViaHook: false - -installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.12 - -commonLabels: - localLabelOne: hello - -global: - commonLabels: - globalLabelOne: world - -operatorGroups: - aqua: - name: aqua - createNamespace: true - targetOwnNamespace: false - otherTargetNamespaces: [] - -operators: - aqua: - channel: "2022.4.0" - installPlanApproval: Manual - name: aqua-operator-certified - source: operatorhubio-catalog - sourceNamespace: openshift-marketplace - csv: aqua-operator.v2022.4.348 diff --git a/charts/operators-installer/ci/test-install-operator-with-long-name.yaml b/charts/operators-installer/ci/test-install-operator-with-long-name-values.yaml similarity index 98% rename from charts/operators-installer/ci/test-install-operator-with-long-name.yaml rename to charts/operators-installer/ci/test-install-operator-with-long-name-values.yaml index 07604608..b9293879 100644 --- a/charts/operators-installer/ci/test-install-operator-with-long-name.yaml +++ b/charts/operators-installer/ci/test-install-operator-with-long-name-values.yaml @@ -1,6 +1,6 @@ approveManualInstallPlanViaHook: false -installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.12 +installPlanApproverAndVerifyJobsImage: quay.io/openshift/origin-cli:4.15 operatorGroups: cmo: @@ -11,11 +11,11 @@ operatorGroups: operators: cmo: + name: costmanagement-metrics-operator channel: stable + csv: costmanagement-metrics-operator.2.0.0 installPlanApproval: Manual - name: costmanagement-metrics-operator source: operatorhubio-catalog sourceNamespace: olm - csv: costmanagement-metrics-operator.2.0.0 namespace: costmanagement-metrics-operator installPlanVerifierActiveDeadlineSeconds: 1200 diff --git a/charts/operators-installer/templates/ConfigMap_operators-installer-approver-scripts.yaml b/charts/operators-installer/templates/ConfigMap_operators-installer-approver-scripts.yaml new file mode 100644 index 00000000..53770e33 --- /dev/null +++ b/charts/operators-installer/templates/ConfigMap_operators-installer-approver-scripts.yaml @@ -0,0 +1,28 @@ +{{- range .Values.operators }} +{{- if eq .installPlanApproval "Manual" }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "operators-installer.scriptsName" . }} + namespace: {{ .namespace | default $.Release.Namespace }} + labels: + {{- include "operators-installer.labels" $ | nindent 4 }} + annotations: + {{- if $.Values.approveManualInstallPlanViaHook }} + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + "helm.sh/hook-weight": "10" + {{- else }} + argocd.argoproj.io/sync-wave: "-30" + {{- end }} +data: + installplan_utils.py: |- +{{ tpl ( $.Files.Get "_scripts/installplan_utils.py" ) $ | indent 4 }} + installplan-approver.py: |- +{{ tpl ( $.Files.Get "_scripts/installplan-approver.py" ) $ | indent 4 }} + installplan-incremental-approver.py: |- +{{ tpl ( $.Files.Get "_scripts/installplan-incremental-approver.py" ) $ | indent 4 }} + installplan-verifier.py: |- +{{ tpl ( $.Files.Get "_scripts/installplan-verifier.py" ) $ | indent 4 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/operators-installer/templates/Job_installplan-approver.yaml b/charts/operators-installer/templates/Job_installplan-approver.yaml index 8b8a842f..4d6a7b03 100644 --- a/charts/operators-installer/templates/Job_installplan-approver.yaml +++ b/charts/operators-installer/templates/Job_installplan-approver.yaml @@ -2,10 +2,11 @@ {{- if eq .installPlanApproval "Manual" }} --- # create one installplan-approver job per manual operator +# Finds and approves InstallPlan matching the given subscription CSV apiVersion: batch/v1 kind: Job metadata: - name: {{ printf "%s-%s" .csv "approver" | trunc -63 | trimAll "-" }} + name: {{ printf "%s-%s" .csv "approver" | trunc -63 | replace "." "-" | trimAll "-" }} namespace: {{ .namespace | default $.Release.Namespace }} labels: {{- include "operators-installer.labels" $ | nindent 4 }} @@ -21,81 +22,49 @@ spec: completions: 1 parallelism: 1 backoffLimit: {{ .installPlanApproverRetries | default 10 }} - activeDeadlineSeconds: {{ .installPlanApproverActiveDeadlineSeconds | default 240 }} + activeDeadlineSeconds: {{ .installPlanApproverActiveDeadlineSeconds }} template: spec: containers: - name: installplan-approver image: {{ $.Values.installPlanApproverAndVerifyJobsImage }} - command: - - /bin/bash - - -c - - | - export HOME=/tmp/approver - - echo - echo "Get Subscription (${SUBSCRIPTION}) UID" - subscriptionUID=$(oc get subscriptions.operators.coreos.com --field-selector metadata.name=${SUBSCRIPTION} -o=jsonpath="{.items[0].metadata.uid}") - echo "Subscription (${SUBSCRIPTION}) UID: ${subscriptionUID}" - if [ ! -z "subscriptionUID" ]; then - # this complicated go-template finds an InstallPlan where both the .spec.clusterServiceVersionNames contains the expected CSV - # and .metadata.ownerReferences contains the expected Subscription owner. JsonPath doesn't let you do and statements and go-templates - # done seem to have a function for checking if a value is in an array without iterating. so here we are. - # NOTE 1: if for whatever reason multiple InstallPlans are found that are associated with the Subscription for the correct CSV, only the first will be approved - # NOTE 2: this is nested in {{` `}} so that helm doesn't try to interpret the go template - {{`installPlanGoTemplate=$(cat << EOF - {{- \$installPlanName := "" -}} - {{- range .items -}} - {{- \$installPlanItem := . -}} - {{- range .spec.clusterServiceVersionNames -}} - {{- if and (eq . "${SUBSCRIPTION_CSV}") (not \$installPlanName) -}} - {{- range \$installPlanItem.metadata.ownerReferences -}} - {{- if eq .uid "${subscriptionUID}" -}} - {{- \$installPlanName = \$installPlanItem.metadata.name -}} - {{- end -}} - {{- end -}} - {{- end -}} - {{- end -}} - {{- end -}} - {{ \$installPlanName }} - EOF - )`}} - - echo - echo "Get InstallPlan for CSV (${SUBSCRIPTION_CSV}) with Subscription (${SUBSCRIPTION_CSV}) (${subscriptionUID}) owner" - installPlan=$(oc get installplan -o=go-template="${installPlanGoTemplate}") - echo "InstallPlan for CSV (${SUBSCRIPTION_CSV}) with Subscription (${SUBSCRIPTION_CSV}) (${subscriptionUID}) owner: ${installPlan}" - if [ ! -z "${installPlan}" ]; then - echo - echo "Check InstallPlan (${installPlan}) approval" - if installPlanApproved=$(oc get installplan ${installPlan} -o=jsonpath="{.spec.approved}"); then - if [ "${installPlanApproved}" == "false" ]; then - echo "Approving InstallPlan (${installPlan})" - oc patch installplan ${installPlan} --type=json -p='[{"op":"replace","path": "/spec/approved", "value": true}]' - else - echo "InstallPlan (${installPlan})already approved" - fi - exit 0 - else - echo "Failed to look up InstallPlan (${installPlan}) approval" - exit 1 - fi - else - echo - echo "Could not find InstallPlan for CSV (${SUBSCRIPTION_CSV}) with Subscription (${SUBSCRIPTION_CSV}) (${subscriptionUID}) owner. This can happen if InstallPlan isn't created yet. Try again." - exit 1 - fi - else - echo - echo "Failed to get Subscription ($SUBSCRIPTION) UID. This really shouldn't happen." - exit 1 - fi + command: ["/bin/sh","-c"] + args: + - >- + {{- if $.Values.installRequiredPythonLibraries }} + python3 -m venv /tmp/venv && + source /tmp/venv/bin/activate && + python3 -m pip install openshift-client semver==2.13.0 --index-url {{ $.Values.pythonIndexURL }} --extra-index-url {{ $.Values.pythonExtraIndexURL }} && + {{- end }} + {{- if .automaticIntermediateManualUpgrades }} + python3 /scripts/installplan-incremental-approver.py + {{- else }} + python3 /scripts/installplan-approver.py + {{- end }} imagePullPolicy: Always env: - - name: SUBSCRIPTION_CSV - value: {{ .csv }} + - name: CSV + value: "{{ .csv }}" - name: SUBSCRIPTION - value: {{ .name }} + value: "{{ .name }}" + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .automaticIntermediateManualUpgrades }} + - name: INCREMENTAL_INSTALL_BACKOFF_LIMIT + value: "{{ .automaticIntermediateManualUpgradesIncrementalInstallBackoffLimit | default 10 }}" + - name: INCREMENTAL_INSTALL_DELAY_INCREMENT + value: "{{ .automaticIntermediateManualUpgradesIncrementalInstallDelayIncrement | default 5 }}" + {{- end }} + volumeMounts: + - name: operators-installer-approver-scripts + mountPath: /scripts + volumes: + - name: operators-installer-approver-scripts + configMap: + name: {{ include "operators-installer.scriptsName" . }} + defaultMode: 0777 dnsPolicy: ClusterFirst restartPolicy: Never serviceAccount: {{ include "operators-installer.approverName" . }} diff --git a/charts/operators-installer/templates/Job_installplan-complete-verifier.yaml b/charts/operators-installer/templates/Job_installplan-complete-verifier.yaml index 50d8bf95..5e549e24 100644 --- a/charts/operators-installer/templates/Job_installplan-complete-verifier.yaml +++ b/charts/operators-installer/templates/Job_installplan-complete-verifier.yaml @@ -5,7 +5,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: {{ printf "%s-%s" .csv "verifier" | trunc -63 | trimAll "-" }} + name: {{ printf "%s-%s" .csv "verifier" | trunc -63 | replace "." "-" | trimAll "-" }} namespace: {{ .namespace | default $.Release.Namespace }} labels: {{- include "operators-installer.labels" $ | nindent 4 }} @@ -21,81 +21,39 @@ spec: completions: 1 parallelism: 1 backoffLimit: {{ .installPlanVerifierRetries | default 10 }} - activeDeadlineSeconds: {{ .installPlanVerifierActiveDeadlineSeconds | default 240 }} + activeDeadlineSeconds: {{ .installPlanVerifierActiveDeadlineSeconds }} template: spec: containers: - name: installplan-complete-verifier image: {{ $.Values.installPlanApproverAndVerifyJobsImage }} - command: - - /bin/bash - - -c - - | - export HOME=/tmp/approver - - echo - echo "Get Subscription (${SUBSCRIPTION}) UID" - subscriptionUID=$(oc get subscriptions.operators.coreos.com --field-selector metadata.name=${SUBSCRIPTION} -o=jsonpath="{.items[0].metadata.uid}") - echo "Subscription (${SUBSCRIPTION}) UID: ${subscriptionUID}" - if [ ! -z "subscriptionUID" ]; then - # this complicated go-template finds an InstallPlan where both the .spec.clusterServiceVersionNames contains the expected CSV - # and .metadata.ownerReferences contains the expected Subscription owner. JsonPath doesn't let you do and statements and go-templates - # done seem to have a function for checking if a value is in an array without iterating. so here we are. - # NOTE 1: if for whatever reason multiple InstallPlans are found that are associated with the Subscription for the correct CSV, only the first will be approved - # NOTE 2: this is nested in {{` `}} so that helm doesn't try to interpret the go template - {{`installPlanGoTemplate=$(cat << EOF - {{- \$installPlanName := "" -}} - {{- range .items -}} - {{- \$installPlanItem := . -}} - {{- range .spec.clusterServiceVersionNames -}} - {{- if and (eq . "${SUBSCRIPTION_CSV}") (not \$installPlanName) -}} - {{- range \$installPlanItem.metadata.ownerReferences -}} - {{- if eq .uid "${subscriptionUID}" -}} - {{- \$installPlanName = \$installPlanItem.metadata.name -}} - {{- end -}} - {{- end -}} - {{- end -}} - {{- end -}} - {{- end -}} - {{ \$installPlanName }} - EOF - )`}} - - echo - echo "Get InstallPlan for CSV (${SUBSCRIPTION_CSV}) with Subscription (${SUBSCRIPTION_CSV}) (${subscriptionUID}) owner" - installPlan=$(oc get installplan -o=go-template="${installPlanGoTemplate}") - echo "InstallPlan for CSV (${SUBSCRIPTION_CSV}) with Subscription (${SUBSCRIPTION_CSV}) (${subscriptionUID}) owner: ${installPlan}" - if [ ! -z "${installPlan}" ]; then - echo - echo "Check InstallPlan (${installPlan}) phase" - if installPlanPhase=$(oc get installplan ${installPlan} -o=jsonpath="{.status.phase}"); then - if [ "${installPlanPhase}" == "Complete" ]; then - echo "InstallPlan (${installPlan}) complete" - exit 0 - else - echo "InstallPlan (${installPlan}) not yet complete: ${installPlanPhase}" - exit 1 - fi - else - echo "Failed to look up InstallPlan (${installPlan}) phase" - exit 1 - fi - else - echo - echo "Could not find InstallPlan for CSV (${SUBSCRIPTION_CSV}) with Subscription (${SUBSCRIPTION_CSV}) (${subscriptionUID}) owner. This can happen if InstallPlan isn't created yet. Try again." - exit 1 - fi - else - echo - echo "Failed to get Subscription ($SUBSCRIPTION) UID. This really shouldn't happen." - exit 1 - fi + command: ["/bin/sh","-c"] + args: + - >- + {{- if $.Values.installRequiredPythonLibraries }} + python3 -m venv /tmp/venv && + source /tmp/venv/bin/activate && + python3 -m pip install openshift-client semver==2.13.0 --index-url {{ $.Values.pythonIndexURL }} --extra-index-url {{ $.Values.pythonExtraIndexURL }} && + {{- end }} + python3 /scripts/installplan-verifier.py imagePullPolicy: Always env: - - name: SUBSCRIPTION_CSV + - name: CSV value: {{ .csv }} - name: SUBSCRIPTION value: {{ .name }} + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: operators-installer-approver-scripts + mountPath: /scripts + volumes: + - name: operators-installer-approver-scripts + configMap: + name: {{ include "operators-installer.scriptsName" . }} + defaultMode: 0777 dnsPolicy: ClusterFirst restartPolicy: Never serviceAccount: {{ include "operators-installer.approverName" . }} diff --git a/charts/operators-installer/templates/Role_installplan-approver.yaml b/charts/operators-installer/templates/Role_installplan-approver.yaml index 033127bb..50138ba1 100644 --- a/charts/operators-installer/templates/Role_installplan-approver.yaml +++ b/charts/operators-installer/templates/Role_installplan-approver.yaml @@ -27,6 +27,7 @@ rules: resources: - installplans - subscriptions + - clusterserviceversions verbs: - get - list diff --git a/charts/operators-installer/templates/_helpers.tpl b/charts/operators-installer/templates/_helpers.tpl index 4f20952b..ba2fdde6 100644 --- a/charts/operators-installer/templates/_helpers.tpl +++ b/charts/operators-installer/templates/_helpers.tpl @@ -60,5 +60,12 @@ app.kubernetes.io/instance: {{ .Release.Name }} Name to use for approver SA, Role, and RoleBinding */}} {{- define "operators-installer.approverName" -}} -{{- printf "%s-%s" .csv "-approver" | trunc -63 | trimAll "-" }} +{{- printf "%s-%s" .csv "approver" | trunc -63 | replace "." "-" | trimAll "-" }} {{- end }} + +{{/* +Name to use for approver SA, Role, and RoleBinding +*/}} +{{- define "operators-installer.scriptsName" -}} +{{- printf "%s-%s" .csv "scripts" | trunc -63 | replace "." "-" | trimAll "-" }} +{{- end }} \ No newline at end of file diff --git a/charts/operators-installer/values.yaml b/charts/operators-installer/values.yaml index f75f17a1..60379a3d 100644 --- a/charts/operators-installer/values.yaml +++ b/charts/operators-installer/values.yaml @@ -10,7 +10,27 @@ approveManualInstallPlanViaHook: true # Image to use for the InstallPlan Approver and Verify Jobs -installPlanApproverAndVerifyJobsImage: registry.redhat.io/openshift4/ose-cli:v4.10@sha256:7804ea66ea8ca0f414148b8b3b52ae454800785e80a32bd8a5eb2db789014a00 +installPlanApproverAndVerifyJobsImage: registry.redhat.io/openshift4/ose-cli:v4.15@sha256:3f2123f42ae7358e1fece41d461bf331f144480da8b7711b9a93aca150f33f3f + +# If `true`, install the required Python libraries (openshift-client, semver==2.13.0) dynamically +# from the given `pythonIndexURL` and `pythonExtraIndexURL` into the `installPlanApproverAndVerifyJobsImage` at run time +# +# This is because the supported ose-cli image from red hat does not include the Python libraries +# and it is beyond the scope of this helm chart to provide an image that provides those tools. +# +# If `false`, such as if you are running in a disconnected environment, you either need to change `installPlanApproverAndVerifyJobsImage` to be an image that includes `oc` and the required python libraires. +# Or change `pythonIndexURL` and `pythonExtraIndexURL` to be a local python index with the required python libraries. +# +# Required Python libraries +# * openshift-client +# * semver==2.13.0 +installRequiredPythonLibraries: true + +# If `installRequiredPythonLibraries` is `true` then use this python index to pull required libraries +pythonIndexURL: https://pypi.org/simple/ + +# If `installRequiredPythonLibraries` is `true` then use this python extra index to pull required library dependencies +pythonExtraIndexURL: https://pypi.org/simple/ # EXAMPLE: declaratively controlled operator version operators: