I wrote a Jenkins file to CI a Gradle Java application with Jenkins. When I push it to Github, the Jenkins pipeline job on AWS runs, and if the test is successful, I use it like deploying to Tomcat on AWS as well.
Declarative Pipeline As it is, Jenkinsfile
node {
....
}
It was written as, but if you look at Jenkins official website This is the Scripted Pipelines notation, From version 2.5 migration of Pipeline Plugin
pipeline {
....
}
A notation called Declarative Pipeline was introduced to write like It was simpler and easier to write, so I rewrote it. It's certainly neat (especially the last email, deploying), It's nice to be able to mix traditional Scripted Pipelines in case of emergency.
Jenkins Not included in the Suggested Plugin at the time of initial setup
Jenkinsfile Run a Jenkins pipeline job on a webhook from Github. For pipeline jobs, use Jenkinsfile on Github. The job flow is as follows. Deploy only if static code analysis and testing are successful.
Jenkinsfile
pipeline {
agent any
//Define constants and variables
environment {
reportDir = 'build/reports'
javaDir = 'src/main/java'
resourcesDir = 'src/main/resources'
testReportDir = 'build/test-results/test'
jacocoReportDir = 'build/jacoco'
javadocDir = 'build/docs/javadoc'
libsDir = 'build/libs'
appName = 'SampleApp'
appVersion = '1.0.0'
}
//Define one or more stages in the stages block
stages {
stage('Advance preparation') {
//The actual processing is defined in the steps block
steps {
deleteDir()
//Check out the Github project that triggered this Job
checkout scm
//Jenkinsfile and build for investigating the cause of job failure.gradle save first
archiveArtifacts "Jenkinsfile"
archiveArtifacts "build.gradle"
//You can also use the traditional Scripted Pipelines notation with the script block
script {
//Grant execute permission so that you will not get angry with Permission denied
if(isUnix()) {
sh 'chmod +x gradlew'
}
}
gradlew 'clean'
}
}
stage('compile') {
steps {
gradlew 'classes testClasses'
}
//The post block can define the process to be executed after the steps block
post {
//The always block is always executed regardless of whether the steps block processing fails or succeeds.
always {
//If you execute it when JavaDoc is generated, JavaDoc warnings will also be included.
//Java compile-time warnings are collected immediately after compile
step([
//The class specification when executing the plugin does not have to be a fully qualified name.
$class: 'WarningsPublisher',
//ConsoleParsers, if you want to collect alerts from the console when running a job
// pmd.When collecting from a file such as xml, specify parser Configurations.
//In the case of parser Configurations, in addition to parser Name, pattern(Path of the file to be aggregated)Must also be specified
//Use the parser name defined in the property file below.
// https://github.com/jenkinsci/warnings-plugin/blob/master/src/main/resources/hudson/plugins/warnings/parser/Messages.properties
consoleParsers: [
[parserName: 'Java Compiler (javac)'],
],
canComputeNew: false,
canResolveRelativesPaths: false,
usePreviousBuildAsReference: true
])
}
}
}
stage('Static code analysis') {
steps {
//Use parallel method for parallel processing
parallel(
'Static code analysis' : {
gradlew 'check -x test'
//You can specify the current directory with the dir method
dir(reportDir) {
step([
$class: 'CheckStylePublisher',
pattern: "checkstyle/*.xml"
])
step([
$class: 'FindBugsPublisher',
pattern: "findbugs/*.xml"
])
step([
$class: 'PmdPublisher',
pattern: "pmd/*.xml"
])
step([
$class: 'DryPublisher',
pattern: "cpd/*.xml"
])
archiveArtifacts "checkstyle/*.xml"
archiveArtifacts "findbugs/*.xml"
archiveArtifacts "pmd/*.xml"
archiveArtifacts "cpd/*.xml"
}
},
'Step count': {
//Report creation
//If you specify outputFile and outputFormat, an Excel file will also be created.
stepcounter outputFile: 'stepcount.xls', outputFormat: 'excel', settings: [
[key:'Java', filePattern: "${javaDir}/**/*.java"],
[key:'SQL', filePattern: "${resourcesDir}/**/*.sql"],
[key:'HTML', filePattern: "${resourcesDir}/**/*.html"],
[key:'JS', filePattern: "${resourcesDir}/**/*.js"],
[key:'CSS', filePattern: "${resourcesDir}/**/*.css"]
]
//For the time being, save the Excel file as a deliverable
archiveArtifacts "stepcount.xls"
},
'Task scan': {
step([
$class: 'TasksPublisher',
pattern: './**',
//Is it case sensitive when searching for aggregation targets?
ignoreCase: true,
//Character strings to be aggregated can be specified for each priority
//If you specify more than one, specify a comma-separated character string.
high: 'System.out.System.err',
normal: 'TODO,FIXME,XXX',
])
},
'JavaDoc': {
gradlew 'javadoc -x classes'
step([
$class: 'JavadocArchiver',
//Javadoc index.Specify the path of the folder where the html is located
javadocDir: "${javadocDir}",
keepAll: true
])
}
)
}
post {
always {
//Collect Javadoc warnings
step([
$class: 'WarningsPublisher',
consoleParsers: [
[parserName: 'JavaDoc Tool']
],
canComputeNew: false,
canResolveRelativesPaths: false,
usePreviousBuildAsReference: true
])
}
}
}
stage('test') {
steps {
gradlew 'test jacocoTestReport -x classes -x testClasses'
junit "${testReportDir}/*.xml"
archiveArtifacts "${testReportDir}/*.xml"
//Generate coverage report (excluding test classes)
step([
$class: 'JacocoPublisher',
execPattern: "${jacocoReportDir}/*.exec",
exclusionPattern: '**/*Test.class'
])
}
}
stage('Deploy') {
//You can specify the conditions for executing stage in the when block
when {
//Do not deploy on static code analysis and test failure
expression {currentBuild.currentResult == 'SUCCESS'}
}
steps {
gradlew 'jar'
archiveArtifacts "${libsDir}/${appName}-${appVersion}.jar"
gradlew 'war'
archiveArtifacts "${libsDir}/${appName}-${appVersion}.war"
deploy warDir: libsDir, appName: appName, appVersion: appVersion
}
}
}
//If you define a post block at the same level as the stages block
//It is possible to define the processing after all stage processing is completed.
post {
always {
//Finally delete the contents of the workspace
deleteDir()
}
//Send an email to yourself except when you are successful in a row
//When the result changes from the last time
changed {
sendMail("${currentBuild.previousBuild.result} => ${currentBuild.currentResult}")
}
//When it fails
failure {
sendMail(currentBuild.currentResult)
}
//Instability (mainly when the test fails)
unstable {
sendMail(currentBuild.currentResult)
}
}
}
//Execute the Gradlew command
def gradlew(command) {
if(isUnix()) {
sh "./gradlew ${command} --stacktrace"
} else {
bat "./gradlew.bat ${command} --stacktrace"
}
}
//Deploy
// args.warDir war storage directory
// args.appName app name
// args.appVersion App version
def deploy(Map args) {
//Private key path * Since the file is transferred to the Tomcat server, it is necessary to store the private key somewhere on the Jenkins server in advance.
def keyDir = '/var/lib/jenkins/.ssh/xxx'
//Tomcat server address and username
def webServerAddress = 'ecX-XX-XXX-X-X.xx-xxxx-x.xxxxxxxx'
def webServerUser = 'hoge-user'
def webServer = "${webServerUser}@${webServerAddress}"
def srcWar = "${args.appName}-${args.appVersion}.war"
def destWar = "${args.appName}.war"
//Transfer files and place war in tomcat webapps
sh "sudo -S scp -i ${keyDir} ./${args.warDir}/${srcWar} ${webServer}:/home/ec2-user"
sh "sudo -S ssh -i ${keyDir} ${webServer} \"sudo cp /home/ec2-user/${srcWar} /usr/share/tomcat8/webapps/${destWar}\""
}
//Send an email to Gmail
def sendMail(result) {
mail to: "[email protected]",
subject: "${env.JOB_NAME} #${env.BUILD_NUMBER} [${result}]",
body: "Build URL: ${env.BUILD_URL}.\n\n"
}
build.gradle Jenkins itself just executes Gradle commands and generates a report based on the output result, so It is necessary that the following processing can be executed in build.gradle of Gradle's Java application. Also, create a Gradle wrapper so that you don't have to install Gradle in Jenkins.
For example,
build.gradle
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'checkstyle'
apply plugin: 'findbugs'
apply plugin: 'pmd'
apply plugin: 'jacoco'
ext {
appVersion = '1.0.0'
appName = 'SampleApp'
javaVersion = 1.8
defaultEncoding = 'UTF-8'
}
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
tasks.withType(AbstractCompile)*.options*.encoding = defaultEncoding
tasks.withType(GroovyCompile)*.groovyOptions*.encoding = defaultEncoding
mainClassName = 'jp.takumon.sapmleapp.App'
repositories {
mavenCentral()
}
dependencies {
//List dependent libraries
compile group: 'junit', name: 'junit', version: '4.12'
}
jar {
baseName = appName
version = appVersion
}
war {
baseName = appName
version = appVersion
}
checkstyle {
//Continue subsequent processing even if it fails
ignoreFailures = true
sourceSets = [sourceSets.main]
toolVersion = '7.6.1'
}
findbugs {
//Continue subsequent processing even if it fails
ignoreFailures = true
sourceSets = [sourceSets.main]
toolVersion = "3.0.1"
}
pmd {
//Continue subsequent processing even if it fails
ignoreFailures = true
sourceSets = [sourceSets.main]
}
tasks.withType(Pmd) {
reports {
xml.enabled = true
}
}
//Added CPD (Duplicate Code Check Processing) to Check Task
check.doLast {
File outputDir = new File("$reportsDir/cpd/")
outputDir.mkdirs()
ant.taskdef(
name: 'cpd',
classname: 'net.sourceforge.pmd.cpd.CPDTask',
classpath: configurations.pmd.asPath)
ant.cpd(
minimumTokenCount: '100',
format: 'xml',
encoding: defaultEncoding,
outputFile: new File(outputDir, 'cpd.xml')
) {
fileset(dir: "src/main/java") {
include(name: '**/*.java')
}
}
}
javadoc {
failOnError = false
//At your favorite level
options.memberLevel = JavadocMemberLevel.PRIVATE
}
test {
//Continue subsequent processing even if it fails
ignoreFailures = true
reports {
junitXml.enabled = true
}
}
jacoco {
toolVersion = '0.7.5.201505241946'
}
jacocoTestReport {
reports {
xml.enabled = true
}
//Exclude test classes from coverage report
afterEvaluate {
classDirectories = files(classDirectories.files.collect {
fileTree(dir: it, exclude: ['**/*Test.class'])
})
}
}
task wrapper (type: Wrapper) {
gradleVersion = '3.4.1'
}
that's all.
Recommended Posts