building a continuous delivery pipeline with gradle and jenkins
DESCRIPTION
Speaker: Peter Niederwieser G&G Special Topics Getting software from a developer's machine to a production environment without a fully automated process is time-consuming and error-prone. Continuous Delivery enables building, testing and deploying of software through build pipelines with well-defined quality gates. In this session, we will discuss how to build such a pipeline with the help of Gradle and Jenkins. With Jenkins as the centerpiece of our build pipeline, we will model our way from build to deployment. We will start by introducing an examplary application and learn how to build it with Gradle. Step by step, we will touch on topics like automating unit, integration and functional tests, incorporating popular code quality tools, as well as packaging, publishing and deploying the deliverable.TRANSCRIPT
Building a Con,nuous Delivery Pipeline
with
and
Peter NiederwieserPrincipal Engineer, Gradleware
@pniederw
Releases don’t have to be painful
Con,nuous Delivery
Deliver so5ware fast and frequently
#1 Every commit can result in a release
#1 Every commit can result in a release
#2 Automate everything!
#1 Every commit can result in a release
#2 Automate everything!
#3 Automated tests are essenCal
#1 Every commit can result in a release
#2 Automate everything!
#3 Automated tests are essenCal
#4 Done means released
Build pipeline
Automated manifestaCon of delivery process
Build quality in!
Establish automated quality gates
Compile/Unit Tests
Compile/Unit Tests
IntegraCon Tests
Compile/Unit Tests
IntegraCon Tests
Code Analysis
Test
Compile/Unit Tests
IntegraCon Tests
Code Analysis
Package/Deploy
Test
Compile/Unit Tests
IntegraCon Tests
Code Analysis
Package/Deploy
Acceptance Tests
Test
Compile/Unit Tests
IntegraCon Tests
Code Analysis
Package/Deploy
UAT
Acceptance Tests
Test
Compile/Unit Tests
IntegraCon Tests
Code Analysis
Package/Deploy
UAT Prod
Acceptance Tests
!
! But how?
The “revolu,onary” sample applica,on
Internet To Do applicationBrowser Data
store
Writes
Reads
Mul,-‐project dependencies
Model
Web Repository
dependdepend
depend
Project hierarchytodo
model
repository
build.gradle
settings.gradle
web
build.gradle
build.gradle
build.gradle
gradle
wrapper
xyz.gradle
gradlew
gradlew.bat
Project hierarchytodo
model
repository
build.gradle
settings.gradle
web
build.gradle
build.gradle
build.gradle
gradle
wrapper
xyz.gradle
gradlew
gradlew.bat
Project hierarchytodo
model
repository
build.gradle
settings.gradle
web
build.gradle
build.gradle
build.gradle
gradle
wrapper
xyz.gradle
gradlew
gradlew.bat
Define project-‐specific behavior
Project hierarchy
include 'model'include 'repository'include 'web'
settings.gradle
Defines which projects are taking part in the build
todo
model
repository
build.gradle
settings.gradle
web
build.gradle
build.gradle
build.gradle
gradle
wrapper
xyz.gradle
gradlew
gradlew.bat
Project hierarchytodo
model
repository
build.gradle
settings.gradle
web
build.gradle
build.gradle
build.gradle
gradle
wrapper
xyz.gradle
gradlew
gradlew.bat
Always use Wrapperto execute the build!
Project hierarchytodo
model
repository
build.gradle
settings.gradle
web
build.gradle
build.gradle
build.gradle
gradle
wrapper
xyz.gradle
gradlew
gradlew.bat
Externalize concerns into script pluginsand organize them in dedicated directory
Project hierarchytodo
model
repository
build.gradle
settings.gradle
web
build.gradle
build.gradle
build.gradle
gradle
wrapper
xyz.gradle
gradlew
gradlew.bat
Externalize concerns into script pluginsand organize them in dedicated directory
Examples:
Versioning strategy Integra,on and func,onal test setup Deployment func,onality ...
Project ar,facts
rootproject
modelproject
repositoryproject
webproject
JAR
JAR
WAR
Project ar,facts
rootproject
modelproject
repositoryproject
webproject
JAR
JAR
WAR
Deployable ar,fact
Stages in build pipeline
Acceptance stage
Functional tests
PublishBinaries
Commit stage
DeployBinaries
UAT
DeployBinaries
Production
IntegrationTests
Code Analysis
AssembleDistribution
CompileUnit Tests
RetrieveBinaries
DeployBinaries
Asserts that system works at a technical level
Stages in build pipeline
Acceptance stage
Functional tests
PublishBinaries
Commit stage
DeployBinaries
UAT
DeployBinaries
Production
IntegrationTests
Code Analysis
AssembleDistribution
CompileUnit Tests
RetrieveBinaries
DeployBinaries
Asserts that system works on a func,onal/non-‐func,onal level
Stages in build pipeline
Acceptance stage
Functional tests
PublishBinaries
Commit stage
DeployBinaries
UAT
DeployBinaries
Production
IntegrationTests
Code Analysis
AssembleDistribution
CompileUnit Tests
RetrieveBinaries
DeployBinaries
Trigger manuallyTrigger manually
Commit stage: Compile/unit tests
Rapid feedback (< 5 mins)
Run on every VCS check-‐in
Priority: fix broken build
PublishBinaries
Commit stage
CompileUnit Tests
IntegrationTests
Code Analysis
AssembleDistribution
Commit stage: Integra,on tests
Long running tests
Require environment setup
Hard to maintain
PublishBinaries
Commit stage
CompileUnit Tests
IntegrationTests
Code Analysis
AssembleDistribution
Separate tests in project layout
todo
model
repository
web
src
integTest
java
main
java
test
java
Produc,on Java sources
Unit test Java sources
Separate tests in project layout
todo
model
repository
web
src
integTest
java
main
java
test
java
Integra,on test Java sources
Produc,on Java sources
Unit test Java sources
Separate tests with SourceSets
sourceSets { integrationTest { java.srcDir file('src/integTest/java') resources.srcDir file('src/integTest/resources') compileClasspath = sourceSets.main.output + configurations.testRuntime runtimeClasspath = output + compileClasspath }}
task integrationTest(type: Test) { description = 'Runs the integration tests.' group = 'verification' testClassesDir = sourceSets.integrationTest.output.classesDir classpath = sourceSets.integrationTest.runtimeClasspath testResultsDir = file("$testResultsDir/integration")}
Separate tests with SourceSets
sourceSets { integrationTest { java.srcDir file('src/integTest/java') resources.srcDir file('src/integTest/resources') compileClasspath = sourceSets.main.output + configurations.testRuntime runtimeClasspath = output + compileClasspath }}
task integrationTest(type: Test) { description = 'Runs the integration tests.' group = 'verification' testClassesDir = sourceSets.integrationTest.output.classesDir classpath = sourceSets.integrationTest.runtimeClasspath testResultsDir = file("$testResultsDir/integration")}
Set source and resources directory
Separate tests with SourceSets
sourceSets { integrationTest { java.srcDir file('src/integTest/java') resources.srcDir file('src/integTest/resources') compileClasspath = sourceSets.main.output + configurations.testRuntime runtimeClasspath = output + compileClasspath }}
task integrationTest(type: Test) { description = 'Runs the integration tests.' group = 'verification' testClassesDir = sourceSets.integrationTest.output.classesDir classpath = sourceSets.integrationTest.runtimeClasspath testResultsDir = file("$testResultsDir/integration")}
Set source and resources directory
Set compile and run,me classpath
Separate tests with SourceSets
sourceSets { integrationTest { java.srcDir file('src/integTest/java') resources.srcDir file('src/integTest/resources') compileClasspath = sourceSets.main.output + configurations.testRuntime runtimeClasspath = output + compileClasspath }}
task integrationTest(type: Test) { description = 'Runs the integration tests.' group = 'verification' testClassesDir = sourceSets.integrationTest.output.classesDir classpath = sourceSets.integrationTest.runtimeClasspath testResultsDir = file("$testResultsDir/integration")}
Custom test results directory
Set source and resources directory
Set compile and run,me classpath
Separate tests with SourceSets
sourceSets { integrationTest { java.srcDir file('src/integTest/java') resources.srcDir file('src/integTest/resources') compileClasspath = sourceSets.main.output + configurations.testRuntime runtimeClasspath = output + compileClasspath }}
task integrationTest(type: Test) { description = 'Runs the integration tests.' group = 'verification' testClassesDir = sourceSets.integrationTest.output.classesDir classpath = sourceSets.integrationTest.runtimeClasspath testResultsDir = file("$testResultsDir/integration")}
Custom test results directory
Set source and resources directory
Set compile and run,me classpath
gradlew integrationTest
Database integra,on tests
stopDatabasetest start
Databasebuild
Schemaintegration
Test check build...
Database integra,on tests
stopDatabasetest start
Databasebuild
Schemaintegration
Test check build...
Database integra,on tests
apply from: "$rootDir/gradle/databaseSetup.gradle"
integrationTest.dependsOn startAndPrepareDatabaseintegrationTest.finalizedBy stopDatabase
check.dependsOn integrationTest
stopDatabasetest start
Databasebuild
Schemaintegration
Test check build...
Database integra,on tests
apply from: "$rootDir/gradle/databaseSetup.gradle"
integrationTest.dependsOn startAndPrepareDatabaseintegrationTest.finalizedBy stopDatabase
check.dependsOn integrationTest
stopDatabasetest start
Databasebuild
Schemaintegration
Test check build...
Separate complex setup logic into script plugin
Database integra,on tests
apply from: "$rootDir/gradle/databaseSetup.gradle"
integrationTest.dependsOn startAndPrepareDatabaseintegrationTest.finalizedBy stopDatabase
check.dependsOn integrationTest
stopDatabasetest start
Databasebuild
Schemaintegration
Test check build...
Separate complex setup logic into script plugin
Integrate tasks into build lifecycle
Picking the “right” code coverage tool
Picking the “right” code coverage tool
Cobertura Offline bytecode instrumenta,on
Picking the “right” code coverage tool
Cobertura Offline bytecode instrumenta,on
Offline bytecode instrumenta,on
Picking the “right” code coverage tool
Cobertura Offline bytecode instrumenta,on
Source code instrumenta,on
Offline bytecode instrumenta,on
Picking the “right” code coverage tool
Cobertura Offline bytecode instrumenta,on
Source code instrumenta,on
Offline bytecode instrumenta,on
On-‐the-‐fly bytecode instrumenta,on
On-‐the-‐fly bytecode instrumentaCon
No modificaCon to source or bytecode
Code coverage with JaCoCo
apply plugin: "jacoco"
task jacocoIntegrationTestReport(type: JacocoReport) { sourceSets sourceSets.main executionData integTest}
Code coverage with JaCoCo
apply plugin: "jacoco"
task jacocoIntegrationTestReport(type: JacocoReport) { sourceSets sourceSets.main executionData integTest}
Apply JaCoCo plugin
Code coverage with JaCoCo
apply plugin: "jacoco"
task jacocoIntegrationTestReport(type: JacocoReport) { sourceSets sourceSets.main executionData integTest}
Apply JaCoCo plugin
Also report code coverage for integra,on tests
Genera,ng coverage reports
test integrationTest
... ... .exec
Genera,ng coverage reports
test integrationTest
... ... .exec
jacocoTestReport
jacocoIntegrationTestReport
.exec .html
Genera,ng coverage reports
test integrationTest
... ... .exec
jacocoTestReport
jacocoIntegrationTestReport
.exec .html
.html
Commit stage: Code analysis
PublishBinaries
Commit stage
CompileUnit Tests
IntegrationTests
Code Analysis
AssembleDistribution
Perform code health check
Fail build for low quality
Record progress over ,me
Sta,c code analysis tools
Checkstyle
FindBugs
apply plugin: 'pmd'
pmd { ignoreFailures = true}
tasks.withType(Pmd) { reports { xml.enabled = false html.enabled = true }}
apply plugin: 'jdepend’
jdepend { toolVersion = '2.9.1' ignoreFailures = true}
gradlew check
Measure quality over ,me with Sonar
Sonardatabase
Gradle
Sonar Runner
root
model
repo.
web
analyzes
publishes reads / writes
Gradle project
Applying the Sonar Runner plugin
apply plugin: "sonar-runner"
sonarRunner { sonarProperties { property "sonar.host.url", "http://my.server.com" property "sonar.jdbc.url", "jdbc:mysql://my.server.com/sonar" property "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver" property "sonar.jdbc.username", "Fred Flintstone" property "sonar.jdbc.password", "very clever" }}
subprojects { sonarRunner { sonarProperties { property "sonar.sourceEncoding", "UTF-8" } }}
gradlew sonarRunner
Commit stage: Assemble distribu,on
Exclude env. configura,on
Include build informa,on
Choose versioning strategy
PublishBinaries
Commit stage
CompileUnit Tests
IntegrationTests
Code Analysis
AssembleDistribution
Versioning strategy
1.0-‐SNAPSHOT 1.0
during development when released
…the Maven way
Change version with Maven Release plugin
Versioning strategy
1.0.134 1.0.134
during development when released
…the Con,nuous Delivery way
1.0.134Jenkins build numberProject version number
Versioning strategy…implemented with Gradle
allprojects { apply from: "$rootDir/gradle/versioning.gradle"}
todo
model
repository
build.gradle
settings.gradle
web
gradle
versioning.gradle Contains version implementa,on
Versioning strategy…implemented with Gradle
ext.buildTimestamp = new Date().format('yyyy-MM-dd HH:mm:ss')
version = new ProjectVersion(1, 0, System.env.SOURCE_BUILD_NUMBER)
class ProjectVersion { Integer major Integer minor String build
ProjectVersion(Integer major, Integer minor, String build) { this.major = major this.minor = minor this.build = build }
@Override String toString() { String fullVersion = "$major.$minor" if(build) { fullVersion += ".$build” } fullVersion }}
Versioning strategy…implemented with Gradle
ext.buildTimestamp = new Date().format('yyyy-MM-dd HH:mm:ss')
version = new ProjectVersion(1, 0, System.env.SOURCE_BUILD_NUMBER)
class ProjectVersion { Integer major Integer minor String build
ProjectVersion(Integer major, Integer minor, String build) { this.major = major this.minor = minor this.build = build }
@Override String toString() { String fullVersion = "$major.$minor" if(build) { fullVersion += ".$build” } fullVersion }}
Jenkins Build Number
Versioning strategy…implemented with Gradle
ext.buildTimestamp = new Date().format('yyyy-MM-dd HH:mm:ss')
version = new ProjectVersion(1, 0, System.env.SOURCE_BUILD_NUMBER)
class ProjectVersion { Integer major Integer minor String build
ProjectVersion(Integer major, Integer minor, String build) { this.major = major this.minor = minor this.build = build }
@Override String toString() { String fullVersion = "$major.$minor" if(build) { fullVersion += ".$build” } fullVersion }}
Jenkins Build Number
Builds version String representa,on
Packaging the deployable ar,fact
project(':web') { apply plugin: 'war'
task createBuildInfoFile << { def buildInfoFile = new File("$buildDir/build-info.properties") Properties props = new Properties() props.setProperty('version', project.version.toString()) props.setProperty('timestamp', project.buildTimestamp) props.store(buildInfoFile.newWriter(), null) } war { dependsOn createBuildInfoFile baseName = 'todo' from(buildDir) { include 'build-info.properties' into('WEB-INF/classes') } }}
Packaging the deployable ar,fact
project(':web') { apply plugin: 'war'
task createBuildInfoFile << { def buildInfoFile = new File("$buildDir/build-info.properties") Properties props = new Properties() props.setProperty('version', project.version.toString()) props.setProperty('timestamp', project.buildTimestamp) props.store(buildInfoFile.newWriter(), null) } war { dependsOn createBuildInfoFile baseName = 'todo' from(buildDir) { include 'build-info.properties' into('WEB-INF/classes') } }}
Creates file containing build informa,on
Packaging the deployable ar,fact
project(':web') { apply plugin: 'war'
task createBuildInfoFile << { def buildInfoFile = new File("$buildDir/build-info.properties") Properties props = new Properties() props.setProperty('version', project.version.toString()) props.setProperty('timestamp', project.buildTimestamp) props.store(buildInfoFile.newWriter(), null) } war { dependsOn createBuildInfoFile baseName = 'todo' from(buildDir) { include 'build-info.properties' into('WEB-INF/classes') } }}
Creates file containing build informa,on
Include build info fileInto WAR distribu,on
Packaging the deployable ar,fact
project(':web') { apply plugin: 'war'
task createBuildInfoFile << { def buildInfoFile = new File("$buildDir/build-info.properties") Properties props = new Properties() props.setProperty('version', project.version.toString()) props.setProperty('timestamp', project.buildTimestamp) props.store(buildInfoFile.newWriter(), null) } war { dependsOn createBuildInfoFile baseName = 'todo' from(buildDir) { include 'build-info.properties' into('WEB-INF/classes') } }}
Creates file containing build informa,on
Include build info fileInto WAR distribu,on
gradlew assemble
Commit stage: Publish binaries
Version ar,fact(s)
Use binary repository
Publish once, then reuse
PublishBinaries
Commit stage
CompileUnit Tests
IntegrationTests
Code Analysis
AssembleDistribution
Publishing the deployable ar,fact
1.0.34
1.0.32 1.0.33 1.0.34
Defining build configura,onbinaryRepository { url = 'http://mycompany.bin.repo:8081/artifactory' username = 'admin' password = 'password' name = 'libs-release-local'}
environments { test { server { hostname = 'mycompany.test' port = 8099 context = 'todo' username = 'manager' password = 'manager' } } uat { server { hostname = 'mycompany.uat' port = 8199 context = 'todo' username = 'manager' password = 'manager' } }
...}
Defining build configura,onbinaryRepository { url = 'http://mycompany.bin.repo:8081/artifactory' username = 'admin' password = 'password' name = 'libs-release-local'}
environments { test { server { hostname = 'mycompany.test' port = 8099 context = 'todo' username = 'manager' password = 'manager' } } uat { server { hostname = 'mycompany.uat' port = 8199 context = 'todo' username = 'manager' password = 'manager' } }
...}
Common configura,on
Defining build configura,onbinaryRepository { url = 'http://mycompany.bin.repo:8081/artifactory' username = 'admin' password = 'password' name = 'libs-release-local'}
environments { test { server { hostname = 'mycompany.test' port = 8099 context = 'todo' username = 'manager' password = 'manager' } } uat { server { hostname = 'mycompany.uat' port = 8199 context = 'todo' username = 'manager' password = 'manager' } }
...}
Environment-‐specificconfigura,on
Common configura,on
Defining build configura,onbinaryRepository { url = 'http://mycompany.bin.repo:8081/artifactory' username = 'admin' password = 'password' name = 'libs-release-local'}
environments { test { server { hostname = 'mycompany.test' port = 8099 context = 'todo' username = 'manager' password = 'manager' } } uat { server { hostname = 'mycompany.uat' port = 8199 context = 'todo' username = 'manager' password = 'manager' } }
...}
Environment-‐specificconfigura,on
Common configura,on Read creden,als
from gradle.proper,es
Read creden,als from gradle.proper,es
Read creden,als from gradle.proper,es
Reading build configura,on
def env = project.hasProperty('env') ? project.getProperty('env') : 'test'logger.quiet "Loading configuration for environment '$env’."
def configFile = file("$rootDir/gradle/config/buildConfig.groovy")def parsedConfig = new ConfigSlurper(env).parse(configFile.toURL()) allprojects { ext.config = parsedConfig}
Initializationphase
Congurationphase
Executionphase
Reading build configura,on
def env = project.hasProperty('env') ? project.getProperty('env') : 'test'logger.quiet "Loading configuration for environment '$env’."
def configFile = file("$rootDir/gradle/config/buildConfig.groovy")def parsedConfig = new ConfigSlurper(env).parse(configFile.toURL()) allprojects { ext.config = parsedConfig}
Initializationphase
Congurationphase
Executionphase
Assign configura,on to extra property
Reading build configura,on
def env = project.hasProperty('env') ? project.getProperty('env') : 'test'logger.quiet "Loading configuration for environment '$env’."
def configFile = file("$rootDir/gradle/config/buildConfig.groovy")def parsedConfig = new ConfigSlurper(env).parse(configFile.toURL()) allprojects { ext.config = parsedConfig}
Initializationphase
Congurationphase
Executionphase
Assign configura,on to extra property
gradlew –Penv=uat ...
Using the Maven Publishing plugin
apply plugin: 'maven-publish'
ext.fullRepoUrl = "$config.binaryRepository.url/$config.binaryRepository.name”
publishing { publications { webApp(MavenPublication) { from components.web } }
repositories { maven { url fullRepoUrl credentials { username = config.binaryRepository.username password = config.binaryRepository.password } } }}
Using the Maven Publishing plugin
apply plugin: 'maven-publish'
ext.fullRepoUrl = "$config.binaryRepository.url/$config.binaryRepository.name”
publishing { publications { webApp(MavenPublication) { from components.web } }
repositories { maven { url fullRepoUrl credentials { username = config.binaryRepository.username password = config.binaryRepository.password } } }}
Build repository URLfrom configura,on
Using the Maven Publishing plugin
apply plugin: 'maven-publish'
ext.fullRepoUrl = "$config.binaryRepository.url/$config.binaryRepository.name”
publishing { publications { webApp(MavenPublication) { from components.web } }
repositories { maven { url fullRepoUrl credentials { username = config.binaryRepository.username password = config.binaryRepository.password } } }}
Build repository URLfrom configura,on
Assign publica,on name and component
Using the Maven Publishing plugin
apply plugin: 'maven-publish'
ext.fullRepoUrl = "$config.binaryRepository.url/$config.binaryRepository.name”
publishing { publications { webApp(MavenPublication) { from components.web } }
repositories { maven { url fullRepoUrl credentials { username = config.binaryRepository.username password = config.binaryRepository.password } } }}
Build repository URLfrom configura,on
gradlew publish
Assign publica,on name and component
Acceptance stage: Retrieve binaries
Request versioned ar,fact
Store in temp. directory
Acceptance stage
DeployBinaries
Functional Tests
RetrieveBinaries
Downloading the deployable ar,fact
1.0.34
1.0.32 1.0.33 1.0.34
1.0.34
Test
UAT
Prod
Task for downloading ar,fact
repositories { maven { url fullRepoUrl }}
configurations { war}
dependencies { war "$project.group:$project.name:$project.version"}
task downloadBinaryArchive(type: Copy) { from configurations.war into "$buildDir/download" }
Task for downloading ar,fact
repositories { maven { url fullRepoUrl }}
configurations { war}
dependencies { war "$project.group:$project.name:$project.version"}
task downloadBinaryArchive(type: Copy) { from configurations.war into "$buildDir/download" }
Target loca,on fordownloaded ar,fact
gradlew downloadBinaryArchive
Acceptance stage: Deploy binaries
Deployment on request
Make it a reliable process
Acceptance stage
DeployBinaries
Functional Tests
RetrieveBinaries
Use process for all envs
Deploying to mul,ple environments
Test
UAT
Prod
–Penv=prod
–Penv=uat
–Penv=test
Deployment with the Cargo plugin
cargoDeployRemote.dependsOn downloadBinaryArchive, cargoUndeployRemote
cargoUndeployRemote { onlyIf appContextStatus} cargo { containerId = 'tomcat7x' port = config.server.port
deployable { file = downloadedArtifact context = config.server.context }
remote { hostname = config.server.hostname username = config.server.username password = config.server.password }}
Deployment with the Cargo plugin
cargoDeployRemote.dependsOn downloadBinaryArchive, cargoUndeployRemote
cargoUndeployRemote { onlyIf appContextStatus} cargo { containerId = 'tomcat7x' port = config.server.port
deployable { file = downloadedArtifact context = config.server.context }
remote { hostname = config.server.hostname username = config.server.username password = config.server.password }}
Download ar,fact from binary repository and undeploy exis,ng one
Deployment with the Cargo plugin
cargoDeployRemote.dependsOn downloadBinaryArchive, cargoUndeployRemote
cargoUndeployRemote { onlyIf appContextStatus} cargo { containerId = 'tomcat7x' port = config.server.port
deployable { file = downloadedArtifact context = config.server.context }
remote { hostname = config.server.hostname username = config.server.username password = config.server.password }}
Download ar,fact from binary repository and undeploy exis,ng one
Only undeploy ifURL context exists
Deployment with the Cargo plugin
cargoDeployRemote.dependsOn downloadBinaryArchive, cargoUndeployRemote
cargoUndeployRemote { onlyIf appContextStatus} cargo { containerId = 'tomcat7x' port = config.server.port
deployable { file = downloadedArtifact context = config.server.context }
remote { hostname = config.server.hostname username = config.server.username password = config.server.password }}
Download ar,fact from binary repository and undeploy exis,ng one
Only undeploy ifURL context exists
Use environment-‐specific configura,on
Deployment with the Cargo plugin
cargoDeployRemote.dependsOn downloadBinaryArchive, cargoUndeployRemote
cargoUndeployRemote { onlyIf appContextStatus} cargo { containerId = 'tomcat7x' port = config.server.port
deployable { file = downloadedArtifact context = config.server.context }
remote { hostname = config.server.hostname username = config.server.username password = config.server.password }}
Download ar,fact from binary repository and undeploy exis,ng one
Only undeploy ifURL context exists
Use environment-‐specific configura,on
gradlew –Penv=uat cargoDeploy
Acceptance stage: Func,onal tests
Test all UI permuta,ons
Test important use cases
Acceptance stage
DeployBinaries
Functional Tests
RetrieveBinaries
Run against different envs
In-‐container func,onal tests
functionalJettyStop
functionalTestClasses
functionalJettyRun
functionalTest check... ...
In-‐container func,onal tests
functionalJettyStop
functionalTestClasses
functionalJettyRun
functionalTest check... ...
task functionalTest(type: Test) { ...}
task functionalJettyRun(type: org.gradle.api.plugins.jetty.JettyRun) { httpPort = functionalJettyHttpPort stopPort = functionalJettyStopPort stopKey = functionalJettyStopKey contextPath = functionalJettyContextPath daemon = true}
task functionalJettyStop(type: org.gradle.api.plugins.jetty.JettyStop) { stopPort = functionalJettyStopPort stopKey = functionalJettyStopKey}
functionalTest.dependsOn functionalJettyRunfunctionalTest.finalizedBy functionalJettyStoptask inContainerFunctionalTest(dependsOn: functionalJettyStop)
In-‐container func,onal tests
functionalJettyStop
functionalTestClasses
functionalJettyRun
functionalTest check... ...
task functionalTest(type: Test) { ...}
task functionalJettyRun(type: org.gradle.api.plugins.jetty.JettyRun) { httpPort = functionalJettyHttpPort stopPort = functionalJettyStopPort stopKey = functionalJettyStopKey contextPath = functionalJettyContextPath daemon = true}
task functionalJettyStop(type: org.gradle.api.plugins.jetty.JettyStop) { stopPort = functionalJettyStopPort stopKey = functionalJettyStopKey}
functionalTest.dependsOn functionalJettyRunfunctionalTest.finalizedBy functionalJettyStoptask inContainerFunctionalTest(dependsOn: functionalJettyStop)
Func,onal test task
In-‐container func,onal tests
functionalJettyStop
functionalTestClasses
functionalJettyRun
functionalTest check... ...
task functionalTest(type: Test) { ...}
task functionalJettyRun(type: org.gradle.api.plugins.jetty.JettyRun) { httpPort = functionalJettyHttpPort stopPort = functionalJettyStopPort stopKey = functionalJettyStopKey contextPath = functionalJettyContextPath daemon = true}
task functionalJettyStop(type: org.gradle.api.plugins.jetty.JettyStop) { stopPort = functionalJettyStopPort stopKey = functionalJettyStopKey}
functionalTest.dependsOn functionalJettyRunfunctionalTest.finalizedBy functionalJettyStoptask inContainerFunctionalTest(dependsOn: functionalJettyStop)
Func,onal test task
Custom Jeiy Run task
In-‐container func,onal tests
functionalJettyStop
functionalTestClasses
functionalJettyRun
functionalTest check... ...
task functionalTest(type: Test) { ...}
task functionalJettyRun(type: org.gradle.api.plugins.jetty.JettyRun) { httpPort = functionalJettyHttpPort stopPort = functionalJettyStopPort stopKey = functionalJettyStopKey contextPath = functionalJettyContextPath daemon = true}
task functionalJettyStop(type: org.gradle.api.plugins.jetty.JettyStop) { stopPort = functionalJettyStopPort stopKey = functionalJettyStopKey}
functionalTest.dependsOn functionalJettyRunfunctionalTest.finalizedBy functionalJettyStoptask inContainerFunctionalTest(dependsOn: functionalJettyStop)
Func,onal test task
Custom Jeiy Run task
Custom Jeiy Stop task
Execu,ng remote func,onal tests
ext { functionalTestReportDir = file("$testReportDir/functional") functionalTestResultsDir = file("$testResultsDir/functional") functionalCommonSystemProperties = ['geb.env': 'firefox', 'geb.build.reportsDir': reporting.file("$name/geb")]}
task remoteFunctionalTest(type: Test) { testClassesDir = sourceSets.functionalTest.output.classesDir classpath = sourceSets.functionalTest.runtimeClasspath testReportDir = functionalTestReportDir testResultsDir = functionalTestResultsDir systemProperties functionalCommonSystemProperties systemProperty 'geb.build.baseUrl', "http://$config.server.hostname:$config.server.port/$config.server.context/"}
Execu,ng remote func,onal tests
ext { functionalTestReportDir = file("$testReportDir/functional") functionalTestResultsDir = file("$testResultsDir/functional") functionalCommonSystemProperties = ['geb.env': 'firefox', 'geb.build.reportsDir': reporting.file("$name/geb")]}
task remoteFunctionalTest(type: Test) { testClassesDir = sourceSets.functionalTest.output.classesDir classpath = sourceSets.functionalTest.runtimeClasspath testReportDir = functionalTestReportDir testResultsDir = functionalTestResultsDir systemProperties functionalCommonSystemProperties systemProperty 'geb.build.baseUrl', "http://$config.server.hostname:$config.server.port/$config.server.context/"}
Reuse setup proper,es
Execu,ng remote func,onal tests
ext { functionalTestReportDir = file("$testReportDir/functional") functionalTestResultsDir = file("$testResultsDir/functional") functionalCommonSystemProperties = ['geb.env': 'firefox', 'geb.build.reportsDir': reporting.file("$name/geb")]}
task remoteFunctionalTest(type: Test) { testClassesDir = sourceSets.functionalTest.output.classesDir classpath = sourceSets.functionalTest.runtimeClasspath testReportDir = functionalTestReportDir testResultsDir = functionalTestResultsDir systemProperties functionalCommonSystemProperties systemProperty 'geb.build.baseUrl', "http://$config.server.hostname:$config.server.port/$config.server.context/"}
Build URL from env. configura,on
Reuse setup proper,es
Execu,ng remote func,onal tests
ext { functionalTestReportDir = file("$testReportDir/functional") functionalTestResultsDir = file("$testResultsDir/functional") functionalCommonSystemProperties = ['geb.env': 'firefox', 'geb.build.reportsDir': reporting.file("$name/geb")]}
task remoteFunctionalTest(type: Test) { testClassesDir = sourceSets.functionalTest.output.classesDir classpath = sourceSets.functionalTest.runtimeClasspath testReportDir = functionalTestReportDir testResultsDir = functionalTestResultsDir systemProperties functionalCommonSystemProperties systemProperty 'geb.build.baseUrl', "http://$config.server.hostname:$config.server.port/$config.server.context/"}
gradlew –Penv=test remoteFunctionalTest
Build URL from env. configura,on
Reuse setup proper,es
Going further: Capacity tes,ng
buildscript { repositories { mavenCentral() }
dependencies { classpath "com.github.kulya:jmeter-gradle-plugin:1.3.1-2.9" }}
apply plugin: com.github.kulya.gradle.plugins.jmeter.JmeterPlugin ext.loadTestResources = "$projectDir/src/loadTest/resources" jmeterRun.configure { jmeterTestFiles = [file("$loadTestResources/todo-test-plan.jmx")] jmeterPropertyFile = file("$loadTestResources/jmeter.properties") jmeterUserProperties = ["hostname=${config.server.hostname}, "port=${config.server.port}", "context=${config.server.context}"] logging.captureStandardError LogLevel.INFO}
gradlew –Penv=uat jmeterRun
Let’s bring Jenkins into play!
Test
UAT
Prod
DeploymentAcceptance Tests
Publish
Download
Trigger Build
Pull Source Code
Model pipeline as series of jobs
Model pipeline as series of jobs
Triggered job when change to SCM is detected
Ini,al Jenkins Build Job
Build Name Seier Plugin
JaCoCo Plugin
Parameterized Trigger Plugin
Gradle Plugin
Always use the Wrapper!
Gradle Plugin
Always use the Wrapper!
Gradle Plugin
Run clean task to remove exis,ng ar,facts
Build Name Seier Plugin
Clearly iden,fy a buildthrough an expressive build name
Build Name Seier Plugin
Use Jenkins environment variable
Clearly iden,fy a buildthrough an expressive build name
Build Name Seier Plugin
Parameterized Trigger Plugin
Next job to trigger if build is stable
Parameterized Trigger Plugin
Next job to trigger if build is stable
Defines build numberparameter provided to subsequent jobs
Parameterized Trigger Plugin
Clone Workspace SCM Plugin
Archive all files
Clone Workspace SCM Plugin
Archive all files
Only archive if build was successful
Clone Workspace SCM Plugin
JaCoCo Plugin
Point to separated test results
JaCoCo Plugin
Point to separated test results
JaCoCo Plugin
Point to JaCoCo files as well as source and class files
Point to separated test results
Fail build of quality gate criteria are not met
JaCoCo Plugin
Point to JaCoCo files as well as source and class files
Clone Workspace SCM Plugin Build Name Seier Plugin
Reuse ini,al workspace
Clone Workspace SCM Plugin Build Name Seier Plugin
Reuse ini,al workspace
Reuse ini,al build number
Clone Workspace SCM Plugin Build Name Seier Plugin
Build Pipeline Plugin
Define the target environment
Build Pipeline Plugin
Define the target environment
Downstream job that requires manual execu,on
Build Pipeline Plugin
Build Pipeline Plugin
Visualiza,on of chained pipeline jobs
> gradle qa:askQuestions
BUILD SUCCESSFUL
Total time: 300 secs