Ozan GUNALP - Emmanuel QUINCEROT
Feature Branching + Continuous Integration
Teamcity
Jenkins
Two systems: unnecessary complexity
Jenkins only!
1000+ plugins
120k+ installations
Code your Continuous Delivery pipeline
Check-in to your code base
Spoiler Alert : Test it!
1 pipeline file → 1 job
1 branch → 1 job defined in Jenkinsfile
parallel
) stage
sbat
, sh
, retry
, timeout
...
node
junit
)
node() {
stage('Checkout') {
checkout(scm)
sh 'git clean -xdf'
}
stage('Build and test') {
sh './gradlew build'
junit 'build/test-results/test/*.xml'
}
}
... and they lived happily ever after
Job Deploy
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.
import groovy.json.JsonSlurper
@NonCPS
def parseJson() {
return new JsonSlurper().parseText('{"aaa":3}')
}
node() {
def a
stage('List') {
for (def item : parseJson()) {
a = item
}
}
stage('print') { echo a }
}
java.io.NotSerializableException: java.util.TreeMap$Entry
import groovy.json.JsonSlurper
@NonCPS
def parseJson(String fieldName) {
return new JsonSlurper().parseText('{"aaa":3}')?."$fieldName"
}
node() {
String output
stage('List') {
output = parseJson('aaa')
}
stage('print') { echo output }
}
No space left on disk
Result:
Next try:
git clean -xdf
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()
}
}
@Override
@Before
void setUp() throws Exception {
super.setUp()
helper.scriptRoots += 'src/main/groovy'
helper.baseScriptRoot = ''
binding.setVariable('scm', [
$class : 'GitSCM',
branches : [[name: 'AMX-12345_test']],
doGenerateSubmoduleConfigurations: false,
extensions : [],
submoduleCfg : [],
userRemoteConfigs : [[ url : "/var/git-repo" ]]
])
helper.registerAllowedMethod('junit', [String.class], null)
}
Jenkinsfile.run()
Jenkinsfile.node(groovy.lang.Closure)
Jenkinsfile.stage(Checkout, groovy.lang.Closure)
Jenkinsfile.checkout({$class=GitSCM, branches=[{name=AMX-12345_test}], ...
Jenkinsfile.sh(git clean -xdf)
Jenkinsfile.stage(Build and test, groovy.lang.Closure)
Jenkinsfile.sh(./gradlew build)
Jenkinsfile.junit(build/test-results/test/*.xml)
@Test
void testNonRegression() throws Exception {
loadScript('Jenkinsfile')
printCallStack()
super.testNonRegression(false)
}
Transforms and interprets Groovy code as in Jenkins
Intercepts method calls for stack tracing and mocking
Done:
Continuous improvement:
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'
}
}
}
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'