Jenkins Slack Notification


서론

이 장은 Jenkins에서 빌드가 완료된 후 Slack에 빌드가 완료되었다는 메시지를 보낼 수 있도록 연동하는 작업에 대해서 설명한다.

Step 1. Slack의 Channel에 Jenkins를 추가

메시지를 받고싶은 채널에 젠킨스를 추가한다.

3단계에 있는 팀 하위 도메인, 통합 토큰 자격 증명 ID를 따로 적어둔다.

  • 팀하위 도메인은 Jenkins의 시스템 설정에서 Workspace란에 넣어준다.
  • 통합 토큰 자격증명 ID는 Credential을 생성할 때 필요하다.

스크롤을 내리면 내가 추가했던 채널 이름이 뜬다. 확인만 해두자.

Step 2. Jenkins에 Slack Plug-in 설치

Jenkins에서 Slack Plug-in을 설치한다.

또는 아래 명령어로 설치 가능하다

java -jar jenkins-cli.jar -s http://127.0.0.1:8080 -auth admin:<Toekn> install-plugin Slack Notification

Slack채널 ID 확인하기

  • 채널ID는 슬랙에서 알림을 보내고자하는 채널아이디를 설정하면 된다
  • Credential은 add버튼을 눌러 알맞게 작성해주고 add버튼을 눌러준다.

Step 3. Pipeline에 알림 추가하기

맨 아래 빌드가 끝난 후 동작할 작업을 추가해주면 된다.


post {
        always {
            slackSend (channel: 'D05TNBPJRHP', color: 'good', message: "Build STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        success {
            slackSend (channel: 'D05TNBPJRHP', color: 'good', message: "Build SUCCESS: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        failure {
            slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILURE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        aborted {
            slackSend (channel: 'D05TNBPJRHP', color: 'warning', message: "Build ABORTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        notBuilt {
            slackSend (channel: 'D05TNBPJRHP', color: 'warning', message: "Build NOT BUILT: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        unstable {
            slackSend (channel: 'D05TNBPJRHP', color: 'warning', message: "Build UNSTABLE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        fixed {
            slackSend (channel: 'D05TNBPJRHP', color: 'good', message: "Build BACK TO NORMAL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }

빌드 상태 설명

  1. SUCCESS: 빌드가 성공적으로 완료되었음을 의미. 모든 단계가 정상적으로 실행되고 종료되었다.
  2. FAILURE: 빌드가 실패했음을 의미. 하나 이상의 단계에서 오류가 발생하여 빌드가 중단되었다.
  3. ABORTED: 빌드가 중단되었음을 의미. 사용자가 빌드를 강제로 중단했거나 시스템에 의해 빌드가 중단되었다.
    • e.g.) 사용자가 수동으로 빌드를 중단했거나 Jenkins가 시스템 자원의 문제로 빌드를 중단했을 때 발생
  4. NOT_BUILT: 빌드가 수행되지 않았음을 의미. 예를 들어, 조건에 따라 특정 빌드가 실행되지 않는 경우가 있다.
    • e.g.) 조건에 따라 특정 빌드를 실행하지 않았을 때 발생. 예를 들어, 특정 분기에 대한 빌드만 실행하도록 설정된 경우
  5. UNSTABLE: 빌드가 완료되었지만 일부 경고나 테스트 실패가 있었음을 의미한다. 이는 코드가 성공적으로 빌드되었으나 모든 테스트가 통과되지 않았거나 경고가 발생했을 때 나타난다.
    • e.g.) 빌드가 성공적으로 완료되었으나, Unit Test에서 일부 실패가 발생한 경우입니다. 즉, 컴파일 및 패키징은 성공했지만 테스트 단계에서 일부 문제가 발견되었 경우.
  6. BACK TO NORMAL: 이전 빌드가 실패하거나 불안정했지만, 현재 빌드가 성공적으로 완료되어 정상 상태로 돌아왔음을 의미한다.
    • e.g.) 이전 빌드가 실패하거나 불안정한 상태였으나 또는 실패, 현재 빌드가 성공적으로 완료되어 상태가 정상으로 돌아온 경우

Step 4. 결과 확인

정상적으로 메시지가 온다.

그러나 보통 빌드는 시작할 때 와야한다고 생각하기 때문에 파이프라인 스크립트를 빌드 시작시에 먼저 알람이오고 그 후에 대한 결과를 슬랙으로 전송하도록 조정하겠다.

ETC.

post부분의 always 딕셔너리 부분을 제거하고 아래처럼 스테이지 맨 앞에 넣어준다.

최종 스크립트
pipeline {
    agent any
    environment {
        SERVER_IP = "192.168.1.8"
        SERVER_USER = "groupai"
        REPO_URL = 'ssh://git@192.168.1.22/git/hmlf/ai_client.git'
        COMMON_LIB_REPO = 'ssh://git@192.168.1.22/git/hmlf/ai_fw_utility.git'
        AUTHENTICATION_REPO = 'ssh://git@192.168.1.22/git/hmlf/ai_sw.git'
        STARTKIT_REPO = 'ssh://git@192.168.1.22/git/hmlf/ai_starterkit.git'
    }
    stages {
        stage('Notify Build Start') {
            steps {
                script {
                    slackSend (channel: 'D05TNBPJRHP', color: 'good', message: "Build STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                }
            }
        }
        // stage('Checkout') {
        //     steps {
        //         checkout scm: [ 
        //             $class: 'GitSCM', 
        //             branches: [[name: '*/master']],
        //             userRemoteConfigs: [[credentialsId: 'service_to_git_key', url: REPO_URL]]
        //         ]
        //     }
        // }
        stage('Prepare Environment') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"

                        if [ ! -d "\${WORKSPACE_DIR}" ]; then
                            echo "Directory does not exist. Creating workspace directory..."
                            mkdir -p \${WORKSPACE_DIR}
                            echo "\${WORKSPACE_DIR} successfully created"
                        fi

                        if [ ! -d "\${VENV_DIR}" ]; then
                            echo "Creating virtual environment..."
                            /usr/bin/python3.11 -m venv --system-site-packages \${VENV_DIR}
                        fi
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Install Project') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        PROJECT_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}"
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"

                        source \${VENV_DIR}/bin/activate
                        pip3 install setuptools wheel

                        if [ ! -d "\${PROJECT_DIR}" ]; then
                            echo "Project directory does not exist. Cloning repository..."
                            cd \${WORKSPACE_DIR}
                            git clone ${REPO_URL}
                            cd \${PROJECT_DIR}
                            pip3 install ./
                        else
                            echo "Project directory exists. Pulling latest changes..."
                            cd \${PROJECT_DIR}
                            git pull
                            pip3 uninstall \${REPO_NAME} -y || true
                            pip3 install ./
                        fi
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Install Dependency') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"
                        
                        source \${VENV_DIR}/bin/activate
                        pip3 install git+${COMMON_LIB_REPO}
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Install Authentication') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"
                        
                        source \${VENV_DIR}/bin/activate
                        pip3 install git+${AUTHENTICATION_REPO}
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Unit Test') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"
                        PROJECT_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}"
                        source \${VENV_DIR}/bin/activate
                        cd \${PROJECT_DIR}
                        echo "Pytest"
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Clone StartKit') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        STARTERKIT_REPO_NAME=\$(basename -s .git ${STARTKIT_REPO})
                        STARTERKIT_DIR="\${WORKSPACE_DIR}/\${STARTERKIT_REPO_NAME}"

                        if [ ! -d "\${STARTERKIT_DIR}" ]; then
                            echo "Starter Kit does not exist. Cloning repository ..."
                            cd \${WORKSPACE_DIR}
                            git clone ${STARTKIT_REPO}
                        else
                            echo "Starter Kit exists. Pulling latest changes..."
                            cd \${STARTERKIT_DIR}
                            git pull
                        fi
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Install Nuitka') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"

                        source \${VENV_DIR}/bin/activate
                        pip3 install nuitka==2.3.2
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        // stage('Run Nuitka Script') {
        //     steps {
        //         script {
        //             sshagent(['jenkins_to_service_key']) {
        //                 sh """
        //                 ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
        //                 set -e
        //                 HOME_DIR=\$(echo ~${SERVER_USER})
        //                 WORKSPACE_DIR="\${HOME_DIR}/workspace"
        //                 REPO_NAME=\$(basename -s .git ${REPO_URL})
        //                 VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"
        //                 PROJECT_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}"
        //                 STARTERKIT_REPO_NAME=\$(basename -s .git ${STARTKIT_REPO})

        //                 source \${VENV_DIR}/bin/activate
                        
        //                 NUITKA_SCRIPT="\${WORKSPACE_DIR}/\${STARTERKIT_REPO_NAME}/packaging/nuitka/\${REPO_NAME}.sh"
                        
        //                 cd \${PROJECT_DIR}
        //                 bash \${NUITKA_SCRIPT}

        //                 echo "\${REPO_NAME} Packing Finished!"
        //                 deactivate
        //                 '
        //                 """
        //             }
        //         }
        //     }
        //     post {
        //         failure {
        //             script {
        //                 slackSend (channel: 'D05TNBPJRHP', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        //             }
        //         }
        //     }
        // }
    }
    post {
        success {
            slackSend (channel: 'D05TNBPJRHP', color: 'good', message: "Build SUCCESS: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        fixed {
            slackSend (channel: 'D05TNBPJRHP', color: 'good', message: "Build BACK TO NORMAL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }
}

WorkFlow

전체 워크 플로우이다.

Pipeline

위 워크플로우의 pipeline 상세 흐름도이다.

  • Prepare Environment
    1. Workspace Directory가 있는지 확인하고 없으면 생성
    2. Workspace/VENV 가 있는 경우 스킵, 없으면 3.11 버전의 파이썬 가상환경 생성
  • Install Project
    1. 가상환경을 Activate
    2. 이미 git clone을 한 프로젝트 디렉토리가 있는 경우 git pull, 없는경우 git clone.
    3. 프로젝트 디렉토리로 들어가 설치한다.
  • Install Dependency
    1. 가상환경을 Activate
    2. pip3로 ai_fw_utility를 설치
  • Install Authentication
    1. pip3로 인증관련 ai_sw를 설치
  • Starter Kit
    1. Startker Kit이 설치되어 있지 않으면 git clone, 해당 디렉토리가 있으면 git pull
  • Install Nuitka
    1. Starterkit에서 스크립트 경로를 프로젝트 디렉토리에서 패키징 시작
  • Notify Build Finish
    1. 빌드 결과를 Slack으로 전송

실패는 일부러 만들어서 테스트해봤으며 결과 메시지는 아래 처럼 된다.

FileUpload

만약 파일을 전송하려 한다면 위에서 설치한 방법과는 다른식으로 진행해야 한다.

아래 앱추가 → 앱 디렉터리를 누른다.

그럼 아래처럼 홈페이지가 뜬다. 구축을 누르자.

YAML을 선택하고 아래 내용으로 채워주자.

display_information:
  name: Jenkins
features:
  bot_user:
    display_name: Jenkins
    always_online: true
oauth_config:
  scopes:
    bot:
      - channels:read
      - chat:write
      - chat:write.customize
      - files:write
      - reactions:write
      - users:read
      - users:read.email
      - groups:read
settings:
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false

[reference]

https://github.com/jenkinsci/slack-plugin/blob/master/README.md

아래 토큰은 Jenkins의 Credential을 등록할 때 써야한다. 복사해두자.

먼저 생성한 채널에 봇을 초대해줘야 한다. 아래 명령어로 봇을 초대해준다.

아래 처럼작성해주자.

마찬가지로 +add 버튼을누른다. 저 채널아이디는 맴버에게는 보낼 수 없는거 같고 슬랙의 채널아이디를 넣어줘야한다.

아래 처럼 버튼을 눌러 테스트 해보자. success 라 뜨면 된다.

스크립트

주의 해야할점은 slackUploadFile에 스크립트에서 filePath가 있는데 env.WORKSPACE를 제외한 파일 이름을 넣어줘야 한다.

파일업로드 최종 스크립트.

최종 스크립트
pipeline {
    agent any
    environment {
        SERVER_IP = "192.168.1.8"
        SERVER_USER = "groupai"
        REPO_URL = 'ssh://git@192.168.1.22/git/hmlf/ai_client.git'
        COMMON_LIB_REPO = 'ssh://git@192.168.1.22/git/hmlf/ai_fw_utility.git'
        AUTHENTICATION_REPO = 'ssh://git@192.168.1.22/git/hmlf/ai_sw.git'
        STARTKIT_REPO = 'ssh://git@192.168.1.22/git/hmlf/ai_starterkit.git'
    }
    stages {
        stage('Notify Build Start') {
            steps {
                script {
                    slackSend (channel: 'C078T1XJE57', color: 'good', message: "Build STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                }
            }
        }
        // stage('Checkout') {
        //     steps {
        //         checkout scm: [ 
        //             $class: 'GitSCM', 
        //             branches: [[name: '*/master']],
        //             userRemoteConfigs: [[credentialsId: 'service_to_git_key', url: REPO_URL]]
        //         ]
        //     }
        // }
        stage('Prepare Environment') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"

                        if [ ! -d "\${WORKSPACE_DIR}" ]; then
                            echo "Directory does not exist. Creating workspace directory..."
                            mkdir -p \${WORKSPACE_DIR}
                            echo "\${WORKSPACE_DIR} successfully created"
                        fi

                        if [ ! -d "\${VENV_DIR}" ]; then
                            echo "Creating virtual environment..."
                            /usr/bin/python3.11 -m venv --system-site-packages \${VENV_DIR}
                        fi
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'C078T1XJE57', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Install Project') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        PROJECT_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}"
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"

                        source \${VENV_DIR}/bin/activate
                        pip3 install setuptools wheel

                        if [ ! -d "\${PROJECT_DIR}" ]; then
                            echo "Project directory does not exist. Cloning repository..."
                            cd \${WORKSPACE_DIR}
                            git clone ${REPO_URL}
                            cd \${PROJECT_DIR}
                            pip3 install ./
                        else
                            echo "Project directory exists. Pulling latest changes..."
                            cd \${PROJECT_DIR}
                            git pull
                            pip3 uninstall \${REPO_NAME} -y || true
                            pip3 install ./
                        fi
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'C078T1XJE57', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Install Dependency') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"
                        
                        source \${VENV_DIR}/bin/activate
                        pip3 install git+${COMMON_LIB_REPO}
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'C078T1XJE57', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Install Authentication') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"
                        
                        source \${VENV_DIR}/bin/activate
                        pip3 install git+${AUTHENTICATION_REPO}
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'C078T1XJE57', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Unit Test') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"
                        PROJECT_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}"
                        source \${VENV_DIR}/bin/activate
                        cd \${PROJECT_DIR}
                        pip3 install pytest
                        pip3 install pytest-mock
                        pip3 install pytest-html
                        pytest --html=\${REPO_NAME}_report.html --self-contained-html
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                always {
                    sshagent(['jenkins_to_service_key']) {
                        script {
                            def HOME_DIR = sh(script: "ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} 'echo ~${SERVER_USER}'", returnStdout: true).trim()
                            def WORKSPACE_DIR = "${HOME_DIR}/workspace"
                            def REPO_NAME = sh(script: "basename -s .git ${REPO_URL}", returnStdout: true).trim()
                            def REMOTE_FILE_PATH = "${WORKSPACE_DIR}/${REPO_NAME}/${REPO_NAME}_report.html"
                            def LOCAL_FILE_PATH = "${env.WORKSPACE}/${REPO_NAME}_report.html"

                            echo "HOME_DIR: ${HOME_DIR}"
                            echo "WORKSPACE_DIR: ${WORKSPACE_DIR}"
                            echo "REPO_NAME: ${REPO_NAME}"
                            echo "REMOTE_FILE_PATH: ${REMOTE_FILE_PATH}"
                            echo "LOCAL_FILE_PATH: ${LOCAL_FILE_PATH}"
                            
                            sh "scp -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP}:${REMOTE_FILE_PATH} ${LOCAL_FILE_PATH}"

                            slackUploadFile (channel: 'C078T1XJE57',
                                            filePath: "${REPO_NAME}_report.html",
                                            initialComment: "Unit Test Report for Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                        }
                    }
                }
            }
        }
        stage('Clone StartKit') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        STARTERKIT_REPO_NAME=\$(basename -s .git ${STARTKIT_REPO})
                        STARTERKIT_DIR="\${WORKSPACE_DIR}/\${STARTERKIT_REPO_NAME}"

                        if [ ! -d "\${STARTERKIT_DIR}" ]; then
                            echo "Starter Kit does not exist. Cloning repository ..."
                            cd \${WORKSPACE_DIR}
                            git clone ${STARTKIT_REPO}
                        else
                            echo "Starter Kit exists. Pulling latest changes..."
                            cd \${STARTERKIT_DIR}
                            git pull
                        fi
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'C078T1XJE57', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
        stage('Install Nuitka') {
            steps {
                script {
                    sshagent(['jenkins_to_service_key']) {
                        sh """
                        ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
                        set -e
                        HOME_DIR=\$(echo ~${SERVER_USER})
                        WORKSPACE_DIR="\${HOME_DIR}/workspace"
                        REPO_NAME=\$(basename -s .git ${REPO_URL})
                        VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"

                        source \${VENV_DIR}/bin/activate
                        pip3 install nuitka==2.3.2
                        deactivate
                        '
                        """
                    }
                }
            }
            post {
                failure {
                    script {
                        slackSend (channel: 'C078T1XJE57', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
                    }
                }
            }
        }
    //     stage('Run Nuitka Script') {
    //         steps {
    //             script {
    //                 sshagent(['jenkins_to_service_key']) {
    //                     sh """
    //                     ssh -o StrictHostKeyChecking=no ${SERVER_USER}@${SERVER_IP} '
    //                     set -e
    //                     HOME_DIR=\$(echo ~${SERVER_USER})
    //                     WORKSPACE_DIR="\${HOME_DIR}/workspace"
    //                     REPO_NAME=\$(basename -s .git ${REPO_URL})
    //                     VENV_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}_venv"
    //                     PROJECT_DIR="\${WORKSPACE_DIR}/\${REPO_NAME}"
    //                     STARTERKIT_REPO_NAME=\$(basename -s .git ${STARTKIT_REPO})

    //                     source \${VENV_DIR}/bin/activate
                        
    //                     NUITKA_SCRIPT="\${WORKSPACE_DIR}/\${STARTERKIT_REPO_NAME}/packaging/nuitka/\${REPO_NAME}.sh"
                        
    //                     cd \${PROJECT_DIR}
    //                     bash \${NUITKA_SCRIPT}

    //                     echo "\${REPO_NAME} Packing Finished!"
    //                     deactivate
    //                     '
    //                     """
    //                 }
    //             }
    //         }
    //         post {
    //             failure {
    //                 script {
    //                     slackSend (channel: 'C078T1XJE57', color: 'danger', message: "Build FAILED in stage '${env.STAGE_NAME}': Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
    //                 }
    //             }
    //         }
    //     }
    }
    post {
        success {
            slackSend (channel: 'C078T1XJE57', color: 'good', message: "Build SUCCESS: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        fixed {
            slackSend (channel: 'C078T1XJE57', color: 'good', message: "Build BACK TO NORMAL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }
}