/**
 * Pipeline to build nerve-led-controller Debian package.
 *
 * https://industrial.jenkins.tttech.com/view/NERVE/
 *
 * See also:
 *  https://confluence.tttech.com/display/JENKINS/Pipeline+guides+and+examples
 *  https://confluence.tttech.com/display/INFRA/How+to+Configure+a+Jenkins+Pipeline+job+for+doc-builder+Docker
 */
pipeline {
    agent { label 'nerve && docker' }
    environment {
        /* Reusable variable used throughout this file: */
        PACKAGE = "nerve-led-controller"

        /* builds from this branch will automatically be uploaded to APT */
        DEFAULT_UPLOAD_BRANCH = "integration"

        /**
         * printf version of the tag format described in debian/gbp.conf.
         * Set to "v%s" if gbp.conf says "debian-tag = v%(version)s"
         */
        TAG_FORMAT = "%s"

        /* variable used to determine if the package is already present. */
        PACKAGE_STATUS = 'not-present'
        REGISTRY = '191911464942.dkr.ecr.eu-west-1.amazonaws.com'
        UID = sh(script: "id -u", returnStdout: true).trim()
        GID = sh(script: "id -g", returnStdout: true).trim()
    }
    options {
        // keep last 10 builds
        buildDiscarder(logRotator(
            artifactDaysToKeepStr: '5',
            artifactNumToKeepStr: '10',
            daysToKeepStr: '5',
            numToKeepStr: '10'
        ))

        // we checkout in our own step, because we need tags and specific branches
        skipDefaultCheckout()
        timestamps()
    }
    parameters {
        string(
            name: 'VERSION',
            defaultValue: '',
            description: 'Old version to rebuild (must equal a tag present in GIT). Only used when branch is "master" to rebuild an older version. Leave blank to build the latest version.'
        )
        string(
            name: 'DIST',
            defaultValue: 'buster',
            description: 'Release to build for (buster = development)'
        )
        choice(
            name: 'ARCH',
            choices: 'amd64\ni386',
            description: 'Architecture to build on (for now should always be "amd64")'
        )
        string(
            name: 'APT_COMPONENT',
            defaultValue: 'main',
            description: 'APT component to upload to.'
        )
        booleanParam (
            name : 'UPLOAD',
            defaultValue: false,
            description: 'Upload package to APT repository (always True for integration branch).'
       )
    }
    stages {
        /**
         * Checkout the source code.
         *
         * The code is already checked out by Jenkins, but in a "detached HEAD" state, which will not work
         * with gbp-buildpackage.
         */
        stage ('Git checkout') {
            steps {
                /* see https://jenkins.io/doc/pipeline/steps/git/ */
                script {
                    checkout poll: false,
                    scm: [
                        $class: 'GitSCM',
                        branches: [[name: env.BRANCH_NAME]],
                        extensions: scm.extensions + [
                            [$class: 'LocalBranch'], [$class: 'WipeWorkspace'],

                            // This line makes git also fetch tags
                            [$class: 'CloneOption', noTags: false, shallow: false, depth: 0, reference: '']
                        ],
                        userRemoteConfigs: [[
                            url:  "https://git.tttech.com/scm/nerve/${PACKAGE}.git",
                            branch: env.BRANCH_NAME,
                            credentialsId: '505a28bb-7065-4c02-8a4f-0477dc5275fb',
                        ]]
                    ]
                }
            }
        }
        stage ('Prepare environment') {
            environment {
                /*
                 * The version in debian/changelog.
                 *
                 * NOTE: equivalent to "dpkg-parsechangelog -n 1 -S Version", but we want to be independent
                 *       of debian tools on the host.
                 */
                DCH_VERSION = sh(
                    script: "sed '/^\\s*\$/d' debian/changelog | head -n 1 | sed 's/.*(\\(.*\\)).*/\\1/'",
                    returnStdout: true
                ).trim()

                /* The tag for the version in debian/changelog */
                DCH_TAG = sh(script: "printf ${env.TAG_FORMAT} ${env.DCH_VERSION}", returnStdout: true).trim()

                /* The tag for the version passed as parameter (if any) */
                PARAMS_TAG = sh(script: "printf ${env.TAG_FORMAT} ${params.VERSION}", returnStdout: true).trim()

                /* current timestamp, just a reusable value */
                DEB_TIMESTAMP = sh(script: "date +%Y%m%d%H%M%S", returnStdout: true).trim()
            }
            steps {

                script {
                    /* Get the final version of the debian package */
                    VERSION = sh(
                        returnStdout: true,
                        script: """
                            # If *not* building on master, so we use a version with timestamp
                            if [ "$BRANCH_NAME" != "master" ]; then
                                echo -n ${env.DCH_VERSION}~${env.DEB_TIMESTAMP}

                            # A version was explicitly passed as parameter, so use that version
                            elif [ -n "${params.VERSION}" ]; then

                                # check if the git tag exists
                                if ! git show-ref ${env.PARAMS_TAG} > /dev/null; then
                                    echo 'No git tag "${env.PARAMS_TAG}" found!'
                                    exit 1
                                fi

                                echo -n ${params.VERSION}

                            # If the version in debian/changelog is tagged and the tag points to HEAD,
                            # that implies there have been no changes since the release of that version.
                            # So we use the version as mentioned in debian/changelog
                            elif [ "\$(git tag -l --points-at HEAD | grep -c '^${env.DCH_TAG}\$')" = "1" ]; then
                                echo -n ${env.DCH_VERSION}

                            # The version in debian/changelog is either not tagged or there have been changes
                            # commited since that tag. So we again use the timestamp.
                            else
                                echo -n ${env.DCH_VERSION}~${DEB_TIMESTAMP}
                            fi
                        """
                    ).trim()

                    /* equivalent to: dpkg-parsechangelog -n 1 -S Source | tr -d '\n' */
                    UPSTREAM_NAME = sh(
                        returnStdout: true,
                        script: "sed '/^\\s*\$/d' debian/changelog | head -n 1 | awk '{ print \$1; }'"
                    ).trim()
                    CHANGES_FILENAME = sh(
                        returnStdout: true,
                        script: "echo -n ${UPSTREAM_NAME}_${VERSION}_${params.ARCH}.changes"
                    ).trim()

                    /* true if we build an exact version without a timestamp */
                    BUILD_EXACT_VERSION = VERSION == env.DCH_VERSION || VERSION == params.VERSION

                    /**
                     * The name of the tag we are building *if* BUILD_EXACT_VERSION is true.
                     *
                     * WARNING: This will always have a value, even if we're not building an exact version.
                     */
                    BUILD_TAG = params.VERSION ? params.VERSION : env.DCH_VERSION
                }
                echo "Building package ${UPSTREAM_NAME} with version ${VERSION} (EXACT_VERSION=${BUILD_EXACT_VERSION})"
                sh "mkdir -p build/"
            }
        }

        /**
         * If we are building a released version without a timestamp, the package may already have been
         * uploaded to our APT repository.
         *
         * The remote "check-package" script will look for the package in our APT repositories and copy the
         * package from another distribution if necessary. The result is stored in the 'PACKAGE_STATUS'
         * variable and will be "not-present" if the package cannot be found in APT.
         *
         * This step only runs if we would be uploading to APT *and* we are building a version without a
         * timestamp (since a timestamp implies that the package is always new).
         */
        stage ('Check APT') {
            when {
                allOf {
                    expression {
                        return BUILD_EXACT_VERSION
                    }
                    anyOf {
                        branch DEFAULT_UPLOAD_BRANCH
                        environment name: 'UPLOAD', value: 'true'
                    }
                }
            }
            steps {
                echo "Building a tagged version, so check if package is already built elsewhere..."
                script {
                    PACKAGE_STATUS = withCredentials([sshUserPrivateKey(
                        credentialsId: 'f0b278d1-0874-4b40-886a-c22622342fab',
                        keyFileVariable: 'SSH_KEYFILE',
                        passphraseVariable: '',
                        usernameVariable: 'SSH_USERNAME'
                    )]) {
                        def remote = [:]
                        remote.name = 'infra.svc.nerve.cloud'
                        remote.host = 'infra.svc.nerve.cloud'
                        remote.allowAnyHosts = true
                        remote.user = SSH_USERNAME
                        remote.identityFile = SSH_KEYFILE
                        sshCommand remote: remote, command: "check-package ${UPSTREAM_NAME} ${VERSION} ${params.DIST}"
                    }
                }
                echo "Package is ${PACKAGE_STATUS}."
            }
        }

        /**
         * Finally, actually build the package.
         */
        stage ('Build package') {
            when {
                expression {
                    return PACKAGE_STATUS == 'not-present'
                }
            }
            environment {
                HOME = sh(script: 'mktemp -d -p `pwd`/build/', returnStdout: true).trim()
                GNUPG_SIGN_KEY = credentials('9dddc074-0903-487c-aa20-7cb6d9de403c')
                BUILD_IMAGE = "${REGISTRY}/nervesw/nerve-gbp"
            }
            steps {
                /* login to ECR */
                withCredentials([
                    [$class: 'AmazonWebServicesCredentialsBinding', credentialsId: '3d5f9461-8baa-4584-8388-62ccd4ca5c94']
                ]) {
                    sh "\$(aws ecr get-login --no-include-email --region eu-west-1)"
                }

                sh """
                    # pull to make sure we have the newest version
                    docker pull ${BUILD_IMAGE}:${params.DIST}
                    docker run --rm -v `pwd`:/gbp \
                        -e UID=${UID} -e GID=${GID} \
                        -v ${GNUPG_SIGN_KEY}:/tmp/gpg.key \
                        -e BUILD_EXACT_VERSION=${BUILD_EXACT_VERSION} \
                        -e VERSION=${VERSION} -e DEBIAN_BRANCH=${BRANCH_NAME} \
                        -e BUILD_TAG=${BUILD_TAG} \
                        ${BUILD_IMAGE}:${params.DIST}
                """

                /* Test if the .changes file was created as we expected it. */
                sh "test -f build/${params.DIST}-${params.ARCH}/${CHANGES_FILENAME}"
            }
            post {
                always {
                    /* remove ECR credentials and older test containers... */
                    sh "rm -rf ${HOME}"
                }
            }
        }
        stage ('Upload to Artifactory') {
            when {
                expression {
                    return PACKAGE_STATUS == 'not-present'
                }
            }
            steps {
                rtBuildInfo (maxBuilds: 10, maxDays: 15, doNotDiscardBuilds: ["1"])
                rtUpload (
                    serverId: 'TTTech Artifactory',
                    spec: """{
                      "files": [
                          {
                            "pattern": "build/${params.DIST}-${params.ARCH}/*",
                            "target": "build/nervesw/${PACKAGE}/deb/${BRANCH_NAME}/build$BUILD_NUMBER/"
                          }
                      ]
                    }"""
                )
                rtPublishBuildInfo (serverId: 'TTTech Artifactory')

            }
        }
        stage ('Upload to APT') {
            when {
                allOf {
                    expression {
                        return PACKAGE_STATUS == 'not-present'
                    }
                    anyOf {
                        branch DEFAULT_UPLOAD_BRANCH
                        environment name: 'UPLOAD', value: 'true'
                    }
                }
            }
            steps {
                script {
                    withCredentials([sshUserPrivateKey(
                        credentialsId: 'f0b278d1-0874-4b40-886a-c22622342fab',
                        keyFileVariable: 'SSH_KEYFILE',
                        passphraseVariable: '',
                        usernameVariable: 'SSH_USERNAME'
                    )]) {
                        def remote = [:]
                        remote.name = 'infra.svc.nerve.cloud'
                        remote.host = 'infra.svc.nerve.cloud'
                        remote.allowAnyHosts = true
                        remote.user = SSH_USERNAME
                        remote.identityFile = SSH_KEYFILE
                        sshPut remote: remote, from: "build/${params.DIST}-${params.ARCH}", into: "incoming/"
                        sshCommand remote: remote, command: "process-upload -c ${params.APT_COMPONENT} -d ${params.DIST} incoming/${params.DIST}-${params.ARCH}/${CHANGES_FILENAME}"
                    }
                }
            }
        }
    }
    post {
        always {
            archiveArtifacts artifacts: "build/${params.DIST}-${params.ARCH}/*", fingerprint: true, allowEmptyArchive: true
            cleanWs()
        }
    }
}
