Home | 簡體中文 | 繁體中文 | 雜文 | 知乎專欄 | Github | OSChina 博客 | 雲社區 | 雲棲社區 | Facebook | Linkedin | 視頻教程 | 打賞(Donations) | About
知乎專欄多維度架構 微信號 netkiller-ebook | QQ群:128659835 請註明“讀者”

171.3. Jenkinsfile

171.3.1. Jenkinsfile - Declarative Pipeline

https://jenkins.io/doc/pipeline/examples/

171.3.1.1. stages
			
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                echo 'Building..'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing..'
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploying....'
            }
        }
    }
}			
			
			
171.3.1.2. script
			
// Declarative //
pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
                script {
                    def browsers = ['chrome', 'firefox']
                    for (int i = 0; i < browsers.size(); ++i) {
                        echo "Testing the ${browsers[i]} browser"
                    }
                }
                script {
                    // 一個優雅的退出pipeline的方法,這裡可執行任意邏輯
                    if( $VALUE1 == $VALUE2 ) {
                       currentBuild.result = 'SUCCESS'
                       return
                    }
                }
            }
        }
    }
}			
			
			
171.3.1.3. junit

junit4

			
		stage("測試") {
            steps {
                echo "單元測試中..."
                // 請在這裡放置您項目代碼的單元測試調用過程,例如:
                sh 'mvn test' // mvn 示例
              	// sh './gradlew test'
                echo "單元測試完成."
                junit 'target/surefire-reports/*.xml' // 收集單元測試報告的調用過程
            }
        }			
			
			

junit5 測試報告路徑與 junit4 的位置不同

			
		stage("測試") {
            steps {
                echo "單元測試中..."
              	sh './gradlew test'
                echo "單元測試完成."
              	junit 'build/test-results/test/*.xml'
            }
        }
			
			
171.3.1.4. withEnv
			
env.PROJECT_DIR='src/netkiller'			
node {
    withEnv(["GOPATH=$WORKSPACE"]) {
        stage('Init gopath') {
            sh 'mkdir -p $GOPATH/{bin,pkg,src}'
        }
        
        stage('Build go proejct') {      
            sh 'cd ${PROJECT_DIR}; go test && go build && go install'
        }
    }
}
			
			
			
node {
     git 'https://github.com/netkiller/api.git'
     withEnv(["PATH+MAVEN=${tool 'm3'}/bin"]) {
          sh "mvn -B –Dmaven.test.failure.ignore=true clean package"
     }
     stash excludes: 'target/', includes: '**', name: 'source'
}			
			
			
171.3.1.5. parameters

參數指令,觸發這個管道需要用戶指定的參數,然後在step中通過params對象訪問這些參數。

			
// Declarative //
pipeline {
    agent any
    parameters {
        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
    }
    stages {
        stage('Example') {
            steps {
                echo "Hello ${params.PERSON}"
            }
        }
    }
}			
			
			
			
Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any
    parameters {
        string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')

        text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')

        booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')

        choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')

        password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')

        file(name: "FILE", description: "Choose a file to upload")
    }
    stages {
        stage('Example') {
            steps {
                echo "Hello ${params.PERSON}"

                echo "Biography: ${params.BIOGRAPHY}"

                echo "Toggle: ${params.TOGGLE}"

                echo "Choice: ${params.CHOICE}"

                echo "Password: ${params.PASSWORD}"
            }
        }
    }
}			
			
			
171.3.1.6. options

還能定義一些管道特定的選項,介紹幾個常用的:

			
skipDefaultCheckout - 在agent指令中忽略源碼checkout這一步驟。
timeout - 超時設置options { timeout(time: 1, unit: 'HOURS') }
retry - 直到成功的重試次數options { retry(3) }
timestamps - 控制台輸出前面加時間戳options { timestamps() }			
			
			
171.3.1.7. triggers

觸發器指令定義了這個管道何時該執行,就可以定義兩種cron和pollSCM

			
cron - linux的cron格式triggers { cron('H 4/* 0 0 1-5') }
pollSCM - jenkins的poll scm語法,比如triggers { pollSCM('H 4/* 0 0 1-5') }

// Declarative //
pipeline {
    agent any
    triggers {
        cron('H 4/* 0 0 1-5')
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}			
			
			

一般我們會將管道和GitHub、GitLab、BitBucket關聯, 然後使用它們的webhooks來觸發,就不需要這個指令了。

171.3.1.8. tools

定義自動安裝並自動放入PATH裡面的工具集合

			
// Declarative //
pipeline {
    agent any
    tools {
        maven 'apache-maven-3.5.0' ①
    }
    stages {
        stage('Example') {
            steps {
                sh 'mvn --version'
            }
        }
    }
}		
			
			

註:① 工具名稱必須預先在Jenkins中配置好了 → Global Tool Configuration.

171.3.1.9. post

post section 定義了管道執行結束後要進行的操作。支持在裡面定義很多Conditions塊: always, changed, failure, success 和 unstable。 這些條件塊會根據不同的返回結果來執行不同的邏輯。

			
always:不管返回什麼狀態都會執行
changed:如果當前管道返回值和上一次已經完成的管道返回值不同時候執行
failure:當前管道返回狀態值為”failed”時候執行,在Web UI界面上面是紅色的標誌
success:當前管道返回狀態值為”success”時候執行,在Web UI界面上面是綠色的標誌
unstable:當前管道返回狀態值為”unstable”時候執行,通常因為測試失敗,代碼不合法引起的。在Web UI界面上面是黃色的標誌

// Declarative //
pipeline {
    agent any
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
    post {
        always {
            echo 'I will always say Hello again!'
        }
    }
}			
			
			

失敗發送郵件的例子

			
    post {
        failure {
            mail to: "${email}",
            subject: "Failed Pipeline: ${currentBuild.fullDisplayName}",
            body: "Something is wrong with ${env.BUILD_URL}"
        }
    }			
			
			
171.3.1.10. when 條件判斷
			
branch - 分支匹配才執行 when { branch 'master' }
environment - 環境變數匹配才執行 when { environment name: 'DEPLOY_TO', value: 'production' }
expression - groovy表達式為真才執行 expression { return params.DEBUG_BUILD } }

// Declarative //
pipeline {
    agent any
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                branch 'production'
            }
            echo 'Deploying'
        }
    }
}			
			
			
171.3.1.11. 拋出錯誤
			
error '執行出錯'
			
			
171.3.1.12. withCredentials
withCredentials: Bind credentials to variables
171.3.1.12.1. token
				
node {
   withCredentials([string(credentialsId: 'token', variable: 'TOKEN')]) {
        sh('echo $TOKEN')
   }
}				
				
				
171.3.1.13. withMaven
			
		withMaven(
            maven: 'M3') {
                sh "mvn test"
        }			
			
			
171.3.1.14. isUnix() 判斷操作系統類型
			
pipeline{
	
	agent any
	stages{
		stage("isUnix") {
			steps{
				script {
					if(isUnix() == true) {
						echo("this jenkins job running on a linux-like system")
					}else {
						error("the jenkins job running on a windows system")
					}
				}
			}
		}
	}
}			
			
			
171.3.1.15. Jenkins pipeline 中使用 sshpass 實現 scp, ssh 遠程運行
			
pipeline {
    agent {
        label "java-8"
    }
    stages  {

     	stage("環境") {
            steps {
                parallel "Maven": {
                  	script{
                      	sh 'mvn -version'
                  	}
                }, "Java": {
                    sh 'java -version'
                }, "sshpass": {
                  	sh 'apt install -y sshpass'
                    sh 'sshpass -v'
                }
            }
          
        }
        
        stage("檢出") {
            steps {
                checkout(
                  [$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]], 
                  userRemoteConfigs: [[url: env.GIT_REPO_URL]]]
                )
            }
        }

        stage("構建") {
            steps {
                echo "構建中..."
              	sh 'mvn package -Dmaven.test.skip=true'
                archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
                echo "構建完成."
            }
        }

        stage("測試") {
            steps {
                parallel "單元測試": {
                    echo "單元測試中..."
                    sh 'mvn test'
                    echo "單元測試完成."
                    junit 'target/surefire-reports/*.xml'
                }, "介面測試": {
                    echo "介面測試中..."
                    // 請在這裡放置您項目代碼的單元測試調用過程,例如 mvn test
                    echo "介面測試完成."
                }, "測試敏感詞":{
                    echo "Username: ${env.username}"
            		echo "Password: ${env.password}"
                }

            }
          
        }
      	stage("運行"){
      		steps {
            	sh 'java -jar target/java-0.0.1-SNAPSHOT.jar'
    	    }
    	}
      	stage("部署"){
      		steps {
              echo "上傳"
              sh 'sshpass -p Passw0rd scp target/*.jar root@dev.netkiller.cn:/root/'
              echo "運行"
              sh 'sshpass -p Passw0rd ssh root@dev.netkiller.cn java -jar /root/java-0.0.1-SNAPSHOT.jar'
                         	
    	    }
    	}
    }
}			
			
			
171.3.1.15.1. 後台運行
				
		stage("部署"){
            parallel{
	            stage("sshpass") {
		        	steps{
		        	    sh 'apt install -y sshpass'
		                sh 'sshpass -v'
		        	}
		        }
            	stage('stop') {
			         steps {
			            sh 'sshpass -p passw0rd ssh -f dev.netkiller.cn pkill -f java-project-0.0.2-SNAPSHOT'
			         }
			    }
			    stage('start') {
			         steps {
			            sh 'sshpass -p passw0rd scp target/*.jar dev.netkiller.cn:/root/'
                	    sh 'sshpass -p passw0rd ssh -f dev.netkiller.cn java -jar /root/java-project-0.0.2-SNAPSHOT.jar'
			          }
			    }	                
    	    }
    	}				
				
				

171.3.2. Jenkinsfile - Scripted Pipeline

		
// Jenkinsfile (Scripted Pipeline)
node {
    stage('Build') {
        echo 'Building....'
    }
    stage('Test') {
        echo 'Building....'
    }
    stage('Deploy') {
        echo 'Deploying....'
    }
}			
		
		
171.3.2.1. git
			
node {

   stage('Checkout') {
      git 'https://github.com/bg7nyt/java.git'
   }
   
}
			
			
171.3.2.2. 切換 JDK 版本
			
node('vagrant-slave') {
    env.JAVA_HOME="${tool 'jdk-8u45'}"
    env.PATH="${env.JAVA_HOME}/bin:${env.PATH}"
    sh 'java -version'
}			
			
			
171.3.2.3. groovy
			
#!groovy
import groovy.json.JsonOutput
import groovy.json.JsonSlurper

/*
Please make sure to add the following environment variables:
HEROKU_PREVIEW=<your heroku preview app>
HEROKU_PREPRODUCTION=<your heroku pre-production app>
HEROKU_PRODUCTION=<your heroku production app>
Please also add the following credentials to the global domain of your organization's folder:
Heroku API key as secret text with ID 'HEROKU_API_KEY'
GitHub Token value as secret text with ID 'GITHUB_TOKEN'
*/

node {

     server = Artifactory.server "artifactory"
     buildInfo = Artifactory.newBuildInfo()
     buildInfo.env.capture = true
    
    // we need to set a newer JVM for Sonar
    env.JAVA_HOME="${tool 'Java SE DK 8u131'}"
    env.PATH="${env.JAVA_HOME}/bin:${env.PATH}"
    
    // pull request or feature branch
    if  (env.BRANCH_NAME != 'master') {
        checkout()
        build()
        unitTest()
        // test whether this is a regular branch build or a merged PR build
        if (!isPRMergeBuild()) {
            preview()
            sonarServer()
            allCodeQualityTests()
        } else {
            // Pull request
            sonarPreview()
        }
    } // master branch / production
    else { 
        checkout()
        build()
        allTests()
        preview()
        sonarServer()
        allCodeQualityTests()
        preProduction()
        manualPromotion()
        production()
    }
}

def isPRMergeBuild() {
    return (env.BRANCH_NAME ==~ /^PR-\d+$/)
}

def sonarPreview() {
    stage('SonarQube Preview') {
        prNo = (env.BRANCH_NAME=~/^PR-(\d+)$/)[0][1]
        mvn "org.jacoco:jacoco-maven-plugin:prepare-agent install -Dmaven.test.failure.ignore=true -Pcoverage-per-test"
        withCredentials([[$class: 'StringBinding', credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN']]) {
            githubToken=env.GITHUB_TOKEN
            repoSlug=getRepoSlug()
            withSonarQubeEnv('SonarQube Octodemoapps') {
                mvn "-Dsonar.analysis.mode=preview -Dsonar.github.pullRequest=${prNo} -Dsonar.github.oauth=${githubToken} -Dsonar.github.repository=${repoSlug} -Dsonar.github.endpoint=https://api.github.com/ org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar"
            }
        }
    } 
}
    
def sonarServer() {
    stage('SonarQube Server') {
        mvn "org.jacoco:jacoco-maven-plugin:prepare-agent install -Dmaven.test.failure.ignore=true -Pcoverage-per-test"
        withSonarQubeEnv('SonarQube Octodemoapps') {
            mvn "org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar"
        }
        
        context="sonarqube/qualitygate"
        setBuildStatus ("${context}", 'Checking Sonarqube quality gate', 'PENDING')
        timeout(time: 1, unit: 'MINUTES') { // Just in case something goes wrong, pipeline will be killed after a timeout
            def qg = waitForQualityGate() // Reuse taskId previously collected by withSonarQubeEnv
            if (qg.status != 'OK') {
                setBuildStatus ("${context}", "Sonarqube quality gate fail: ${qg.status}", 'FAILURE')
                error "Pipeline aborted due to quality gate failure: ${qg.status}"
            } else {
                setBuildStatus ("${context}", "Sonarqube quality gate pass: ${qg.status}", 'SUCCESS')
            }    
        }
    }
}
    



def checkout () {
    stage 'Checkout code'
    context="continuous-integration/jenkins/"
    context += isPRMergeBuild()?"pr-merge/checkout":"branch/checkout"
    checkout scm
    setBuildStatus ("${context}", 'Checking out completed', 'SUCCESS')
}

def build () {
    stage 'Build'
    mvn 'clean install -DskipTests=true -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true -B -V'
}


def unitTest() {
    stage 'Unit tests'
    mvn 'test -B -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true'
    if (currentBuild.result == "UNSTABLE") {
        sh "exit 1"
    }
}

def allTests() {
    stage 'All tests'
    // don't skip anything
    mvn 'test -B'
    step([$class: 'JUnitResultArchiver', testResults: '**/target/surefire-reports/TEST-*.xml'])
    if (currentBuild.result == "UNSTABLE") {
        // input "Unit tests are failing, proceed?"
        sh "exit 1"
    }
}

def allCodeQualityTests() {
    stage 'Code Quality'
    lintTest()
    coverageTest()
}

def lintTest() {
    context="continuous-integration/jenkins/linting"
    setBuildStatus ("${context}", 'Checking code conventions', 'PENDING')
    lintTestPass = true

    try {
        mvn 'verify -DskipTests=true'
    } catch (err) {
        setBuildStatus ("${context}", 'Some code conventions are broken', 'FAILURE')
        lintTestPass = false
    } finally {
        if (lintTestPass) setBuildStatus ("${context}", 'Code conventions OK', 'SUCCESS')
    }
}

def coverageTest() {
    context="continuous-integration/jenkins/coverage"
    setBuildStatus ("${context}", 'Checking code coverage levels', 'PENDING')

    coverageTestStatus = true

    try {
        mvn 'cobertura:check'
    } catch (err) {
        setBuildStatus("${context}", 'Code coverage below 90%', 'FAILURE')
        throw err
    }

    setBuildStatus ("${context}", 'Code coverage above 90%', 'SUCCESS')

}

def preview() {
    stage name: 'Deploy to Preview env', concurrency: 1
    def herokuApp = "${env.HEROKU_PREVIEW}"
    def id = createDeployment(getBranch(), "preview", "Deploying branch to test")
    echo "Deployment ID: ${id}"
    if (id != null) {
        setDeploymentStatus(id, "pending", "https://${herokuApp}.herokuapp.com/", "Pending deployment to test");
        herokuDeploy "${herokuApp}"
        setDeploymentStatus(id, "success", "https://${herokuApp}.herokuapp.com/", "Successfully deployed to test");
    }
    mvn 'deploy -DskipTests=true'
}

def preProduction() {
    stage name: 'Deploy to Pre-Production', concurrency: 1
    switchSnapshotBuildToRelease()
    herokuDeploy "${env.HEROKU_PREPRODUCTION}"
    buildAndPublishToArtifactory()
}

def manualPromotion() {
    // we need a first milestone step so that all jobs entering this stage are tracked an can be aborted if needed
    milestone 1
    // time out manual approval after ten minutes
    timeout(time: 10, unit: 'MINUTES') {
        input message: "Does Pre-Production look good?"
    }
    // this will kill any job which is still in the input step
    milestone 2
}

def production() {
    stage name: 'Deploy to Production', concurrency: 1
    step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
    herokuDeploy "${env.HEROKU_PRODUCTION}"
    def version = getCurrentHerokuReleaseVersion("${env.HEROKU_PRODUCTION}")
    def createdAt = getCurrentHerokuReleaseDate("${env.HEROKU_PRODUCTION}", version)
    echo "Release version: ${version}"
    createRelease(version, createdAt)
    promoteInArtifactoryAndDistributeToBinTray()
}

def switchSnapshotBuildToRelease() {
    def descriptor = Artifactory.mavenDescriptor()
    descriptor.version = '1.0.0'
    descriptor.pomFile = 'pom.xml'
    descriptor.transform()
}

def buildAndPublishToArtifactory() {       
        def rtMaven = Artifactory.newMavenBuild()
        rtMaven.tool = "Maven 3.x"
        rtMaven.deployer releaseRepo:'libs-release-local', snapshotRepo:'libs-snapshot-local', server: server
        rtMaven.resolver releaseRepo:'libs-release', snapshotRepo:'libs-snapshot', server: server
        rtMaven.run pom: 'pom.xml', goals: 'install', buildInfo: buildInfo
        server.publishBuildInfo buildInfo
}

def promoteBuildInArtifactory() {
        def promotionConfig = [
            // Mandatory parameters
            'buildName'          : buildInfo.name,
            'buildNumber'        : buildInfo.number,
            'targetRepo'         : 'libs-prod-local',
 
            // Optional parameters
            'comment'            : 'deploying to production',
            'sourceRepo'         : 'libs-release-local',
            'status'             : 'Released',
            'includeDependencies': false,
            'copy'               : true,
            // 'failFast' is true by default.
            // Set it to false, if you don't want the promotion to abort upon receiving the first error.
            'failFast'           : true
        ]
 
        // Promote build
        server.promote promotionConfig
}

def distributeBuildToBinTray() {
        def distributionConfig = [
            // Mandatory parameters
            'buildName'             : buildInfo.name,
            'buildNumber'           : buildInfo.number,
            'targetRepo'            : 'reading-time-dist',  
            // Optional parameters
            //'publish'               : true, // Default: true. If true, artifacts are published when deployed to Bintray.
            'overrideExistingFiles' : true, // Default: false. If true, Artifactory overwrites builds already existing in the target path in Bintray.
            //'gpgPassphrase'         : 'passphrase', // If specified, Artifactory will GPG sign the build deployed to Bintray and apply the specified passphrase.
            //'async'                 : false, // Default: false. If true, the build will be distributed asynchronously. Errors and warnings may be viewed in the Artifactory log.
            //"sourceRepos"           : ["yum-local"], // An array of local repositories from which build artifacts should be collected.
            //'dryRun'                : false, // Default: false. If true, distribution is only simulated. No files are actually moved.
        ]
        server.distribute distributionConfig
}

def promoteInArtifactoryAndDistributeToBinTray() {
    stage ("Promote in Artifactory and Distribute to BinTray") {
        promoteBuildInArtifactory()
        distributeBuildToBinTray()
    }
}

def mvn(args) {
    withMaven(
        // Maven installation declared in the Jenkins "Global Tool Configuration"
        maven: 'Maven 3.x',
        // Maven settings.xml file defined with the Jenkins Config File Provider Plugin
        
        // settings.xml referencing the GitHub Artifactory repositories
         mavenSettingsConfig: '0e94d6c3-b431-434f-a201-7d7cda7180cb',
        // we do not need to set a special local maven repo, take the one from the standard box
        //mavenLocalRepo: '.repository'
        ) {
        // Run the maven build
        sh "mvn $args -Dmaven.test.failure.ignore"
    }
}

def herokuDeploy (herokuApp) {
    withCredentials([[$class: 'StringBinding', credentialsId: 'HEROKU_API_KEY', variable: 'HEROKU_API_KEY']]) {
        mvn "heroku:deploy -DskipTests=true -Dmaven.javadoc.skip=true -B -V -D heroku.appName=${herokuApp}"
    }
}

def getRepoSlug() {
    tokens = "${env.JOB_NAME}".tokenize('/')
    org = tokens[tokens.size()-3]
    repo = tokens[tokens.size()-2]
    return "${org}/${repo}"
}

def getBranch() {
    tokens = "${env.JOB_NAME}".tokenize('/')
    branch = tokens[tokens.size()-1]
    return "${branch}"
}

def createDeployment(ref, environment, description) {
    withCredentials([[$class: 'StringBinding', credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN']]) {
        def payload = JsonOutput.toJson(["ref": "${ref}", "description": "${description}", "environment": "${environment}", "required_contexts": []])
        def apiUrl = "https://api.github.com/repos/${getRepoSlug()}/deployments"
        def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GITHUB_TOKEN}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d '${payload}' ${apiUrl}").trim()
        def jsonSlurper = new JsonSlurper()
        def data = jsonSlurper.parseText("${response}")
        return data.id
    }
}

void createRelease(tagName, createdAt) {
    withCredentials([[$class: 'StringBinding', credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN']]) {
        def body = "**Created at:** ${createdAt}\n**Deployment job:** [${env.BUILD_NUMBER}](${env.BUILD_URL})\n**Environment:** [${env.HEROKU_PRODUCTION}](https://dashboard.heroku.com/apps/${env.HEROKU_PRODUCTION})"
        def payload = JsonOutput.toJson(["tag_name": "v${tagName}", "name": "${env.HEROKU_PRODUCTION} - v${tagName}", "body": "${body}"])
        def apiUrl = "https://api.github.com/repos/${getRepoSlug()}/releases"
        def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GITHUB_TOKEN}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d '${payload}' ${apiUrl}").trim()
    }
}

void setDeploymentStatus(deploymentId, state, targetUrl, description) {
    withCredentials([[$class: 'StringBinding', credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN']]) {
        def payload = JsonOutput.toJson(["state": "${state}", "target_url": "${targetUrl}", "description": "${description}"])
        def apiUrl = "https://api.github.com/repos/${getRepoSlug()}/deployments/${deploymentId}/statuses"
        def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GITHUB_TOKEN}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d '${payload}' ${apiUrl}").trim()
    }
}

void setBuildStatus(context, message, state) {
  // partially hard coded URL because of https://issues.jenkins-ci.org/browse/JENKINS-36961, adjust to your own GitHub instance
  step([
      $class: "GitHubCommitStatusSetter",
      contextSource: [$class: "ManuallyEnteredCommitContextSource", context: context],
      reposSource: [$class: "ManuallyEnteredRepositorySource", url: "https://octodemo.com/${getRepoSlug()}"],
      errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]],
      statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ]
  ]);
}

def getCurrentHerokuReleaseVersion(app) {
    withCredentials([[$class: 'StringBinding', credentialsId: 'HEROKU_API_KEY', variable: 'HEROKU_API_KEY']]) {
        def apiUrl = "https://api.heroku.com/apps/${app}/dynos"
        def response = sh(returnStdout: true, script: "curl -s  -H \"Authorization: Bearer ${env.HEROKU_API_KEY}\" -H \"Accept: application/vnd.heroku+json; version=3\" -X GET ${apiUrl}").trim()
        def jsonSlurper = new JsonSlurper()
        def data = jsonSlurper.parseText("${response}")
        return data[0].release.version
    }
}

def getCurrentHerokuReleaseDate(app, version) {
    withCredentials([[$class: 'StringBinding', credentialsId: 'HEROKU_API_KEY', variable: 'HEROKU_API_KEY']]) {
        def apiUrl = "https://api.heroku.com/apps/${app}/releases/${version}"
        def response = sh(returnStdout: true, script: "curl -s  -H \"Authorization: Bearer ${env.HEROKU_API_KEY}\" -H \"Accept: application/vnd.heroku+json; version=3\" -X GET ${apiUrl}").trim()
        def jsonSlurper = new JsonSlurper()
        def data = jsonSlurper.parseText("${response}")
        return data.created_at
    }
}			
			
			
171.3.2.4. Groovy code
171.3.2.4.1. Groovy 函數
				
node {
    stage("Test") {
        test()
    }
}

def test() {
    echo "Start"
    sleep(5)
    echo "Stop"
}
				
				
171.3.2.5. Ansi Color
			
// This shows a simple build wrapper example, using the AnsiColor plugin.
node {
    // This displays colors using the 'xterm' ansi color map.
    ansiColor('xterm') {
        // Just some echoes to show the ANSI color.
        stage "\u001B[31mI'm Red\u001B[0m Now not"
    }
}	
			
			
171.3.2.6. 寫檔案操作
			
// This shows a simple example of how to archive the build output artifacts.
node {
    stage "Create build output"
    
    // Make the output directory.
    sh "mkdir -p output"

    // Write an useful file, which is needed to be archived.
    writeFile file: "output/usefulfile.txt", text: "This file is useful, need to archive it."

    // Write an useless file, which is not needed to be archived.
    writeFile file: "output/uselessfile.md", text: "This file is useless, no need to archive it."

    stage "Archive build output"
    
    // Archive the build output artifacts.
    archiveArtifacts artifacts: 'output/*.txt', excludes: 'output/*.md'
}			
			
			
171.3.2.7. modules 實現模組
			
def modules = [
  'Java',
  'PHP',
  'Python',
  'Ruby'
]

node() {
  
  stage("checkout") {
    echo "checkout"
  }
  
  modules.each { module ->
    stage("build:${module}") {
      echo "${module}"
    }
  }
}			
			
			
171.3.2.8. docker
			
node('master') {

    stage('Build') {
        docker.image('maven:3.5.0').inside {
            sh 'mvn --version'
        }
    }

    stage('Deploy') {
        if (env.BRANCH_NAME == 'master') {
            echo 'I only execute on the master branch'
        } else {
            echo 'I execute elsewhere'
        }
    }
}			
			
			
171.3.2.9. input
			
node {
    stage('Git') {
        def branch = input message: 'input branch name for this job', ok: 'ok', parameters: [string(defaultValue: 'master', description: 'branch name', name: 'branch')]
        echo branch
    }
}			
			
			
			
node {
    stage('Git') {
        def result = input message: 'input branch name for this job', ok: 'ok', parameters: [string(defaultValue: 'master', description: 'branch name', name: 'branch'), string(defaultValue: '', description: 'commit to switch', name: 'commit')]

        echo result.branch
        echo result.commit
    }
}

node {
    stage('Git') {
        def result = input message: 'input branch name for this job', ok: 'ok', parameters: [string(defaultValue: 'master', description: 'branch name', name: 'branch'), string(defaultValue: '', description: 'commit to switch', name: 'commit')]

        sh "echo ${result.branch}"
        sh "echo ${result.commit}"
    }
}
			
			
171.3.2.10. if 條件判斷
			
node {
    dir('/var/www') {
        stage('Git') {
            if(fileExists('project')) {
                dir('project') {
                    sh 'git fetch origin'
                    sh 'git checkout master'
                    sh 'git pull'
                }
            } else {
                sh 'git clone git@git.netkiller.cn:neo/project.git project'
            }
        }
    }
}			
			
			
171.3.2.11. Docker
				
node {
	stage("Checkout") {
		checkout([$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]],userRemoteConfigs: [[url: env.GIT_REPO_URL]]])
	}
        
	docker.image('ruby').inside {
		stage("Init") {
			sh 'pwd && ls'		
			sh 'gem install rails'
			// sh 'gem install ...'		
		}	
		stage("Test") {
			sh 'ruby tc_simple_number.rb'
		}
		stage("Build") {
			sh 'ruby --version'
			archiveArtifacts artifacts: 'bin/*', fingerprint: true
		}
		stage("Deploy") {
			sh 'rsync -auzv --delete * www@host.netkiller.cn:/path/to/dir'
		}
	}
}			
			
			
171.3.2.12. conditionalSteps
			
def projectName = 'myProject'

def jobClosure = {
  steps {
    conditionalSteps {
      condition {
        fileExists(projectName+'/target/test.jar', BaseDir.WORKSPACE)
      }
      runner('Fail')
      steps {
        batchFile('echo Found some tests')
      }
    }
  }
}

freeStyleJob('AAA-Test', jobClosure)			
			
			
171.3.2.13. nexus
			
stage("Deploy") {
    nexusArtifactUploader artifacts: [
        [artifactId: 'java11', type: 'jar', file: 'target/java11.jar']
    ],
    groupId: 'org.springframework.samples',
    nexusUrl: 'netkiller.cn/repository/maven/',
    nexusVersion: 'nexus3',
    protocol: 'http',
    repository: 'maven',
    version: '2.0.0.BUILD'
}			
			
			

171.3.3. 設置環境變數

environment定義鍵值對的環境變數

		
// Declarative //
pipeline {
    agent any
    environment {
        CC = 'clang'
    }
    stages {
        stage('Example') {
            environment {
                DEBUG_FLAGS = '-g'
            }
            steps {
                sh 'printenv'
            }
        }
    }
}			
		
		
		
// Declarative //
pipeline {
    agent any
    environment {
        CC = 'clang'
    }
    stages {
        stage('Example') {
            environment { 
                AN_ACCESS_KEY = credentials('my-prefined-secret-text') ③
            }
            steps {
                sh 'printenv'
            }
        }
    }
}			
		
		
171.3.3.1. 系統環境變數
		
echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}"			
		
		
		
${env.WORKSPACE}	
println env.JOB_NAME
println env.BUILD_NUMBER	
		
		

打印所有環境變數

		
node {
    echo sh(returnStdout: true, script: 'env')
    echo sh(script: 'env|sort', returnStdout: true)
    // ...
}		
			
			
			
pipeline {
  	agent any
    stages {
        stage('Environment Example') {
            steps {
              script{
              	def envs = sh(returnStdout: true, script: 'env').split('\n')
				envs.each { name  ->
    				println "Name: $name"
				}	
              }
            }
        }
    }
}	
			
			

171.3.4. agent

agent 指令指定整個管道或某個特定的stage的執行環境。它的參數可用使用:

		
agent: 
any - 任意一個可用的agent
none - 如果放在pipeline頂層,那麼每一個stage都需要定義自己的agent指令
label - 在jenkins環境中指定標籤的agent上面執行,比如agent { label 'my-defined-label' }
node - agent { node { label 'labelName' } } 和 label一樣,但是可用定義更多可選項
docker - 指定在docker容器中運行
dockerfile - 使用源碼根目錄下面的Dockerfile構建容器來運行				
		
		
171.3.4.1. label
			
	agent {
        label "java-8"
    }
			
			
171.3.4.2. docker

https://jenkins.io/doc/book/pipeline/docker/

添加 jenkins 用戶到 docker 組

			
[root@localhost ~]# gpasswd -a jenkins docker
Adding user jenkins to group docker

[root@localhost ~]# cat /etc/group | grep ^docker
docker:x:993:jenkins	
			
			
171.3.4.2.1. 指定docker 鏡像
			
pipeline {
    agent { docker { image 'maven:3.3.3' } }
    stages {
        stage('build') {
            steps {
                sh 'mvn --version'
            }
        }
    }
}
			
				
			
pipeline {
    agent { docker { image 'php' } }
    stages {
        stage('build') {
            steps {
                sh 'php --version'
            }
        }
    }
}

pipeline {
    agent {
        docker { image 'php:latest' }
    }
    stages {
        stage('Test') {
            steps {
                sh 'php --version'
            }
        }
    }
}
			
				
171.3.4.2.2. args 參數

掛在 /root/.m2 目錄

			
pipeline {
    agent {
        docker {
            image 'maven:latest'
            args '-v $HOME/.m2:/root/.m2'
        }
    }
    stages {
        stage('Build') {
            steps {
                sh 'mvn -B'
            }
        }
    }
}			
			
				
171.3.4.2.3. Docker outside of Docker (DooD)
				
	docker.image('maven').inside("-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker") {
		sh 'docker images'
	}				
				
				
171.3.4.2.4. 掛在宿主主機目錄
				
node {
    stage("Checkout") {
        checkout(
          [$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]], userRemoteConfigs: [[url: env.GIT_REPO_URL]]]
        )
		sh 'pwd'
    }
	docker.image('maven:latest').inside("-v /root/.m2:/root/.m2") {
        stage("Build") {
                sh 'java -version'
                sh 'mvn package -Dmaven.test.failure.ignore -Dmaven.test.skip=true'
                archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
        }

        stage("Test") {
                sh 'java -jar target/webflux-0.0.1-SNAPSHOT.jar &'
                sleep 20
                sh 'mvn test -Dmaven.test.failure.ignore'
                junit 'target/surefire-reports/*.xml'
        }
    }
}				
				
				
171.3.4.2.5. 構建鏡像
				
node {
    checkout scm

    docker.withRegistry('http://hub.netkiller.cn:5000') {

        def customImage = docker.build("project/api:1.0")

        /* Push the container to the custom Registry */
        customImage.push()
    }
}				
				
				

容器內運行腳本

				
node {
    checkout scm

    def customImage = docker.build("my-image:${env.BUILD_ID}")

    customImage.inside {
        sh 'make test'
    }
}
				
				
				
		dir ('example') {
            /* 構建鏡像 */
            def customImage = docker.build("example-group/example:${params.VERSION}")

            /* hub.netkiller.cn是你的Docker Registry */
            docker.withRegistry('https://hub.netkiller.cn/', 'docker-registry') {
                /* Push the container to the custom Registry */
                // push 指定版本
                customImage.push('latest')
            }
        }				
				
				
				
stage('DockerBuild') {
    sh """
    rm -f src/docker/*.jar
    cp target/*.jar src/docker/*.jar
    """
    
    dir ("src/docker/") {
        def image = docker.build("your/demo:1.0.0")
        image.push()
    }
}				
				
				
171.3.4.3. Dockerfile

創建 Dockerfile 檔案

			
FROM node:7-alpine

RUN apk add -U subversion
			
			

創建 Jenkinsfile 檔案

			
// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent { dockerfile true }
    stages {
        stage('Test') {
            steps {
                sh 'node --version'
                sh 'svn --version'
            }
        }
    }
}
			
			

171.3.5. Steps

171.3.5.1. parallel 平行執行
			
	stage('test') {
      parallel {
        stage('test') {
          steps {
            echo 'hello'
          }
        }
        stage('test1') {
          steps {
            sleep 1
          }
        }
        stage('test2') {
          steps {
            retry(count: 5) {
              echo 'hello'
            }

          }
        }
      }			
			
			
171.3.5.2. echo
			
    stage('Deploy') {
        echo 'Deploying....'
    }
			
			
171.3.5.3. catchError 捕獲錯誤
			
node {
    catchError {
        sh 'might fail'
    }
    step([$class: 'Mailer', recipients: 'admin@somewhere'])
}

			
	stage('teatA') {
      steps {
        catchError() {
          sh 'make'
        }

        mail(subject: 'Test', body: 'aaaa', to: 'netkiller@msn.com')
      }
    }
			
			
			
171.3.5.4. 睡眠
			
node {
    sleep 10
    echo 'Hello World'
}			
			
			
			
sleep(time:3,unit:"SECONDS")			
			
			
171.3.5.5. 限制執行時間
			
		stage('enforce') {
          steps {
            timeout(activity: true, time: 1) {
              echo 'test'
            }

          }
        }
			
			
171.3.5.6. 時間截
			
	stage('timestamps') {
          steps {
            timestamps() {
              echo 'test'
            }
          }
    }			
			
			

171.3.6. 版本控制

171.3.6.1. checkout

https://github.com/jenkinsci/workflow-scm-step-plugin/blob/master/README.md

下面配置適用與 Webhook 方式

			
	stage('checkout') {
      steps {
        checkout(scm: [$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]], 
                          userRemoteConfigs: [[url: env.GIT_REPO_URL]]], changelog: true, poll: true)
      }
    }			
			
			

從 URL 獲取代碼

			
node {
   checkout ([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '', url: 'https://github.com/bg7nyt/java.git']]])   
}			
			
			
171.3.6.2. Git
			
		stage('Git') {
          	steps {
            	git(url: 'https://git.dev.tencent.com/netkiller/java.git', branch: 'master', changelog: true, poll: true)
          	}
    	}
			
			

171.3.7. 節點與過程

171.3.7.1. sh
			
		stage("build") {
            steps {
                sh "mvn package -Dmaven.test.skip=true"
            }
        }
			
			
			
		steps {
        	script{
            	sh 'find /etc/'
			}
		}
			
			

例 171.1. Shell Docker 示例

Shell Docker 使用 docker 命令完成構建過程

				
registryUrl='127.0.0.1:5000'	    # 私有鏡像倉庫地址
imageName='netkiller/project'	# 鏡像名稱
imageTag=$BRANCH	            # 上面設置Branch分支,這裡可以當做環境變數使用

echo ' >>> [INFO] enter workspace ...'

cd $WORKSPACE/				# 進入到jenkins的工作區,jenkins會將gitlab倉庫代碼pull到這裡,用於製作鏡像檔案

# 根據不同的Branch生成不同的swoft的配置檔案,區分測試還是生成等 
echo ' >>> [INFO] create startup.sh ...'
(
cat << EOF

# 啟動 Shell 寫在此處

EOF
) > ./entrypoint.sh

# 生成 Dockerfile
echo ' >>> [INFO] begin create Dockerfile ...'
rm -f ./Dockerfile
(
cat << EOF
FROM netkiller/base
LABEL maintainer=netkiller@msn.com

COPY . /var/www/project
WORKDIR /var/www/project
EXPOSE 80
ENV PHP_ENVIRONMENT $BRANCH
ENTRYPOINT [ "bash", "/var/www/project/entrypoint.sh" ]
EOF
) > ./Dockerfile


#刪除無用鏡像
echo ' >>> [INFO] begin cleaning image ...'
for image in `docker images | grep -w $imageName | grep -i -w $imageTag | awk '{print $3}'`
do
    docker rmi $image -f
done

#製作鏡像
echo ' >>> [INFO] begin building image ...'
docker build --tag $imageName:$imageTag --rm .

#給鏡像打標籤
img=`docker images | grep -w $imageName | grep -i -w $imageTag | awk '{print $3}'`
docker tag $img $registryUrl/$imageName:$imageTag

#push到私有鏡像倉庫
echo ' >>> [INFO] begin publishing image ...'
docker push $registryUrl/$imageName:$imageTag

#刪除剛剛製作的鏡像, 釋放存儲空間
echo ' >>> [INFO] cleaning temporary building ...'
docker rmi -f $imageName:$imageTag
docker rmi -f $registryUrl/$imageName:$imageTag	
				
				

171.3.7.2. Windows 批處理腳本
			
	stage('bat') {
        steps {
            bat(returnStatus: true, returnStdout: true, label: 'aa', encoding: 'utf-8', script: 'dir')
    	}
    }			
			
			
171.3.7.3. 分配工作空間
			
	stage('alocate') {
        steps {
            ws(dir: 'src') {
                echo 'aaa'
            }

    	}
    }
			
			
171.3.7.4. node
			
	stage('node') {
    	steps {
            node(label: 'java-8') {
              sh 'mvn package'
            }

        }
	}				
			
			

171.3.8. 工作區

171.3.8.1. 變更目錄
			
	stage('subtask') {
        steps {
            dir(path: '/src') {
             	echo 'begin'
              	sh '''mvn test'''
            	echo 'end'
        	}
		}
	}
			
			
171.3.8.2. 判斷檔案是否存在
			
	stage('exists') {
          steps {
            fileExists '/sss'
          }
    }	
			
			
			
def exists = fileExists 'file'

if (exists) {
    echo 'Yes'
} else {
    echo 'No'
}
			
			
			
if (fileExists('file')) {
    echo 'Yes'
} else {
    echo 'No'
}			
			
			
			

 
pipeline{
	agent any
	stages{
      stage("Checkout") {
            steps {
                checkout(
                  [$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]], 
                  userRemoteConfigs: [[url: env.GIT_REPO_URL]]]
                )
            }
        }
      
		stage("fileExists") {
			steps{
              	echo pwd()
              	sh 'ls -1'
				script {
					def exists = fileExists 'README.md'
                    if (exists) {
                        echo 'Yes'
                    } else {
                        echo 'No'
                    }
				}
			}
		}
	}
}			
			
			
171.3.8.3. 分配工作區
			
    stage('alocate') {
        steps {
            ws(dir: 'src') {
                echo 'aaa'
            }

    	}
    }			
			
			
171.3.8.4. 清理工作區
			
	stage('test') {
        steps {
            cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, cleanWhenSuccess: true, cleanWhenUnstable: true, cleanupMatrixParent: true, deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true, skipWhenFailed: true, externalDelete: '/aa')
    	}
    }			
			
			
171.3.8.5. 遞歸刪除目錄
			
	stage('deldir') {
          steps {
            deleteDir()
          }
    }			
			
			
171.3.8.6. 寫檔案
			
		stage('write') {
          steps {
            writeFile(file: 'hello.txt', text: 'Helloworld')
          }
        }
		
			
			
171.3.8.7. 讀檔案
			
        stage('read') {
          steps {
            readFile 'hello.txt'
          }
        }