Ozan GUNALP - Emmanuel QUINCEROT
Feature Branching + Continuous Integration
Teamcity
Jenkins
Two systems: unnecessary complexity
Jenkins only!
Groovy interpreter that runs Groovy code in the continuation passing style, so that the execution can be paused any moment and restarted without cooperation from the program being interpreted.
@NonCPS
def parseJson() {
return new JsonSlurper().parseText('{"tag":"jenkins"}')
}
node() {
def a
stage('List') {
for (def item : parseJson()) {
a = item
}
}
stage('Print') { echo a.tag }
}
java.io.NotSerializableException: java.util.TreeMap$Entry
import groovy.json.JsonSlurper
@NonCPS
def parseJson(String fieldName) {
return new JsonSlurper().parseText('{"tag":"jenkins"}')?."$fieldName"
}
node() {
String output
stage('List') {
output = parseJson('tag')
}
stage('print') { echo output }
}
node() {
stage('List') {
List<Long> list = createList()
}
}
@NonCPS
long getLong() {
return 2L
}
@NonCPS
List<Long> createList() {
return [getLong()]
}
hudson.remoting.ProxyException:
org.codehaus.groovy.runtime.typehandling.GroovyCastException:
Cannot cast object '2' with class 'java.lang.Long' to class 'java.util.List'
No space left on disk
First try
Result
Second try
ws
git clean -xdf
Much closer to the TeamCity behaviour
src/main/jenkins
├── job All Jenkins jobs declarations
│ ├── deploy.jenkins
│ ├── integration.jenkins
│ ├── seleniums.jenkins
│ └── sonar.jenkins
├── lib
│ ├── commons.jenkins Common function declarations
│ └── scriptLoader.jenkins Main loader for scripts
└── step
├── deploy Jenkins files for Deployment steps
│ ├── flyway.jenkins
│ └── tomcat.jenkins
└── ...
Map imports() {
[ commons: 'lib/commons.jenkins',
flyway: 'step/deploy/flyway.jenkins',
tomcat: 'step/deploy/tomcat.jenkins',
]
}
void execute() {
stage('flyway') {
flyway.execute()
}
stage('tomcat') {
tomcat.execute()
}
}
return this
String scriptToLoad = 'deploy.jenkins'
def loadedScript = node () {
checkout(/* GIT CONFIGURATION */)
def runner = load 'jenkins/src/main/jenkins/lib/scriptLoader.jenkins'
return runner.configure(scriptToLoad)
}
loadedScript.execute()
def configure(filename) {
def runnable = load(filename)
checkIsScript(runnable, filename)
importAll(runnable)
return runnable
}
private void importAll(runnable) {
List l = createList(runnable.imports().keySet())
for (int i = 0; i < l.size(); i++) {
String scriptFile = runnable.imports()[l.get(i)]
def script = load(scriptFile)
checkIsScript(script, scriptFile)
runnable."${l.get(i)}" = script
}
}
return this
The same code for many environments.
One change can cause regression.
Oops! The load test is triggered after deploying the production!
We need something to test and track the impact of our changes!
pipeline-as-code
Open Sourced by LesFurets.com
node() {
stage('Checkout') {
checkout(scm)
sh 'git clean -xdf'
}
stage('Build and test') {
sh './gradlew build'
junit 'build/test-results/test/*.xml'
}
}
import com.lesfurets.jenkins.helpers.BasePipelineTest
class TestJenkinsfile extends BaseRegressionTestCPS {
/*...*/
@Test
void testJenkinsFile() throws Exception {
loadScript('Jenkinsfile')
printCallStack()
}
}
Transforms and interprets Groovy code as in Jenkins
Intercepts method calls for stack tracing and mocking
Pros
Cons
That rocks!
Emmanuel Quincerot
Ozan Gunalp
Open Source @ LesFurets
https://github.com/lesfurets
JenkinsPipelineUnit
https://github.com/lesfurets/JenkinsPipelineUnit
Scripted
node('linux') {
stage('build') {
try {
sh 'mvn clean install'
sh 'notif_success.sh'
}
catch (e) { sh 'rollback.sh' }
}
}
Declarative
pipeline {
agent linux
stages {
stage('build') {
sh 'mvn clean install'
}
postBuild {
success {
sh 'notif_success.sh'
}
failure {
sh 'rollback.sh'
}
}
}