/*
    ! Jenkins Watchdog Pipeline

    Этот Pipeline реализует сборку на Kubernetes worker-ноде с контролем и завершением со стороны Jenkins master-ноды.
    Master отслеживает статус worker'а в реальном времени и завершает процесс в случае ошибки, таймаута или успешного завершения.
*/

// Статусы воркера. Необходимо мастеру для определения состояния джоба
def workerFailed  = false
def workerSuccess = false
def DEBUGGING_MODE = false
// Мапа для запуска мастера и воркера параллельно
def parallelSteps = [:]

// Блок констант
// Мапа, в которой будут переменные из vars/build.yaml
buildVars = [:]
buildVars.system = [:]
buildVars.kubeconfig = "config.kube"

// Подгрузка либы. Версия в свойствах папки Jenkins
library "jlib"
cmdbVars = constants.cmdbVars

TimeoutTime = [
    time: binding.hasVariable('timeoutTime') ? timeoutTime : (cmdbVars?.system?.timeoutTime ?: 20),
    unit: binding.hasVariable('timeoutUnit') ? timeoutUnit : (cmdbVars?.system?.timeoutUnit ?: "MINUTES")
]

parallelSteps["worker"] = {
    node("docker") {
        timestamps() {
        ansiColor("xterm"){
        dir (env.BUILD_NUMBER) {
            try {
                stage ("Prepare environments") {
                    // Выкачивание проекта из Git
                    checkout scm
                    // Определение типа джобы:
                    // - сборка и пуш в кубер: DEV / PR
                    // - проверка образа (из DEV) и пуш в кубер: TEST / PROD
                    logger.info("Targeting stand for deploy")
                    // Если PR - в DEV
                    if (env.CHANGE_ID)
                        buildVars.gitBranch = "dev"
                    else
                        buildVars.gitBranch = env.BRANCH_NAME

                    // Проверка на нужные бренчи
                    if (!(buildVars.gitBranch in ["prod", "test", "dev"])) {
                        logger.err("Ветка в Git не соответствует 'prod', 'test' или 'dev'", true)
                    }

                    envConfig = "build/vars/build.yaml"
                    support.getVarsAll(envConfig, buildVars)

                    logger.info("Get kubeconfig")
                    try {
                        withCredentials([vaultString(credentialsId: "${buildVars.vaultPath}", variable: "pem")]) {
                            runsh """
                                set +x
                                echo "${pem}" | base64 -d > ${ buildVars.kubeconfig }
                                set -x
                            """, true
                        }
                        logger.info("kubeconfig getting successfully")
                    } catch (Exception kubeconfigGetError) {
                        logger.err("Не удалось получить кубконфиг для подключению к Kubernetes-кластеру. Ошибка: ${kubeconfigGetError.message}", true)
                    }

                    logger.info("Getting version...")
                    try {
                        version = runsh buildVars.getCommandVersion
                        logger.info("Version app: ${ version }")
                    } catch (Exception versionGetError) {
                        logger.err("Не удалось получить версию приложения. Ошибка: ${versionGetError.message}", true)
                    }
                }

                // Получение токена для авторизации в GITEA
                withCredentials([vaultString(credentialsId: "gitea.services.ngcloud.ru-deck-token", variable: "token")]) { GITEA_AUTH_TOKEN = token }

                // Сборка Image (Только DEV)
                if (buildVars.gitBranch == "dev") {
                    if (buildVars.needBuildLib.toBoolean()) {
                        stage ("Check library") {
                            headers = [ [ name: "Authorization", value: "Bearer ${ GITEA_AUTH_TOKEN }" ] ]
                            // Проверка что либа существует
                            try {
                                retry(3) {
                                    httpcall.get( url="https://gitea.services.ngcloud.ru/api/v1/packages/deck/npm/%40nubes%2F${ buildVars.app.name }/${ version }", headerList=headers)
                                }
                                needToBuildLib = false
                            } catch (Exception errGetPackage) {
                                logger.info("Package does not exist. It's OK")
                                needToBuildLib = true
                            }
                        }
                        stage ("Build library") {
                            if (needToBuildLib.toBoolean()) {
                                retry(3) {
                                    try {
                                        docker.build("${ buildVars.registryImageName }:${ version }", "--no-cache --build-arg GITEA_AUTH_TOKEN=${ GITEA_AUTH_TOKEN } -f ./build/Dockerfile_build_lib .")
                                    } catch (Exception errorDocker) {
                                        logger.err("Не удалось собрать библиотеку. Ошибка: ${ errorDocker.message }", true)
                                    }
                                }
                            }
                        }
                    }
                    stage ("Build image") {
                        catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
                            logger.info("Start building image ${ buildVars.registryContainerName }:${ version }")
                            retry(3) {
                                try {
                                    app = docker.build("${ buildVars.registryImageName }:${ version }", "--no-cache --build-arg GITEA_AUTH_TOKEN=${ GITEA_AUTH_TOKEN } -f ./build/Dockerfile .")
                                } catch (Exception errorDocker) {
                                    logger.err("Не удалось собрать основной образ. Ошибка: ${ errorDocker.message }", true)
                                }
                            }
                        }

                        logger.info("Start uploading image ${ buildVars.registryImageName }:${ version }")
                        docker.withRegistry("https://${ buildVars.registryUrl }/${ buildVars.repoPath }", "registry-jenkins-cred") {
                            // retry на случай если 500 случайно отдаст registry
                            retry(3) {
                                try {
                                    app.push("${ version }")
                                } catch (Exception errorDocker) {
                                    logger.err("Не удалось запушить образ в Registry. Ошибка: ${ errorDocker.message }", true)
                                }
                            }
                        }
                    }
                }
                // Проверка Image в Registry (Всё кроме DEV)
                if (buildVars.gitBranch != "dev") {
                    stage("Check if image exist") {
                        docker.withRegistry("https://${ buildVars.registryUrl }/${ buildVars.repoPath }", "registry-jenkins-cred") {
                            def img = docker.image("${ buildVars.registryImageName }:${ version }")
                            // TODO переписать либу docker под себя
                            // try/catch не поможет, ошибка выведет всегда 'script returned exit code 1'
                            // retry на случай если 500 случайно отдаст registry
                            retry(3) {
                                try {
                                    img.pull()
                                } catch (Exception errorDocker) {
                                    logger.err("Не удалось получить образ из Registry. Ошибка: ${ errorDocker.message }", true)
                                }
                            }
                        }
                    }
                }
                // Деплой в Kubernetes (все стенды)
                stage("Deploy to K8s test") {
                    logger.info("Check exist pods in k8s...")
                    retry(3) {
                        runsh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } get po"
                    }

                    logger.info("Prepare list of args on helm...")
                    commandArgs = [
                        "--set deployment.image.repository=${ buildVars.registryUrl }/${ buildVars.registryImageName }",
                        "--set deployment.readinessProbe.httpGet.path=${ buildVars.app.health }",
                        "--set deployment.containerPort=${ buildVars.app.port }",
                        "--set deployment.resources.requests.cpu=${ buildVars.app.cpu }m",
                        "--set deployment.resources.requests.memory=${ buildVars.app.mem }Mi",
                        "--set deployment.resources.limits.cpu=${ buildVars.app.cpu }m",
                        "--set deployment.resources.limits.memory=${ buildVars.app.mem }Mi",
                        "--set version=${ version }",

                        "--set deployment.image.tag=${ version }",
                        "--set stand=${ buildVars.gitBranch }"
                    ]
                    // Если включен Vault
                    if (buildVars.needStoreVaultSecret.toBoolean()) {
                        commandArgs.add("--set vault.secretStore=${ buildVars.namespace }")
                        commandArgs.add("--set serviceAccountRole=${ buildVars.namespace }-sa")
                        commandArgs.add("--set vault.secrets=\"{${ buildVars.storedSecretsList.join(',') }}\"")
                        commandArgs.add("--set vault.enable=true")
                    } else {
                        commandArgs.add("--set vault.enable=false")
                    }

                    installArgs = [
                        "--kubeconfig ${ buildVars.kubeconfig }",
                        "--install",
                        "--namespace ${ buildVars.namespace }",
                    ]

                    cmdTemplate = "helm template ${commandArgs.join(' ')} ./build/helm/"
                    cmd         = "helm upgrade ${ buildVars.app.name } ${installArgs.join(' ')} ${commandArgs.join(' ')} ./build/helm/"
                    logger.info("""Command for push...
                    ${cmd}
                    """)

                    logger.info("Template helm-release...")
                    runsh cmdTemplate

                    logger.info("Push helm-release...")
                    retry(3) {
                        // От того, что Helm-чарт будет ждать, мало что изменится
                        runsh cmd
                    }

                    logger.info("Check exist pods in k8s...")
                    retry(3) {
                        runsh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } get po"
                    }

                    // Нужны проверки после установки
                    buildVars.application = [:]
                    buildVars.application.typeOfProject = "deployments.apps"
                    buildVars.application.namespace     = buildVars.namespace

                    // sleep ибо загрузка манифестов в K8S занимает какое-то время
                    sleep(5)
                    try {
                        k8sStagesV2.correctReplicaActive(cmdbVars=buildVars, applicationName=buildVars.app.name)
                        // Чтобы не оставлять мусора за собой в эвентах
                        sh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } delete events --all"
                    } catch (Exception errorRun) {
                        logger.info("Не удалось произвести проверку работы микросервиса. Дебаг:")
                        runsh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } events | grep ${ buildVars.app.name }"
                        runsh "kubectl --kubeconfig=${ buildVars.kubeconfig } --namespace ${ buildVars.namespace } logs -l 'app=${ buildVars.app.name }'"
                        error("Не удалось произвести проверку работы микросервиса. Детали в логах pipeline")
                    }
                }

                currentBuild.result = 'SUCCESS'
            }
            catch (Exception err) {
                logger.err("Произошла ошибка")
                logger.err(err.message, true)
                currentBuild.description = err.message
                currentBuild.result      = "FAILURE"
            }
            finally {
                if (currentBuild.result == 'FAILURE') {
                    echo "Worker stage действительно упал — ставим флаг workerFailed"
                    workerFailed = true
                } else if (currentBuild.result == 'SUCCESS') {
                    echo "Worker stage завершился успешно - ставим флаг workerSuccess"
                    workerSuccess = true
                } else {
                    echo "Неопознаный currentBuild.result: ${currentBuild.result} — ставим флаг workerFailed"
                    workerFailed = true
                }
                runsh "rm -rf ${env.BUILD_NUMBER}"
            }
        }}}
    }
}

parallelSteps["master"] = {
    node("master") {
        try {
            stage ("Master: start watchdog") {
                timeout(time: TimeoutTime.time, unit: TimeoutTime.unit) {
                    while (true) {
                        // Следим за окончанием сборки
                        if (DEBUGGING_MODE) {
                            sleep 1
                            // Только в режиме отладки используем echo
                            echo "DEBUG: workerFailed  = ${workerFailed}"
                            echo "DEBUG: workerSuccess = ${workerSuccess}"
                        }
                        // Проверка флагов без использования шагов Jenkins
                        if (workerFailed) {
                            echo "Watchdog: обнаружен сбой в воркере"
                            break
                        } else if (workerSuccess) {
                            echo "Watchdog: воркер завершил работу в Jenkins"
                            break
                        }
                    }
                }
            }
            runsh "rm -rf ${env.BUILD_NUMBER}"
        }
        catch (Exception masterErrorTimeout) {
            logger.err("Ошибка по таймауту")
        }
    }
}

parallel parallelSteps