#!/usr/bin/env bash

set -Eeuo pipefail

isRollback=false

certManagerVersion=""
oldNonUpdatableVersion=""

shouldDeletePreviousManager=false

parseParams() {
    while :; do
        case "${1-}" in
        --rollback) isRollback=true ;;
        -?*) ./lib/die.sh "Unknown option: $1" ;;
        *) break ;;
        esac
        shift
    done
}

# Run 'auth can-i' checks based on the content of cert-manager.yaml
runChecks() {
    ./lib/msg.sh "Checking permissions..."
    ./lib/check-permissions.sh "create CustomResourceDefinition"
    ./lib/check-permissions.sh "delete CustomResourceDefinition"
    ./lib/check-permissions.sh "create Namespace"
    ./lib/check-permissions.sh "delete Namespace"
    ./lib/check-permissions.sh "create ServiceAccount"
    ./lib/check-permissions.sh "delete ServiceAccount"
    ./lib/check-permissions.sh "create ClusterRole"
    ./lib/check-permissions.sh "patch ClusterRole"
    ./lib/check-permissions.sh "create ClusterRoleBinding"
    ./lib/check-permissions.sh "patch ClusterRoleBinding"
    ./lib/check-permissions.sh "create Role"
    ./lib/check-permissions.sh "patch Role"
    ./lib/check-permissions.sh "create RoleBinding"
    ./lib/check-permissions.sh "patch RoleBinding"
    ./lib/check-permissions.sh "create Service"
    ./lib/check-permissions.sh "patch Service"
    ./lib/check-permissions.sh "create Deployment"
    ./lib/check-permissions.sh "patch Deployment"
    ./lib/check-permissions.sh "create MutatingWebhookConfiguration"
    ./lib/check-permissions.sh "patch MutatingWebhookConfiguration"
    ./lib/check-permissions.sh "create ValidatingWebhookConfiguration"
    ./lib/check-permissions.sh "patch ValidatingWebhookConfiguration"
}

# Check the current cert-manager deployment version
checkCertManagerVersion() {
    certManagerGetCommand="kubectl get deployment cert-manager -n cert-manager -o yaml > lib/tmp/current-cert-manager.yaml"
    ./lib/msg.sh "reading current cert-manager: $certManagerGetCommand"
    if ! eval "$certManagerGetCommand"; then
        ./lib/die.sh "cert-manager deployment not found"
    fi

    containerIndex="$(lib/yq_linux_amd64 'e' '.spec.template.spec.containers.[] | select(.name == "cert-manager") | path | .[-1]' 'lib/tmp/current-cert-manager.yaml')"
    certManagerImage="$(lib/yq_linux_amd64 'e' ".spec.template.spec.containers[$containerIndex].image" 'lib/tmp/current-cert-manager.yaml')"
    certManagerVersion=$( echo "$certManagerImage" | cut -d ':' -f 2 )
    oldNonUpdatableVersion="$(lib/yq_linux_amd64 'e' '.old-non-updatable-cert-manager-version' 'lib/config.yaml')"
    if [[ "$oldNonUpdatableVersion" = "$certManagerVersion" ]]; then
        shouldDeletePreviousManager=true
    fi
}

# die, if the cert manager can't be upgraded because it has a version we don't expect
dieIfWrongVersionUpgrade() {
    checkCertManagerVersion
    if [[ "$oldNonUpdatableVersion" != "$certManagerVersion" && "$certManagerVersion" != v1.* ]]; then
        ./lib/die.sh "Current version of cert-manager should be either $oldNonUpdatableVersion or at least v1.0. Aborting"
    fi
}

# die, if the cert manager can't be downgraded because it has a version we don't expect
dieIfWrongVersionDowngrade() {
    checkCertManagerVersion
    downgradeableVersion="$(lib/yq_linux_amd64 'e' '.downgradeable-cert-manager-version' 'lib/config.yaml')"
    if [[ "$downgradeableVersion" != "$certManagerVersion" ]]; then
        ./lib/die.sh "No automatic downgrade available for this version. Aborting"
    fi
}

# Remove the current installation with a new manifest
removeCurrentInstallation() {
    command="kubectl delete -f cert-manager/cert-manager-remove-v0.10.yaml"
    ./lib/msg.sh "Removing the current installation with a new manifest: $command"
    eval "$command"
}

# Install the new version of cert-manager
certManagerInstall() {
    command="kubectl apply -f cert-manager/cert-manager.yaml"
    ./lib/msg.sh "Installing the new version of cert-manager: $command"
    eval "$command"

    waitCommand="kubectl rollout status -n cert-manager deployment/cert-manager --timeout=5m"
    ./lib/msg.sh "Waiting for the cert-manager deployment to be ready: $waitCommand"
    eval "$waitCommand"
}

# Add issuers
addIssuers() {
    command="kubectl apply -f cert-manager/issuers-v1.4.yaml"
    ./lib/msg.sh "Add issuers: $command"
    # have to wait a bit after install to recognize the new type of resource
    sleep 25
    eval "$command"
}

# Create backup of the current issuer, clusterIssuer and cert configuration into /lib/tmp/cert-manager-backup.yaml
backupCertManagerConfig() {
    command="kubectl get --all-namespaces -o yaml issuer,clusterissuer,cert > lib/tmp/cert-manager-backup.yaml"
    ./lib/msg.sh "Creating a backup of the current issuer, clusterIssuer and cert: $command"
    eval "$command"

    lib/yq_linux_amd64 'e' 'del(.items[].metadata.creationTimestamp)' 'lib/tmp/cert-manager-backup.yaml' '--inplace'
    lib/yq_linux_amd64 'e' 'del(.items[].metadata.resourceVersion)' 'lib/tmp/cert-manager-backup.yaml' '--inplace'
    lib/yq_linux_amd64 'e' 'del(.items[].metadata.selfLink)' 'lib/tmp/cert-manager-backup.yaml' '--inplace'
    lib/yq_linux_amd64 'e' 'del(.items[].metadata.managedFields)' 'lib/tmp/cert-manager-backup.yaml' '--inplace'
    lib/yq_linux_amd64 'e' 'del(.items[].metadata.uid)' 'lib/tmp/cert-manager-backup.yaml' '--inplace'
    lib/yq_linux_amd64 'e' 'del(.items[].status)' 'lib/tmp/cert-manager-backup.yaml' '--inplace'
}

# Roll back the previous issuer, clusterIssuer and cert configuration
rollbackCertManagerConfig() {
    command="kubectl delete -f cert-manager/issuers-v1.4.yaml"
    ./lib/msg.sh "Deleting currently installed issuers: $command"
    eval "$command"

    command="kubectl delete -f cert-manager/cert-manager-remove-v1.4.yaml"
    ./lib/msg.sh "Deleting currently installed cert-manager: $command"
    eval "$command"

    command="kubectl apply -f cert-manager/cert-manager-v0.10.yaml"
    ./lib/msg.sh "Installing previous version of cert manager: $command"
    eval "$command"

    command="kubectl apply -f cert-manager/issuers-v0.10.yaml"
    # have to wait a bit after install to recognize the new type of resource
    sleep 15
    ./lib/msg.sh "Adding old issuers: $command"
    eval "$command"

    command="kubectl apply -f lib/tmp/cert-manager-backup.yaml"
    ./lib/msg.sh "Restoring saved resources: $command"
    eval "$command"
}

main() {
    parseParams "$@"

    upDowngrade=""
    [[ $isRollback == true ]] && upDowngrade="downgrade" || upDowngrade="upgrade"

    ./lib/msg.sh ""
    ./lib/msg.sh "-------- Cert-manager $upDowngrade ---------"
    ./lib/msg.sh "This step ${upDowngrade}s the cert-manager using the official manifest provided in the installation guide."
    ./lib/msg.sh "It also adds an additional CA ClusterIssuer called ca-issuer, which can be used for automatic key management."
    ./lib/msg.sh "If you have any custom modifications, running this step will overwrite them. "
    ./lib/msg.sh "If you have custom modifications that you want to keep, follow the official guide instead: https://cert-manager.io/docs/installation/upgrading/"

    read -p "$upDowngrade cert-manager? [y/n] " -n 1 -r
    printf '\n'
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        return
    fi

    runChecks
    if [[ $isRollback == true ]]; then
        dieIfWrongVersionDowngrade
        rollbackCertManagerConfig
        ./lib/msg.sh "Rolled back cert-manager upgrade."
        return
    fi
    dieIfWrongVersionUpgrade
    if [[ $shouldDeletePreviousManager == true ]]; then
        backupCertManagerConfig
        removeCurrentInstallation
    fi
    certManagerInstall
    if [[ $shouldDeletePreviousManager == true ]]; then
        addIssuers
    fi
    ./lib/msg.sh "Cert-manager upgrade completed."
}

main "$@"
