知乎專欄 | 多維度架構 | 微信號 netkiller-ebook | QQ群:128659835 請註明“讀者” |
https://jenkins.io/doc/pipeline/examples/
pipeline { agent any stages { stage('Build') { steps { echo 'Building..' } } stage('Test') { steps { echo 'Testing..' } } stage('Deploy') { steps { echo 'Deploying....' } } } }
// 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 } } } } } }
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' } }
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' }
參數指令,觸發這個管道需要用戶指定的參數,然後在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}" } } } }
還能定義一些管道特定的選項,介紹幾個常用的:
skipDefaultCheckout - 在agent指令中忽略源碼checkout這一步驟。 timeout - 超時設置options { timeout(time: 1, unit: 'HOURS') } retry - 直到成功的重試次數options { retry(3) } timestamps - 控制台輸出前面加時間戳options { timestamps() }
觸發器指令定義了這個管道何時該執行,就可以定義兩種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來觸發,就不需要這個指令了。
定義自動安裝並自動放入PATH裡面的工具集合
// Declarative // pipeline { agent any tools { maven 'apache-maven-3.5.0' ① } stages { stage('Example') { steps { sh 'mvn --version' } } } }
註:① 工具名稱必須預先在Jenkins中配置好了 → Global Tool Configuration.
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}" } }
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' } } }
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") } } } } } }
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' } } } }
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' } } } }
// Jenkinsfile (Scripted Pipeline) node { stage('Build') { echo 'Building....' } stage('Test') { echo 'Building....' } stage('Deploy') { echo 'Deploying....' } }
node('vagrant-slave') { env.JAVA_HOME="${tool 'jdk-8u45'}" env.PATH="${env.JAVA_HOME}/bin:${env.PATH}" sh 'java -version' }
#!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 } }
// 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" } }
// 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' }
def modules = [ 'Java', 'PHP', 'Python', 'Ruby' ] node() { stage("checkout") { echo "checkout" } modules.each { module -> stage("build:${module}") { echo "${module}" } } }
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' } } }
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}" } }
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' } } } }
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' } } }
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)
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' }
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' } } } }
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" } } } } } }
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構建容器來運行
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
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' } } } }
掛在 /root/.m2 目錄
pipeline { agent { docker { image 'maven:latest' args '-v $HOME/.m2:/root/.m2' } } stages { stage('Build') { steps { sh 'mvn -B' } } } }
docker.image('maven').inside("-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker") { sh 'docker images' }
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' } } }
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() } }
stage('test') { parallel { stage('test') { steps { echo 'hello' } } stage('test1') { steps { sleep 1 } } stage('test2') { steps { retry(count: 5) { echo 'hello' } } } }
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') } }
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']]]) }
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
stage('bat') { steps { bat(returnStatus: true, returnStdout: true, label: 'aa', encoding: 'utf-8', script: 'dir') } }
stage('subtask') { steps { dir(path: '/src') { echo 'begin' sh '''mvn test''' echo 'end' } } }
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' } } } } } }
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') } }