侧边栏壁纸
博主头像
一朵云的博客博主等级

拥抱生活,向阳而生。

  • 累计撰写 107 篇文章
  • 累计创建 28 个标签
  • 累计收到 7 条评论

目 录CONTENT

文章目录

Jenkins -- Pipeline自动化部署Python项目

一朵云
2024-02-24 / 0 评论 / 1 点赞 / 95 阅读 / 27703 字

Jenkins -- 自动化部署Python项目

前言:

​ ​ 本次记录基于 Linux(CentOS)Stream 9 系统,实现 Git+Jenkins 自动拉取并启动 PythonPytest 项目。

一、环境准备:

# Python3 和 pip
sudo dnf install -y python39 python39-pip python39-devel

# Git
sudo dnf install -y git

# 虚拟环境(隔离项目依赖)
sudo pip3 install virtualenv

通过 GitHub 下载 Allure

# 下载 Allure 2.25.0(最新稳定版)
wget https://github.com/allure-framework/allure2/releases/download/2.25.0/allure-2.25.0.zip

# 安装 unzip
sudo dnf install -y unzip

# 解压
unzip allure-2.25.0.zip -d /opt/

# 创建软链接(allure目录,为后续Jenkins中的allure插件使用)
sudo ln -s /opt/allure-2.25.0 /opt/allure

# 测试 allure
/opt/allure/bin/allure --version

核验Jenkins的运行用户是否适配环境:

①、查看jenkins通过啥用户运行的

ps aux | grep java | grep jenkins

image-qkfd.png

②、临时切换jenkins运行用户,并测试依赖权限是否正常

# 测试 Python
sudo -u jenkins python3 --version

# 测试 pip
sudo -u jenkins pip3 --version

# 测试 Git
sudo -u jenkins git --version

image-zexa.png

​ 

二、Jenkins 配置

1、安装/检查插件

  • Git
  • Pipeline(用于写 Jenkinsfile)
  • Workspace Cleanup Plugin(清理工作空间)
  • Allure(用于展示测试报告链接)
  • Email Extension Plugin(邮件插件)

2、进行Allure配置

image-sxva.png

3、创建流水线任务

image-stqb.png

4、添加git连接凭证

①、在linux上生成了公私钥,且将公钥配置到gitee上。

操作可参考该链接:https://help.gitee.com/base/account/SSH%E5%85%AC%E9%92%A5%E8%AE%BE%E7%BD%AE

# 1. 创建 jenkins 的 .ssh 目录(如果不存在)
sudo -u jenkins mkdir -p /var/lib/jenkins/.ssh

# 2. 生成 Ed25519 密钥对
sudo -u jenkins ssh-keygen -t ed25519 -b 4096 -C "729513753@qq.com" -f /var/lib/jenkins/.ssh/id_ed25519 -N ""

# 3. 设置正确的权限(非常重要!)
sudo chmod 700 /var/lib/jenkins/.ssh
sudo chmod 600 /var/lib/jenkins/.ssh/id_ed25519
sudo chmod 644 /var/lib/jenkins/.ssh/id_ed25519.pub
sudo chown -R jenkins:jenkins /var/lib/jenkins/.ssh

# 4. 查看公钥
cat /var/lib/jenkins/.ssh/id_ed25519.pub

将公钥配置到gitee上

image-bgsl.png

②、为jenkins用户信任Gitee

sudo -u jenkins ssh -T git@gitee.com

然后 yes 确认即可。(不基于jenkins这个用户操作这步,是连接不通gitee的!!!)

image-zqvx.png

③、jenkins流水线配置git

image-qqjp.png

新增凭证:

image-ublg.png

5、新增Jenkinsfile脚本

大致结构:

pipeline {
    agent any           // 在任何可用的 Jenkins Agent 上运行
    environment { ... } // 定义环境变量
    stages { ... }      // 分阶段执行任务
    post { ... }        // 后置处理(无论成功/失败都会执行)
}

脚本内容:

pipeline {
    agent any

    environment {
        VENV = 'venv'
        REPORT_DIR    = 'reports'
        RESULTS_DIR   = 'reports/results'
        HTML_DIR      = 'reports/html'
        LOG_DIR       = 'logs'
        CONFIG_DIR    = 'config'
        ALLURE_REPORT_NAME = '我的自动化测试报告'
    }

    stages {
        stage('初始化') {
            steps {
                script {
                    // 在 pipeline 中定义变量,支持 Jenkins 内置变量
                    ALLURE_HISTORY_PATH = "${JENKINS_HOME}/userContent/allure-history/${JOB_NAME}"
                    echo "Allure 历史数据路径: ${ALLURE_HISTORY_PATH}"
                }
            }
        }

        stage('创建虚拟环境') {
            steps {
                sh '''
                    echo "创建 Python 虚拟环境..."
                    python3 -m venv ${VENV}
                    python3 --version
                    echo "虚拟环境创建成功: ${VENV}"
                '''
            }
        }

        stage('安装依赖') {
            steps {
                sh '''
                    echo "激活虚拟环境并升级 pip..."
                    source ${VENV}/bin/activate
                    pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
                    pip cache purge
                    pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
                    echo "依赖安装完成"
                '''
            }
        }

        stage('生成环境变量文件') {
            steps {
                sh '''
                    source ${VENV}/bin/activate
                    python3 ${CONFIG_DIR}/generate_env.py
                    echo "环境变量文件生成完成"
                '''
            }
        }

        stage('准备报告目录') {
            steps {
                sh '''
                    mkdir -p ${RESULTS_DIR} ${HTML_DIR}
                    echo "报告目录创建完成"
                '''
            }
        }

        stage('恢复 Allure 历史数据') {
            steps {
                script {
                    def historyDir = "${ALLURE_HISTORY_PATH}/history"
                    if (sh(script: "test -d '${historyDir}'", returnStatus: true) == 0) {
                        sh """
                            echo "恢复历史数据..."
                            cp -r '${historyDir}' '${RESULTS_DIR}/'
                        """
                    } else {
                        echo "未发现历史数据,本次为首次运行"
                    }
                }
            }
        }

         stage('运行 Pytest 测试') {
                    steps {
                        script {
                            // 直接在这里执行 pytest 并处理退出码
                            def exitCode = sh(
                                script: """
                                    source venv/bin/activate
                                    pytest testcases/test_sdk_api.py --alluredir=reports/results -v
                                """,
                                returnStatus: true  // 关键:返回状态码,而不是抛异常
                            )

                            if (exitCode == 0) {
                                echo "测试全部通过"
                                // 构建继续
                            } else if (exitCode == 1) {
                                echo "有测试用例失败"
                                currentBuild.result = 'UNSTABLE'
                                // 构建继续,标记为黄色
                            } else {
                                echo "pytest 执行异常(exit code: ${exitCode})"
                                error("Pytest 执行失败,可能是命令不存在或环境错误,终止构建")
                            }
                        }
                    }
                }

        stage('持久化 Allure 历史数据') {
            steps {
                sh """
                    echo "持久化历史数据..."
                    mkdir -p '${ALLURE_HISTORY_PATH}'
                    rm -rf '${ALLURE_HISTORY_PATH}/history'
                    if [ -d '${HTML_DIR}/history' ]; then
                        cp -r '${HTML_DIR}/history' '${ALLURE_HISTORY_PATH}/'
                        echo "历史数据已保存至: ${ALLURE_HISTORY_PATH}"
                    else
                        echo "未生成 history 目录"
                    fi
                """
            }
        }

        stage('归档报告与结果') {
            steps {
                archiveArtifacts(
                    artifacts: "${LOG_DIR}/*",
                    fingerprint: true,
                    allowEmptyArchive: true
                )
            }
        }
    }

    post {
        success {
            echo '✅ 构建成功,测试全部通过!'
        }
        unstable {
            echo '⚠️ 构建完成,但有测试失败'
            echo "📊 查看报告: ${BUILD_URL}allure"
        }
        failure {
            echo '❌ 构建失败!可能是脚本错误、依赖问题或 pytest 执行异常'
            echo "📎 检查日志: ${BUILD_URL}console"
        }
        always {
            echo "构建状态: ${currentBuild.result}"

            // ✅ 使用 Allure 插件发布报告(核心)
            allure([
                includeProperties: false,
                jdk: '',
                properties: [],
                reportBuildPolicy: 'ALWAYS',  // 无论成功失败都生成报告
                results: [[path: 'reports/results']]  // 必须和 --alluredir 一致
            ])
        }
    }
}

6、添加Clean before checkout

image-pofq.png

​ 

三、查看报告

image-iugc.png

离线看allure报告

下载解压 allure-report.zip 后进入目录,在cmd执行下列指令

allure open .

​ 

四、添加邮件通知

1、为系统管理员添加邮箱,不配置会发送邮件失败!

image-ftis.png

2、去QQ邮箱等邮箱设置中生成对应信息填入。

image-mouo.png

3、优化Jenkinsfile脚本

    post {
        success {
            echo '✅ 构建成功,测试全部通过!'

            emailext(
                subject: "✅ 构建成功: ${JOB_NAME} #${BUILD_NUMBER}",
                body: """
                    <p>项目: <strong>${JOB_NAME}</strong></p>
                    <p>构建号: ${BUILD_NUMBER}</p>
                    <p>状态: <span style='color:green;'>SUCCESS</span></p>
                    <p>分支: ${env.BRANCH_NAME}</p>
                    <p>构建耗时: ${currentBuild.durationString}</p>
                    <p>触发人: ${env.CHANGE_AUTHOR ?: 'System'}</p>
                    <p>构建日志: <a href='${BUILD_URL}'>点击查看</a></p>
                    <p><img src='${BUILD_URL}buildStatus/icon' /> Jenkins 构建状态图标</p>
                """,
                recipientProviders: [
                    [$class: 'DevelopersRecipientProvider'],  // 提交代码的人
                    [$class: 'RequesterRecipientProvider']     // 触发构建的人
                ],
                to: 'xu.chunjie@foxmail.com',
                mimeType: 'text/html'
            )
        }
        unstable {
            echo '⚠️ 构建完成,但有测试失败'
            echo "📊 查看报告: ${BUILD_URL}allure"

            emailext(
                subject: "⚠️ 构建不稳定: ${JOB_NAME} #${BUILD_NUMBER}",
                body: """
                    <p>项目: ${JOB_NAME}</p>
                    <p>状态: UNSTABLE(测试失败)</p>
                    <p>请检查测试报告。</p>
                    <p><a href='${BUILD_URL}'>构建地址</a></p>
                """,
                to: 'xu.chunjie@foxmail.com',
                mimeType: 'text/html'
            )
        }
        failure {
            echo '❌ 构建失败!可能是脚本错误、依赖问题或 pytest 执行异常'
            echo "📎 检查日志: ${BUILD_URL}console"

            emailext(
                subject: "❌ 构建失败: ${JOB_NAME} #${BUILD_NUMBER}",
                body: """
                    <p>项目: <strong>${JOB_NAME}</strong></p>
                    <p>构建号: ${BUILD_NUMBER}</p>
                    <p>状态: <span style='color:red;'>FAILED</span></p>
                    <p>分支: ${env.BRANCH_NAME}</p>
                    <p>触发人: ${env.CHANGE_AUTHOR ?: 'Unknown'}</p>
                    <p>错误日志:</p>
                    <pre style='background:#f4f4f4; padding:10px; border:1px solid #ccc;'>
                        ${currentBuild.rawBuild.getLog(100).join('\n')}
                    </pre>
                    <p>构建日志: <a href='${BUILD_URL}console'>点击查看完整日志</a></p>
                    <p>请尽快修复!</p>
                """,
                recipientProviders: [
                    [$class: 'DevelopersRecipientProvider'],
                    [$class: 'CulpritsRecipientProvider']  // 最近提交导致失败的人
                ],
                to: 'xu.chunjie@foxmail.com',
                mimeType: 'text/html'
            )
        }
        always {
            echo "构建状态: ${currentBuild.result}"

            // ✅ 使用 Allure 插件发布报告(核心)
            allure([
                includeProperties: false,
                jdk: '',
                properties: [],
                reportBuildPolicy: 'ALWAYS',  // 无论成功失败都生成报告
                results: [[path: 'reports/results']]  // 必须和 --alluredir 一致
            ])

        }
    }

​ 

五、企业邮箱通知

使用 Qy Wechat Notification 插件,在 Jenkins Pipeline 构建成功或失败时,自动发送消息到企业微信群。

1、安装Jenkins插件:WXWork Notification

image-sarr.png

2、获取企业微信机器人 Webhook URL

  • 在企业微信中打开一个群聊
  • 点击右上角「…」→ 群机器人 → 添加机器人 → 选择「自定义机器人」
  • 保存生成的 Webhook URL,格式如下:
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

3、添加一个新的机器人配置

这个 ID 名称将在 Jenkinsfile 中使用。

image-slas.png

4、优化Jenkinsfile脚本

在always中添加markdown语法通知。

        always {
            script {
                // 定义消息标题和状态图标
                def statusTitle = "构建结果: ${currentBuild.currentResult}"
                def statusEmoji = currentBuild.currentResult == 'SUCCESS' ? '✅' : '❌'

                // 构建 Markdown 消息内容数组
                def markdownText = [
                    "# ${statusTitle} ${statusEmoji}", // 一级标题,包含结果和图标
                    "> ## 项目信息", // 二级标题
                    "- **项目名称**: `${env.JOB_NAME}`", // 项目全名
                    "- **构建编号**: `${currentBuild.displayName}`", // 构建号,如 #15
                    "- **触发原因**: ${currentBuild.getBuildCauses().collect{it.shortDescription}.join(', ')}", // 触发原因
                    "> ", // 空行
                    "## 构建详情", // 二级标题
                    "- **开始时间**: ${new Date(currentBuild.startTimeInMillis).format('yyyy-MM-dd HH:mm:ss')}", // 格式化时间
                    "- **持续时间**: ${currentBuild.durationString}", // 如 "1 min 20 sec"
                    "- **构建日志**: [点击查看完整日志](${currentBuild.absoluteUrl}console)", // 超链接到日志
                    "> ", // 空行
                    "**请及时关注构建状态!**" // 强调文字
                ]

                // 发送 Markdown 消息
                wxwork(
                    robot: 'Jenkins-CI-Bot', // 替换成你在Jenkins系统配置中的机器人ID
                    type: 'markdown', // 指定消息类型为 markdown
                    text: markdownText // 传入构建好的Markdown内容数组
                )
            }
        }

​ 

六、最终完整的Jenkinsfile

再补充 --junitxml=report.xml + junit 'report.xml',可以在Jenkin上展示出趋势图。

pipeline {
    agent any

    environment {
        VENV = 'venv'
        REPORT_DIR    = 'reports'
        RESULTS_DIR   = 'reports/results'
        HTML_DIR      = 'reports/html'
        LOG_DIR       = 'logs'
        CONFIG_DIR    = 'config'
        ALLURE_REPORT_NAME = '我的自动化测试报告'
        REPORTFILE = 'report.xml'
    }

    stages {
        stage('初始化') {
            steps {
                script {
                    // 在 pipeline 中定义变量,支持 Jenkins 内置变量
                    ALLURE_HISTORY_PATH = "${JENKINS_HOME}/userContent/allure-history/${JOB_NAME}"
                    echo "Allure 历史数据路径: ${ALLURE_HISTORY_PATH}"
                }
            }
        }

        stage('创建虚拟环境') {
            steps {
                sh '''
                    echo "创建 Python 虚拟环境..."
                    python3 -m venv ${VENV}
                    python3 --version
                    echo "虚拟环境创建成功: ${VENV}"
                '''
            }
        }

        stage('安装依赖') {
            steps {
                sh '''
                    echo "激活虚拟环境并升级 pip..."
                    source ${VENV}/bin/activate
                    pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
                    pip cache purge
                    pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
                    echo "依赖安装完成"
                '''
            }
        }

        stage('生成环境变量文件') {
            steps {
                sh '''
                    source ${VENV}/bin/activate
                    python3 ${CONFIG_DIR}/generate_env.py
                    echo "环境变量文件生成完成"
                '''
            }
        }

        stage('准备报告目录') {
            steps {
                sh '''
                    mkdir -p ${RESULTS_DIR} ${HTML_DIR}
                    echo "报告目录创建完成"
                '''
            }
        }

        stage('恢复 Allure 历史数据') {
            steps {
                script {
                    def historyDir = "${ALLURE_HISTORY_PATH}/history"
                    if (sh(script: "test -d '${historyDir}'", returnStatus: true) == 0) {
                        sh """
                            echo "恢复历史数据..."
                            cp -r '${historyDir}' '${RESULTS_DIR}/'
                        """
                    } else {
                        echo "未发现历史数据,本次为首次运行"
                    }
                }
            }
        }

         stage('运行 Pytest 测试') {
                    steps {
                        script {
                            // 直接在这里执行 pytest 并处理退出码
                            def exitCode = sh(
                                script: """
                                    source venv/bin/activate
                                    pytest testcases/test_sdk_api.py --alluredir=reports/results -v --junitxml=${REPORTFILE}
                                """,
                                returnStatus: true  // 关键:返回状态码,而不是抛异常
                            )

                            // 只有在 report.xml 存在时才收集并解析 JUnit 报告
                            if (fileExists(REPORTFILE)) {
                                echo "发现测试报告文件: ${REPORTFILE},正在收集..."
                                junit REPORTFILE
                            } else {
                                 echo "未生成测试报告文件: ${REPORTFILE}"
                            }

                            if (exitCode == 0) {
                                echo "测试全部通过"
                                // 构建继续
                            } else if (exitCode == 1) {
                                echo "有测试用例失败"
                                currentBuild.result = 'UNSTABLE'
                                // 构建继续,标记为黄色
                            } else {
                                echo "pytest 执行异常(exit code: ${exitCode})"
                                error("Pytest 执行失败,可能是命令不存在或环境错误,终止构建")
                            }
                        }
                    }
                }

        stage('持久化 Allure 历史数据') {
            steps {
                sh """
                    echo "持久化历史数据..."
                    mkdir -p '${ALLURE_HISTORY_PATH}'
                    rm -rf '${ALLURE_HISTORY_PATH}/history'
                    if [ -d '${HTML_DIR}/history' ]; then
                        cp -r '${HTML_DIR}/history' '${ALLURE_HISTORY_PATH}/'
                        echo "历史数据已保存至: ${ALLURE_HISTORY_PATH}"
                    else
                        echo "未生成 history 目录"
                    fi
                """
            }
        }

        stage('归档报告与结果') {
            steps {
                archiveArtifacts(
                    artifacts: "${LOG_DIR}/*,report.xml",
                    fingerprint: true,
                    allowEmptyArchive: true
                )
            }
        }
    }

    post {
        success {
            echo '✅ 构建成功,测试全部通过!'

            emailext(
                subject: "✅ 构建成功: ${JOB_NAME} #${BUILD_NUMBER}",
                body: """
                    <p>项目: <strong>${JOB_NAME}</strong></p>
                    <p>构建号: ${BUILD_NUMBER}</p>
                    <p>状态: <span style='color:green;'>SUCCESS</span></p>
                    <p>构建耗时: ${currentBuild.durationString}</p>
                    <p>触发人: ${env.CHANGE_AUTHOR ?: 'System'}</p>
                    <p>构建日志: <a href='${BUILD_URL}'>点击查看</a></p>
                """,
                recipientProviders: [
                    [$class: 'DevelopersRecipientProvider'],  // 提交代码的人
                    [$class: 'RequesterRecipientProvider']     // 触发构建的人
                ],
                to: 'xu.chunjie@foxmail.com',
                mimeType: 'text/html'
            )

        }
        unstable {
            echo '⚠️ 构建完成,但有测试失败'
            echo "📊 查看报告: ${BUILD_URL}allure"

            emailext(
                subject: "⚠️构建不稳定: ${JOB_NAME} #${BUILD_NUMBER}",
                body: """
                    <p>项目: ${JOB_NAME}</p>
                    <p>状态: UNSTABLE(测试失败)</p>
                    <p>触发人: ${env.CHANGE_AUTHOR ?: 'Unknown'}</p>
                    <p>请检查测试报告。</p>
                    <p><a href='${BUILD_URL}'>构建地址</a></p>
                """,
                to: 'xu.chunjie@foxmail.com',
                mimeType: 'text/html'
            )

        }
        failure {
            echo '❌ 构建失败!可能是脚本错误、依赖问题或 pytest 执行异常'
            echo "📎 检查日志: ${BUILD_URL}console"

            emailext(
                subject: "❌ 构建失败: ${JOB_NAME} #${BUILD_NUMBER}",
                body: """
                    <p>项目: <strong>${JOB_NAME}</strong></p>
                    <p>构建号: ${BUILD_NUMBER}</p>
                    <p>状态: <span style='color:red;'>FAILED</span></p>
                    <p>触发人: ${env.CHANGE_AUTHOR ?: 'Unknown'}</p>
                    <p>错误日志:</p>
                    <pre style='background:#f4f4f4; padding:10px; border:1px solid #ccc;'>
                        ${currentBuild.rawBuild.getLog(100).join('\n')}
                    </pre>
                    <p>构建日志: <a href='${BUILD_URL}console'>点击查看完整日志</a></p>
                    <p>请尽快修复!</p>
                """,
                recipientProviders: [
                    [$class: 'DevelopersRecipientProvider'],
                    [$class: 'CulpritsRecipientProvider']  // 最近提交导致失败的人
                ],
                to: 'xu.chunjie@foxmail.com',
                mimeType: 'text/html'
            )

        }

        always {
            echo "构建状态: ${currentBuild.result}"

            // ✅ 使用 Allure 插件发布报告(核心)
            allure([
                includeProperties: false,
                jdk: '',
                properties: [],
                reportBuildPolicy: 'ALWAYS',  // 无论成功失败都生成报告
                results: [[path: 'reports/results']]  // 必须和 --alluredir 一致
            ])

            script {
                // 定义消息标题和状态图标
                def statusTitle = "构建结果: ${currentBuild.currentResult}"
                def statusEmoji = currentBuild.currentResult == 'SUCCESS' ? '✅' : '❌'

                // 构建 Markdown 消息内容数组
                def markdownText = [
                    "# ${statusTitle} ${statusEmoji}", // 一级标题,包含结果和图标
                    "> ## 项目信息", // 二级标题
                    "- **项目名称**: `${env.JOB_NAME}`", // 项目全名
                    "- **构建编号**: `${currentBuild.displayName}`", // 构建号,如 #15
                    "- **触发原因**: ${currentBuild.getBuildCauses().collect{it.shortDescription}.join(', ')}", // 触发原因
                    "> ", // 空行
                    "## 构建详情", // 二级标题
                    "- **开始时间**: ${new Date(currentBuild.startTimeInMillis).format('yyyy-MM-dd HH:mm:ss')}", // 格式化时间
                    "- **持续时间**: ${currentBuild.durationString}", // 如 "1 min 20 sec"
                    "- **构建日志**: [点击查看完整日志](${currentBuild.absoluteUrl}console)", // 超链接到日志
                    "> ", // 空行
                    "**请及时关注构建状态!**" // 强调文字
                ]

                // 发送 Markdown 消息
                wxwork(
                    robot: 'Jenkins-CI-Bot', // 替换成你在Jenkins系统配置中的机器人ID
                    type: 'markdown', // 指定消息类型为 markdown
                    text: markdownText // 传入构建好的Markdown内容数组
                )
            }
        }
    }
}

image-sswv.png

1

评论区