跳转至

使用 JenkinsFile 构建 golang 项目


2024-08-26 by dongnan

简介

Jenkinsfile 是一个包含 Jenkins Pipeline 定义的文本文件,使用 Groovy 编写。它描述了构建、测试和部署项目的步骤。 通过 Jenkinsfile,可以实现持续集成和持续部署(CI/CD)的自动化过程。

目标

使用 JenkinsFile 方式,构建 golang 项目。

环境描述

  • OS: Ubuntu Server 20.04 LTS
  • Docker: 19.03.15
  • Docker-compose: 1.29.2
  • Jenkins: 2.452.2 LTS

前提条件

  • 首先是 jenkins 环境,如果你没有 Jenkins,可以使用 Docker 部署。参考这篇文章
  • 其次是 golang 项目托管在 git 仓库,并且在项目中创建了一个 Jenkinsfile 文件。

基本结构

Jenkinsfile 的基本结构可以分为以下几个部分:

  • 声明式 Pipeline: 使用声明式语法定义流水线,语法简单、易读。
  • 脚本式 Pipeline: 使用脚本式语法定义流水线,语法灵活、功能强大,但较复杂。

下面的例子是 声明式 Pipeline 为例。

示例

pipeline {
    agent any

    environment {
        //GO
        GO111MODULE = 'on'
        GOPROXY = 'https://goproxy.cn,direct'
        GOPATH = "${env.WORKSPACE}/go"
        GOBIN = "${env.GOPATH}/bin"
        PATH = "${env.GOBIN}:${env.PATH}"
        APPNAME = 'ci-go'
        // Docker
        HUBADDR = 'yjt.xxx.com'
        HUBPROJECT = 'test'
        HUBENV = "${BRANCH_NAME}"
        DOCKERFILE_HOME = 'docker'
        DOCKER_IMAGE_INFO = """${sh(
            returnStdout: true,
            script: 'tail -n1 ./version.txt'
        ).trim()}"""                        //返回值会带有换行符,使用trim 替换掉!!!
        DOCKER_IMAGE_TAG = "${HUBADDR}/${HUBPROJECT}/${HUBENV}/${DOCKER_IMAGE_INFO}"
        // Email
        RECIPIENTS = 'admin@xxx.com'
        //RECIPIENTS = 'admin@xxx.com, dongnan@xxx.com'
        SUBJECT_FAILURE = "Build Failure: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
        SUBJECT_FIXED = "Build Fixed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
        SUBJECT_NOT_BUILT = "Build Skipped: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
        BODY_TEMPLATE = """<p>Build Status: ${BUILD_STATUS}</p>
                           <p>Job: ${env.JOB_NAME} #${env.BUILD_NUMBER}</p>
                           <p>Check console output at Jenkins_URL to view the results.</p>"""
    }

    stages {
        // stage('Checkout') {
        //     steps {
        //         git 'https://your-git-repo-url.git'
        //     }
        // }

        stage('Check Docker Image') {
            steps {
                // groovy
                script {
                    def imageExists = sh(script: "docker inspect ${DOCKER_IMAGE_TAG} > /dev/null 2>&1", returnStatus: true) == 0
                    if (imageExists) {
                        currentBuild.result = 'NOT_BUILT'
                        echo "Docker image ${DOCKER_IMAGE_TAG} already exists, skipping build."
                        currentBuild.description = "Docker image exists, build skipped."
                        return
                    } else {
                        echo "Docker image ${DOCKER_IMAGE_TAG} does not exist, proceeding with build."
                    }
                }
            }
        }

        stage('Build Go') {
            // docker镜像不存在,则执行构建步骤,否则跳过此步骤
            when {
                expression { currentBuild.result != 'NOT_BUILT' }
            }
            steps {
                // Go版本; 清理并下载依赖; 编译Go 
                sh "go version && go mod tidy && go build -o ${APPNAME}"
                sh "mv ${APPNAME} ${DOCKERFILE_HOME}/"
            }
        }

        stage('Build Docker') {
            // docker镜像不存在,则执行构建步骤,否则跳过此步骤
            when {
                expression { currentBuild.result != 'NOT_BUILT' }
            }
            steps {
                // 多行 shell
                sh """
                cd ${DOCKERFILE_HOME}
                pwd
                docker build -t ${DOCKER_IMAGE_TAG} .
                """
                // sh "docker push ${DOCKER_IMAGE_TAG}"
            }
        }

        stage('Test Image') {
            when {
                expression { currentBuild.result != 'NOT_BUILT' }
            }
            steps {
                // 添加你的测试步骤
                sh 'echo "Running tests..."'
            }
        }
    }

    post {
        failure {
            script {
                def subject = SUBJECT_FAILURE
                def body = BODY_TEMPLATE.replace('null', 'FAILURE')

                mail(
                    to: "${RECIPIENTS}",
                    subject: subject,
                    body: body,
                    mimeType: 'text/html'
                )
            }
        }

        fixed {
            script {
                def subject = SUBJECT_FIXED
                def body = BODY_TEMPLATE.replace('null', 'FIXED')

                mail(
                    to: "${RECIPIENTS}",
                    subject: subject,
                    body: body,
                    mimeType: 'text/html'
                )
            }
        }

        notBuilt {
            script {
                def subject = SUBJECT_NOT_BUILT
                def body = BODY_TEMPLATE.replace('null', 'NOT_BUILT')

                mail(
                    to: "${RECIPIENTS}",
                    subject: subject,
                    body: body,
                    mimeType: 'text/html'
                )
            }
        }

    }
}

主要元素:

  • environment: 定义 golang、docker、邮件 所使用的环境变量。
  • stages:阶段包括 stage 与 steps。
    • Check Docker Image:检查 docker 镜像是否存在,存在则退出。
    • Build Go: 编译 golang 程序。
    • Build Docker:打包 docker 镜像。
    • Test Image:测试镜像,过程略。
  • post :定义在 Pipeline 结束后执行的操作。
    • 这里只用了 failure、fixe、notBuilt 这3种条件块。

配置项目

在 Jenkins 的项目中配置如下:

配置 git 仓库地址与分支

确定 Jenkinsfile 文件的路径位置

验证

通过 git 仓库中的 Jenkinsfile 文件构建项目,不同的阶段状态:

总结

Jenkinsfile 提供了一种强大且灵活的方法来定义 CI/CD 流水线。 通过使用声明式或脚本式 Pipeline 语法,可以根据项目需求自动化构建、测试和部署过程。

利用环境变量、多分支 Pipeline 和条件执行等功能,可以进一步增强流水线的功能和灵活性。

参考文档

回到页面顶部