void setBuildStatus(String context, String message, String state, String url) {
if (state == 'PENDING') {
backref = "${env.RUN_DISPLAY_URL}"
} else {
backref = url
}
step([
$class: "GitHubCommitStatusSetter",
reposSource: [$class: "ManuallyEnteredRepositorySource", url: "${env.GIT_URL}"],
contextSource: [$class: "ManuallyEnteredCommitContextSource", context: "ci/jenkins/${context}"],
errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]],
statusBackrefSource: [$class: "ManuallyEnteredBackrefSource", backref: backref],
statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ]
]);
title = 'Build Check'
if (state == 'PENDING') {
publishChecks title: title,
name: context,
status: 'IN_PROGRESS',
detailsURL: url
} else if (state != 'SUCCESS') {
publishChecks title: title,
name: context,
status: 'COMPLETED',
conclusion: 'FAILURE',
detailsURL: url
} else {
publishChecks title: title,
name: context,
detailsURL: url
}
}
void notifySlack(String message, String color) {
slackSend (channel: '#my_channel', color: color, message: message + ": Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
pipeline {
agent {
dockerfile { filename './build/Dockerfile' }
}
environment {
HOME = "${env.WORKSPACE}"
YARN_CACHE_FOLDER = "${env.HOME}/.yarn-cache"
isDevelop = "${env.BRANCH_NAME ==~ /(develop)/}"
isMainBranch = "${env.BRANCH_NAME ==~ /(master|develop|release.*)/}"
isPRMergeBuild = "${env.BRANCH_NAME ==~ /^PR-\d+$/}"
isSonarCoverage= "${env.isDevelop}"
npmCommand = "yarn"
buildUrl ="${env.BUILD_URL}"
storybookUrl = "https://storybook-url"
sonarQubeUrl = "http://sonar-url/dashboard?id=test"
}
options {
buildDiscarder(logRotator(artifactDaysToKeepStr: '1', artifactNumToKeepStr: '10', daysToKeepStr: '3',numToKeepStr: "10"))
timestamps()
timeout(time: 30, unit: 'MINUTES')
}
stages {
stage('Checkout') {
steps {
echo "Branch: ${env.BRANCH_NAME}, PrBranch: ${env.CHANGE_BRANCH}"
sh "which node; npm --version; node --version; yarn -version"
sh 'which java; java -version'
sh "printenv"
}
}
stage('Install dependencies') {
steps {
echo 'Installing dependencies...'
sh "yarn config set registry http://internal/content/groups/npm/"
sh "yarn install"
}
}
stage('Lint') {
environment {
context="lint"
}
steps {
setBuildStatus(context, "${context} Progressing...", "PENDING", buildUrl);
script {
try {
if (isDevelop == 'true') {
sh "${npmCommand} run lint -- -f json -o eslint.json"
}
sh "${npmCommand} run lint -- -f checkstyle -o eslint.xml"
} catch (e) {
sh "${npmCommand} run lint"
}
}
}
post {
always {
recordIssues enabledForFailure: true, aggregatingResults: true, tool: checkStyle(pattern: 'eslint.xml')
}
success {
setBuildStatus("${context}", "${context} Pass", "SUCCESS", "${buildUrl}/checkstyle");
}
failure {
setBuildStatus("${context}", "${context} Failed", "FAILURE", "${buildUrl}/checkstyle");
}
}
}
stage('Test') {
parallel {
stage('Unit Test') {
environment {
context="unit-test"
}
steps {
setBuildStatus("${context}", "${context} Progressing...", "PENDING", "${buildUrl}");
sh "${npmCommand} run test:unit -- -- --coverage --testResultsProcessor jest-sonar-reporter --detectOpenHandles"
}
post {
always {
script {
if (currentBuild.currentResult != 'FAILURE') {
setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${buildUrl}/cobertura");
} else {
setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${buildUrl}/cobertura");
}
}
}
}
}
stage('Storyshot') {
environment {
context="storybook/snapshot-test"
}
steps {
setBuildStatus(context, "${context} Progressing...", "PENDING", "${buildUrl}");
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
script {
try {
sh "${npmCommand} run test:storybook"
} catch (e) {
setBuildStatus("${context}", "${context} Failed", "FAILURE", "${buildUrl}");
error e.message
}
}
}
}
post {
success {
setBuildStatus("${context}", "${context} Pass", "SUCCESS", "${buildUrl}");
}
}
}
}
post {
always {
step([$class: 'CoberturaPublisher', coberturaReportFile: 'coverage/cobertura-coverage.xml'])
}
}
}
stage('Build') {
parallel {
stage('App') {
environment {
context="build"
}
steps {
setBuildStatus("${context}", "${context} Progressing...", "PENDING", "${buildUrl}");
sh "${npmCommand} run build"
}
post {
always {
script {
if (currentBuild.currentResult != 'FAILURE') {
setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${buildUrl}");
} else {
setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${buildUrl}");
}
}
}
}
}
stage('Storybook') {
when {
expression { isMainBranch == 'true' }
}
environment {
context="storybook/build"
}
steps {
setBuildStatus("${context}", "${context} Progressing...", "PENDING", "${buildUrl}");
script {
withCredentials([string(credentialsId: 'ZEPLIN_TOKEN', variable: 'TOKEN')]) {
sh "STORYBOOK_ZEPLIN_TOKEN=${TOKEN} ${npmCommand} run storybook:build"
}
}
}
post {
always {
script {
if (currentBuild.currentResult != 'FAILURE') {
setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${buildUrl}");
} else {
setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${buildUrl}");
}
}
}
}
}
}
}
stage('Deploy') {
parallel {
stage('Storybook image snapshot Test') {
when {
expression { env.BRANCH_NAME ==~ /(develop|release.*)/ }
}
environment {
context="storybook/snapshot-image-test"
}
steps {
setBuildStatus(context, "${context} Progressing...", "PENDING", "${buildUrl}");
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
script {
try {
sh "${npmCommand} run test:image-snapshot"
} catch (e) {
setBuildStatus("${context}", "${context} Failed", "FAILURE", "${buildUrl}");
error e.message
}
}
}
}
post {
success {
setBuildStatus("${context}", "${context} Pass", "SUCCESS", "${buildUrl}");
}
}
}
stage('Deploy Storybook') {
when {
expression { isMainBranch == 'true' }
}
environment {
context="storybook/deploy"
host = "deploy-host"
outputDir = ".out"
branchName = "${env.BRANCH_NAME.split("/")[0]}"
deployTempDir = "/tmp/jenkins_tmp/${env.GIT_BRANCH}"
deployTargetDir = "~/deploy/host/${env.branchName}"
}
steps {
setBuildStatus(context, "${context} Progressing...", "PENDING", "${buildUrl}");
script {
def remote = [:]
remote.name = "deploy-host"
remote.host = "deploy-host"
remote.allowAnyHosts = true
withCredentials([sshUserPrivateKey(credentialsId: 'jenkins-private-key', keyFileVariable: 'identity')]) {
remote.user = 'user'
remote.identityFile = identity
remote.logLevel = 'INFO'
sshCommand remote: remote, command: "mkdir -p ${deployTempDir}"
sshPut remote: remote, from: "./${outputDir}", into: "${deployTempDir}"
sshCommand remote: remote, command: "rsync -avzh ${deployTempDir}/${outputDir}/* ${deployTargetDir}/ --delete"
sshRemove remote: remote, path: "${deployTempDir}", failOnError: false
}
}
}
post {
always {
script {
if (currentBuild.currentResult != 'FAILURE') {
setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${storybookUrl}/${branchName}");
} else {
setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${storybookUrl}/${branchName}");
}
}
}
}
}
}
}
stage('SonarQube Quality Gate') {
when {
expression { isSonarCoverage == 'true' }
}
environment {
context="SonarQube"
}
steps {
setBuildStatus(context, "${context} Progressing...", "PENDING", "${buildUrl}");
script{
timeout(time: 1, unit: 'HOURS') {
def qg = waitForQualityGate()
if (qg.status != 'OK') {
echo "Pipeline aborted due to quality gate failure: ${qg.status}"
setBuildStatus("${context}", "${context} Failed", "UNSTABLE", "${sonarQubeUrl}");
}
}
}
}
post {
always {
script {
if (currentBuild.currentResult != 'FAILURE') {
setBuildStatus("${context}", "${env.STAGE_NAME} Success", "SUCCESS", "${sonarQubeUrl}");
} else {
setBuildStatus("${context}", "${env.STAGE_NAME} Failed", "FAILURE", "${sonarQubeUrl}");
}
}
}
}
}
}
post {
cleanup {
cleanWs(
deleteDirs: true,
patterns: [
[pattern: 'dist', type: 'INCLUDE'],
[pattern: '.out', type: 'INCLUDE'],
]
)
}
success {
script {
def previousResult = currentBuild.previousBuild?.result
if (!previousResult || (previousResult && previousResult != currentBuild.result)) {
notifySlack ('SUCCESS', '#00FF00')
}
}
}
unstable {
notifySlack ('UNSTABLE', '#FFFF00')
}
failure {
notifySlack ('FAILED', '#FF0000')
}
}
}