diff --git a/scripts/longhorn_rancher_chart_migration.sh b/scripts/longhorn_rancher_chart_migration.sh new file mode 100755 index 0000000..3adc252 --- /dev/null +++ b/scripts/longhorn_rancher_chart_migration.sh @@ -0,0 +1,201 @@ +#!/bin/bash +#set -x + +kubectl get-all version &> /dev/null +if [ $? -ne 0 ]; then + echo "ERROR: command (kubectl get-all) is not found. Please install it here: https://github.com/corneliusweig/ketall#installation" + exit 1 +fi + +set -e + +usage() { + echo "" + echo "The migration includes:" + echo "1. Running the script with --type=migrate to migrate the labels and annotations for Longhorn resources" + echo "2. Manually installing Longhorn chart in app&marketplace UI" + echo "3. Running script with --type=cleanup to remove the old Longhorn chart from old catalog UI" + echo "" + echo "usage:" + echo "$0 [options]" + echo " -u | --upstream-kubeconfig: upstream rancher cluster kubeconfig path" + echo " -d | --downstream-kubeconfig: downstream cluster kubeconfig path" + echo " -t | --type: specify the type you want to run (migrate or cleanup)" + echo " --dry-run: do not run migriation" + echo "" + echo "example:" + echo " $0 -u /path/to/upstream/rancher/cluster/kubeconfig -d /path/to/downstream/cluster/kubeconfig" +} + +SCRIPT_DIR="$(dirname "$0")" +UPSTREAM_KUBECONFIG="" +DOWNSTREAM_KUBECONFIG="" +KUBECTL_DRY_RUN="" + +while [ "$1" != "" ]; do + case $1 in + -u | --upstream-kubeconfig) + shift + UPSTREAM_KUBECONFIG="$1" + ;; + -d | --downstream-kubeconfig) + shift + DOWNSTREAM_KUBECONFIG="$1" + ;; + -t | --type) + shift + TYPE="$1" + ;; + --dry-run) + KUBECTL_DRY_RUN="--dry-run=client" + ;; + *) + usage + exit 1 + ;; + esac + shift +done + +if [ -z "$UPSTREAM_KUBECONFIG" ]; then + echo "--upstream-kubeconfig is mandatory" + usage + exit 1 +fi +if [ -z "$DOWNSTREAM_KUBECONFIG" ]; then + echo "--downstream-kubeconfig is mandatory" + usage + exit 1 +fi +if [ "$TYPE" != "migrate" ] && [ "$TYPE" != "cleanup" ] ; then + echo "--type must be set to migrate or cleanup" + usage + exit 1 +fi + + +# Longhorn Namespace +RELEASE_NAMESPACE=longhorn-system +# Longhorn Release Name +RELEASE_NAME=longhorn-system + + +echo "Looking up Rancher Project App '${RELEASE_NAME}' ..." +DOWNSTREAMCLUSTERID=$(cat ${DOWNSTREAM_KUBECONFIG} | grep "server:.*https://.*/k8s/clusters/.*" | awk -F'/' '{print $(NF)}' | awk -F'"' '{print $1}') +RANCHERAPP=$(kubectl --kubeconfig ${UPSTREAM_KUBECONFIG} get --all-namespaces apps.project.cattle.io -o jsonpath='{range.items[*]}{.metadata.namespace} {.metadata.name} {.spec.targetNamespace} {.spec.projectName} {.spec.externalId}{"\n"}{end}' | grep -s "${RELEASE_NAME} ${RELEASE_NAMESPACE} ${DOWNSTREAMCLUSTERID}") +RANCHERAPPNS=$(echo "${RANCHERAPP}" | awk '{print $1}') +RANCHERAPPEXTERNALID=$(echo "${RANCHERAPP}" | awk '{print $5}') +RANCHERAPPCATALOG=$(echo "${RANCHERAPPEXTERNALID}" | sed -n 's/.*catalog=\(.*\)/\1/p' | awk -F '&' '{print $1}' | sed 's/migrated-//') +RANCHERAPPTEMPLATE=$(echo "${RANCHERAPPEXTERNALID}" | sed -n 's/.*template=\(.*\)/\1/p' | awk -F '&' '{print $1}') +RANCHERAPPTEMPLATEVERSION=$(echo "${RANCHERAPPEXTERNALID}" | sed -n 's/.*version=\(.*\)/\1/p' | awk -F '&' '{print $1}') +RANCHERAPPVALUES="" +RANCHERAPPANSWERS="" + +if [ -z "$DOWNSTREAMCLUSTERID" ] || [ -z "$RANCHERAPP" ] || [ -z "$RANCHERAPPNS" ] || [ -z "$RANCHERAPPCATALOG" ] || [ -z "$RANCHERAPPTEMPLATE" ] || [ -z "$RANCHERAPPTEMPLATEVERSION" ]; then + echo "Rancher Project App '${RELEASE_NAME}' not found!" + exit 1 +fi + +RANCHERAPPVALUES=$(kubectl --kubeconfig ${UPSTREAM_KUBECONFIG} -n ${RANCHERAPPNS} get apps.project.cattle.io ${RELEASE_NAME} -o go-template='{{if .spec.valuesYaml}}{{.spec.valuesYaml}}{{end}}') +if [ -z "${RANCHERAPPVALUES}" ]; then + RANCHERAPPANSWERS=$(kubectl --kubeconfig ${UPSTREAM_KUBECONFIG} -n ${RANCHERAPPNS} get apps.project.cattle.io ${RELEASE_NAME} -o go-template='{{if .spec.answers}}{{range $key,$value := .spec.answers}}{{$key}}: {{$value}}{{"\n"}}{{end}}{{end}}' | sed 's/: /=/' | sed 's/$/,/' | sed '$ s/.$//' | tr -d '\n') +fi +if [ -z "${RANCHERAPPVALUES:-$RANCHERAPPANSWERS}" ]; then + echo "No valid answers found!" + exit 1 +fi + +echo "" +echo "Rancher Project App '${RELEASE_NAME}' found:" +echo " Project-Namespace: ${RANCHERAPPNS}" +echo " Downstream-Cluster: ${DOWNSTREAMCLUSTERID}" +echo " Catalog: ${RANCHERAPPCATALOG}" +echo " Template: ${RANCHERAPPTEMPLATE} (${RANCHERAPPTEMPLATEVERSION})" +echo " Answers:" +printf '%s\n' "${RANCHERAPPVALUES:-$RANCHERAPPANSWERS}" +echo "" + +if [ "$TYPE" == "cleanup" ] ; then + + MANAGER=$(kubectl --kubeconfig ${DOWNSTREAM_KUBECONFIG} -n ${RELEASE_NAMESPACE} get ds longhorn-manager -ojsonpath="{.metadata.labels['app\.kubernetes\.io/managed-by']}") + if [ $MANAGER != "Helm" ] ; then + echo "Labels have not been migrated. Did you run the part 1 by specifying the flag --type=migrate ?" + exit 1 + fi + + echo "" + echo "Patching Project App Catalog ..." + kubectl --kubeconfig ${UPSTREAM_KUBECONFIG} -n ${RANCHERAPPNS} ${KUBECTL_DRY_RUN} patch apps.project.cattle.io ${RELEASE_NAME} --type=merge --patch-file=/dev/stdin <<-EOF + { + "metadata": { + "annotations": { + "cattle.io/skipUninstall": "true", + "catalog.cattle.io/ui-source-repo": "helm3-library", + "catalog.cattle.io/ui-source-repo-type": "cluster", + "apps.cattle.io/migrated": "true" + } + } + } +EOF + + if [ $? -ne 0 ]; then + echo "Failed Patching Project App Catalog" + exit 1 + fi + + echo "" + echo "Deleting Project App Catalog ..." + kubectl --kubeconfig ${UPSTREAM_KUBECONFIG} -n ${RANCHERAPPNS} ${KUBECTL_DRY_RUN} delete apps.project.cattle.io ${RELEASE_NAME} + + exit 0 +fi + +echo "" +echo "" +echo "Checking concurrent-automatic-engine-upgrade-per-node-limit setting ..." +SETTING=$(kubectl --kubeconfig ${DOWNSTREAM_KUBECONFIG} -n ${RELEASE_NAMESPACE} get settings.longhorn.io concurrent-automatic-engine-upgrade-per-node-limit -ojsonpath="{.value}") +if [ "$SETTING" != "0" ]; then + echo "concurrent-automatic-engine-upgrade-per-node-limit must be set to 0 before the migration" + exit 1 +fi + +echo "" +echo "" +echo "Looking up existing Resources ..." +RESOURCES=$(kubectl get-all --kubeconfig ${DOWNSTREAM_KUBECONFIG} --exclude AppRevision -o name -l io.cattle.field/appId=${RELEASE_NAME} 2>/dev/null | sort) +if [[ "$RESOURCES" == "No resources"* ]]; then + RESOURCES="" +fi + +echo "" +echo "Patching CRD Resources ..." +for resource in $RESOURCES; do + if [[ $resource == "customresourcedefinition.apiextensions.k8s.io/"* ]]; then + kubectl --kubeconfig ${DOWNSTREAM_KUBECONFIG} -n ${RELEASE_NAMESPACE} ${KUBECTL_DRY_RUN} annotate --overwrite ${resource} "meta.helm.sh/release-name"="longhorn-crd" "meta.helm.sh/release-namespace"="${RELEASE_NAMESPACE}" "helm.sh/resource-policy"="keep" + kubectl --kubeconfig ${DOWNSTREAM_KUBECONFIG} -n ${RELEASE_NAMESPACE} ${KUBECTL_DRY_RUN} label --overwrite ${resource} "app.kubernetes.io/managed-by"="Helm" + fi + +done + +echo "" +echo "Patching Other Resources ..." +for resource in $RESOURCES; do + if [[ $resource == "customresourcedefinition.apiextensions.k8s.io/"* ]]; then + continue + fi + kubectl --kubeconfig ${DOWNSTREAM_KUBECONFIG} -n ${RELEASE_NAMESPACE} ${KUBECTL_DRY_RUN} annotate --overwrite ${resource} "meta.helm.sh/release-name"="longhorn" "meta.helm.sh/release-namespace"="${RELEASE_NAMESPACE}" + kubectl --kubeconfig ${DOWNSTREAM_KUBECONFIG} -n ${RELEASE_NAMESPACE} ${KUBECTL_DRY_RUN} label --overwrite ${resource} "app.kubernetes.io/managed-by"="Helm" +done + + +echo "" +echo "-----------------------------" +echo "Successfully updated the annotations and labels for the resources!" +echo "Next step:" +echo " 1. Go to Rancher UI -> Go to the downstream cluster -> App&Marketplace -> Charts" +echo " 2. Find and select the Longhorn chart" +echo " 3. Select the chart version corresponding the Longhorn version ${RANCHERAPPTEMPLATEVERSION}" +echo " 4. Install the chart with the correct helm values. Here are the helm values of your old charts: " +printf '%s\n' "${RANCHERAPPVALUES:-$RANCHERAPPANSWERS}" +echo " 5. Verify that the migrated charts are working ok" +echo " 6. Run this script again with the flag --type=cleanup to remove the old chart from the legacy UI"