Build Automation: CI/CD with Jenkins
Introduction to build automation
- Need a dedicated server
- Test environment is prepared
- Docker credentials configured
- All necessary tools installed
- Trigger build automatically
- In a build automation process
- Code is automatically checked out
- Code is tested
- Application is built
- Pushed to repository
- Deploy to server
- Tools
- Jenkins
- Software that you install on server
- Has UI for configuration
- Install all the tools you need (Docker, Gradle, Maven, etc)
- Configure the tasks (run tests, build app, deployment, etc)
- Configure the automatic trigger of the workflow
- Jenkins
- What you can do with Jenkins
- Run tests
- Build artifacts
- Publish artifacts
- Deploy artifacts
- Send notifications
- Needs to integrate with other tools
- Docker
- Build tools
- Repos
- Deployment servers
- Has plugins
- What do you need to configure?
- Run tests
- Build tools need to be available
- npm test, gradlew test, mvn test
- Configure test environment (eg test database)
- Build tools need to be available
- Run tests
- Build Application
- Build tools or Docker
- Publish Docker Image
- Store Credentials in Jenkins
- Jenkins user must have access to all these technologies and platforms
Install Jenkins
- 2 ways
- Install Jenkins directly on OS
- download package and install on server
- create separate Jenkins User on server
- Run Jenkins as a Docker container
- Install Jenkins directly on OS
- Must have at least a gigabyte of RAM
8080- Jenkins- Need to open port 50000 as well
- This is where jenkins master and worker nodes communicate
- We'll mount volumes too.
Install docker
- Visit docs to install docker
- NB: Installing with
snapcomes with some issues
apt update
docker run -p 8080:8080 -p 50000:50000 -d -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts
Jenkins UI tour
- 2 roles for Jenkins App
- Jenkins Administrator
- administers and manages Jenkins
- sets up Jenkins cluster
- installs plugins
- backup Jenkins data
- Jenkins Administrator
- Developer or DevOps teams
- creating the actual jobs to run workflows
Installing Build Tools
- Create Job to automate your apps workflow
- Depending on your App (Programming Language) you need to have different Tools installed and configured on Jenkins
- 2 ways to install and configure those tools
- Jenkins Plugins
- just install plugin (via UI) for your tool
- Install tools directly on server
- enter in remote server and install
- inside the Docker container, when Jenkins runs as a container
- Jenkins Plugins
Configure Plugin for Maven
- Global tool configuration on UI
Install npm and Node in Jenkins container
- Enter container as a root user
docker exec -u 0 -it <id> bash
# Check distro
cat /etc/issue # Debian
- So we need the node and npm installation guide for Debian
apt update
apt install curl
apt install nodejs npm -y
Jenkins Basics Demo
Create a Simple Freestyle Job & Plugin Configuration
- We'll use the Freestyle Project
- In prod, we don't use this. We use the Pipeline and multibranch pipeline
- Build steps
- Execute shell
- Can run
npmcommands because we installed it in the container. Can't run maven commands - Can use Invoke top-level Maven targets since we have the Maven plugin installed
- Even though installing a tool on the server requires a bit more effort, it is more flexible
- The plugin gives you a UI that is limited to the provided input fields
- For a tool to show up on the Global Tool Config page, it has to be installed as a plugin
Configure Git Repository
-
SSH
- Enter container as Jenkins user
- Create ssh key
- Paste private key in UI
- Paste public key in github
- Run ssh -T git@github.com
- Restart Jenkins
-
Jobs are saved in a directory
/var/jenkins_home/jobs
-
Git checkout data
/var/jenkins_home/workspace
-
Do something from Git repo in Jenkins Job
- Add commands in execute shell
chmod +x freestyle-build.sh
./freestyle-build.sh
Docker In Jenkins
Make Docker Available In Jenkins
- Attach a volume to Jenkins from the host machine
- We have docker on our droplet
- So we mount the docker runtime directory from our droplet into the container as a volume
- This will make docker available inside the container
- We'll restart our container with new volumes
docker run -p 8080:8080 -p 50000:50000 -d \
> -v jenkins_home:/var/jenkins_home \
> -v /var/run/docker.sock:/var/run/docker.sock \ # Docker volume
> -v $(which docker):/usr/bin/docker jenkins/jenkins:lts # Docker runtime
- Give permission to docker.sock file to Jenkins user
- Log in to container as root
chmod 666 /var/run/docker.sock
- If there is an issue with docker in the container
- Create a new Jenkins container without the docker runtime volume
docker run -p 8080:8080 -p 50000:50000 -d -v jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock jenkins/jenkins:lts
Install docker in Jenkins container
curl https://get.docker.com > dockerinstall && chmod 777 dockerinstall && ./dockerinstall
Change permissions to allow Jenkins user to read and write to docker.sock
chmod 666 /var/run/docker.sock
Build Docker Image
- In build step
docker build -t java-maven-app:1.0 .
Push image to DockerHub
- Create credentials for DockerHub in Jenkins
- Route:
/manage/credentials/store/system/domain/_/
- Route:
- In build environment, use secret text to reference credentials
- In shell script
docker build -t alfredasare/devops-demo-app:jma-1.0 .
docker login -u $USERNAME -p $PASSWORD <private repo domain>
docker push alfredasare/devops-demo-app:jma-1.0
To fix password insecure error
docker build -t alfredasare/devops-demo-app:jma-1.1 .
echo $PASSWORD | docker login -u $USERNAME --password-stdin
docker push alfredasare/devops-demo-app:jma-1.1
Push Docker Image to Nexus Repository
- Need to allow insecure URLs on Jenkins like we did for Docker Desktop
- Add to daemon.json
vim /etc/docker/daemon.json
daemon.js
{
"insecure-registries": ["46.101.137.161:8083"]
}
Restart Docker
systemctl restart docker
- After restarting docker, all containers are stopped
- Start them back up
- Change permissions to /var/run/docker.sock inside the container again
- Enter container as root
chmod 666 /var/run/docker.sock
- Create credentials for Nexus
Build script
- Change credentials to Nexus credentials
docker build -t 46.101.137.161:8083/java-maven-app:1.1 .
echo $PASSWORD | docker login -u $USERNAME --password-stdin 46.101.137.161:8083
docker push 46.101.137.161:8083/java-maven-app:1.1
Freestyle to pipeline jobs
- Freestyle jobs with multiple steps aren't recommended
- Freestyle jobs should have 1 job/step
- One job for integration tests
- Another for building the app
- Another for deploying the app
- Not suitable for complex workflows
- Pipeline
- Suitable for CI/CD
- Scripting - pipeline as code
Introduction to pipelines
- Create pipeline
- Configuration
- Pipeline: Pipelines are scripted
- Configuration is done in a script and not the UI
- Script is written in Groovy (similar to Java)
- Configuration
- Groovy Sandbox
- You can execute a limited no. of Groovy functions, for that you don't need approval from a Jenkins admin
- Best practice to IaC:
- Pipeline Script should be in your Git Repo
- We'll use Pipeline Script from SCM (Source Code Mgmt)
- Specify Script Path as Jenkinsfile
- Jenkins looks for a Jenkinsfile after checking out your app
- Pipeline syntax
- Can be written as a Scripted or Declarative Pipeline
- Scripted
- First syntax
- Groovy engine
- Advanced scripting capabilities, high flexibility
- Difficult to start
- Declarative
- More recent addition
- Easier to get started with but not that powerful
- Pre-defined structure
Required fields of Jenkinsfile
- Pipeline must be top-level
- agent - where to execute
- Could be a node or executor on that node
- Relevant with a Jenkins cluster
- pipeline + agent == node
- stages: where the work happens
- stages and steps
Jenkinsfile
pipeline {
agent any
stages {
stage("build") {
steps {
}
}
stage("test") {
steps {
}
}
stage("deploy") {
steps {
}
}
}
}
node {
// groovy script
}
Advantages of Pipeline Jobs over Freestyle jobs
- Parallel jobs
- User input
- Conditional statements
- Set variables
- No reliance on plugins
- No need to manage plugins
- No need to manage different jobs and their plugins for your CI process
Jenkins Syntax
Post Attribute In JenkinsFile
- Execute some logic AFTER all stages executed
- Conditions
- always
- success
- failure
post {
always {
}
}
Define conditions for each stage
stage("test") {
when {
expression {
BRANCH_NAME == 'dev' || BRANCH_NAME == 'master'
}
}
steps {
echo 'testing the application...'
}
}
Example 2
def gv
CODE_CHANGES = getGitChanges() // Groovy script that checks for code changes
pipeline {
agent any
stages {
stage("init") {
steps {
script {
echo "initialize"
// gv = load "script.groovy"
}
}
}
stage("test") {
when {
expression {
BRANCH_NAME == 'dev'
}
}
steps {
echo 'testing the application...'
}
}
stage("build jar") {
when {
expression {
BRANCH_NAME == 'dev' && CODE_CHANGES == true
}
}
steps {
script {
echo "building jar"
//gv.buildJar()
}
}
}
stage("build image") {
steps {
script {
echo "building image"
//gv.buildImage()
}
}
}
stage("deploy") {
steps {
script {
echo "deploying"
//gv.deployApp()
}
}
}
}
}
Environmental Variables
- What variables are available from Jenkins?
/env-vars.html- http://68.183.215.97:8080/env-vars.html/
Using Credentials in Jenkinsfile
- Define Credentials in Jenkins UI
credentials("credentialId")binds the credentials to your env variable- For that you need
Credentials BindingPluginCredentials Plugin: allows you to store credentials in JenkinsCredentials Binding Plugin: allows credentials to be bound to environment variables for use from misc build steps
Jenkinsfile
def gv
pipeline {
agent any
//////////////////////////////////
environment {
NEW_VERSION = '1.3.0'
// SERVER_CREDENTIALS = credentials('server-credentials')
}
stages {
stage("init") {
steps {
script {
echo "initialize"
// gv = load "script.groovy"
}
}
}
stage("build jar") {
steps {
script {
echo "building jar"
echo "building version ${NEW_VERSION}"
//gv.buildJar()
}
}
}
stage("build image") {
steps {
script {
echo "building image"
//gv.buildImage()
}
}
}
stage("deploy") {
steps {
script {
echo "deploying"
////////////////////////
withCredentials([
usernamePassword(credentialsId: 'server-credentials', usernameVariable: 'USER', passwordVariable: 'PWD')
]) {
sh "some script $USER $PWD"
}
//gv.deployApp()
}
}
}
}
}
Tools attribute for Build Tools
- Only 3 build tools available
- gradle, maven and jdk
Jenkinsfile
pipeline {
agent any
tools {
maven 'maven-3.9' // Name of build tool installation
}
}
Parameters in Jenkinsfile
Jenkinsfile
def gv
pipeline {
agent any
parameters {
// string(name: 'VERSION', defaultValue: '', description: 'version to deploy on prod')
choice(name: 'VERSION', choices: ['1.1.0', '1.2.0', '1.3.0'], description: '')
booleanParam(name: 'executeTests', defaultValue: true, description: '')
}
stages {
stage("init") {
steps {
script {
echo "initialize"
// gv = load "script.groovy"
}
}
}
stage("test") {
when {
expression {
params.executeTests
}
}
steps {
echo 'testing the application...'
}
}
stage("build jar") {
steps {
script {
echo "building jar"
//gv.buildJar()
}
}
}
stage("build image") {
steps {
script {
echo "building image"
//gv.buildImage()
}
}
}
stage("deploy") {
steps {
script {
echo "deploying"
echo "deploying version ${params.VERSION}"
//gv.deployApp()
}
}
}
}
}
Using external Groovy Script
- Good idea to clean up scripts in Jenkinsfile and put them in their own groovy script file
- Write scripts in scripts block
- All environmental variables in Jenkinsfile are available in the groovy script
script.groovy
def buildJar() {
echo "building the application..."
// sh 'mvn package'
}
//def buildImage() {
// echo "building the docker image..."
// withCredentials([usernamePassword(credentialsId: 'docker-hub-repo', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
// sh 'docker build -t nanajanashia/demo-app:jma-2.0 .'
// sh "echo $PASS | docker login -u $USER --password-stdin"
// sh 'docker push nanajanashia/demo-app:jma-2.0'
// }
//}
def testApp() {
echo 'testing the application...'
}
def deployApp() {
echo "deploying"
echo "deploying version ${params.VERSION}"
}
return this
Jenkinsfile
def gv
pipeline {
agent any
parameters {
// string(name: 'VERSION', defaultValue: '', description: 'version to deploy on prod')
choice(name: 'VERSION', choices: ['1.1.0', '1.2.0', '1.3.0'], description: '')
booleanParam(name: 'executeTests', defaultValue: true, description: '')
}
stages {
stage("init") {
steps {
script {
echo "initialize"
gv = load "script.groovy"
}
}
}
stage("test") {
when {
expression {
params.executeTests
}
}
steps {
script {
gv.testApp();
}
}
}
stage("build jar") {
steps {
script {
gv.buildJar()
}
}
}
stage("build image") {
steps {
script {
echo "building image"
//gv.buildImage()
}
}
}
stage("deploy") {
steps {
script {
gv.deployApp()
}
}
}
}
}
- You can go to Replay mode for a build and make changes to the script to see how the changes affect the build without having to commit any code
Input Parameter in Jenkinsfile
Jenkinsfile
stage("deploy") {
input {
message "Select the environment to deploy to"
ok "Done"
parameters {
choice(name: 'ONE', choices: ['dev', 'staging', 'production'], description: '')
choice(name: 'TWO', choices: ['dev', 'staging', 'production'], description: '')
}
}
steps {
script {
gv.deployApp()
echo "Deploying to ${ONE}"
echo "Deploying to ${TWO}"
}
}
}
Parameters in the script block
stage("deploy") {
steps {
script {
env.ENV = input message: "Select the environment to deploy to", ok: "Done", parameters: [choice(name: 'ONE', choices: ['dev', 'staging', 'production'], description: '')]
gv.deployApp()
echo "Deploying to ${ENV}"
}
}
}
Create a complete Pipeline
script.groovy
def buildJar() {
echo "building the application..."
sh 'mvn package'
}
def buildImage() {
echo "building the docker image..."
withCredentials([usernamePassword(credentialsId: 'docker-hub-repo', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
sh 'docker build -t alfredasare/devops-demo-app:jma-2.0 .'
sh "echo $PASS | docker login -u $USER --password-stdin"
sh 'docker push alfredasare/devops-demo-app:jma-2.0'
}
}
def deployApp() {
echo "deploying the application"
}
return this
Jenkinsfile
def gv
pipeline {
agent any
tools {
maven 'maven-3.9'
}
stages {
stage("init") {
steps {
script {
gv = load "script.groovy"
}
}
}
stage("build jar") {
steps {
script {
gv.buildJar()
}
}
}
stage("build image") {
steps {
script {
gv.buildImage()
}
}
}
stage("deploy") {
steps {
script {
gv.deployApp()
}
}
}
}
}
Introduction to multibranch pipeline
- Run tests on every branch
- Execute deployments only on Main/Master Branch
- When merged into master: test, build and deploy App
- We'll build pipelines for ALL the branches
- Need different behaviour based on branch
- We need to create a multibranch pipeline
- We need to dynamically create new pipelines for new branches
- Multibranch pipeline name is usually the name of the app
- For Behaviours choose Filter by name (with regular expression)
.*--> To match all branches
- Can also specify multiple branches for a regular pipeline but you won't have access to variables and plugins for multiple branch pipelines
- You won't have an overview of the status of the different branches
- You would have one Jenkinsfile that all the branches share
- We need logic to allow deploys only on master and tests on other branches
Branch-based logic for a multibranch pipeline
- The
BRANCH_NAMEis only available in the multibranch pipeline jobs
Jenkinsfile
stage("test") {
steps {
script {
echo "Testing the application..."
echo "Executing pipeline for branch $BRANCH_NAME"
}
}
}
stage("build") {
when {
expression {
BRANCH_NAME == 'master'
}
}
steps {
script {
echo "Building the application..."
}
}
}
stage("deploy") {
when {
expression {
BRANCH_NAME == 'master'
}
}
steps {
script {
echo "Deploying the application..."
// gv.deployApp()
}
}
}
Jenkins Job Overview
- 3 Types of Jobs
- Freestyle
- Executing a single task
- Pipeline
- Better solution for CI/CD
- One job for multiple steps
- Multibranch pipeline
- Parent of pipeline jobs
- One pipeline is meant for a single branch
- Multibranch pipeline is for multiple branches
- Freestyle
Credentials In Jenkins
- Credentials Plugin helps to store and manage credentials centrally
- Credentials Scopes
- System
- Only available on Jenkins Server
- e.g. For Jenkins admin.
- Not visible or accessible by Jenkins Jobs
- Global
- Available everywhere
- System
- Credential Types
- Github App
- SSH Username with private key
- Secret file
- Secret text
- Certificate
- Can have new types based on plugins
Credential IDis what you reference in your jobs- There is also a Credentials Page for multibranch pipelines
- That's another scope besides Global and System
- This scope is limited to the multibranch pipeline or project
- This comes from a folder plugin
- Enables you to have credentials scoped to your project
- It's a good way to seperate credentials for different projects
Jenkins Shared Library
- Assuming we have microservices
- In Jenkins
- We'll need a multistage pipeline for each of those microservices to build them
- All the projects have their own Jenkins files
- May have the same logic in those Jenkins files
- We don't want to copy and paste
- Shared Library
- Extension to Pipeline
- Is its own repository written in Groovy
- Reference shared logic in Jenkinsfile
- Another use case
- A company may have multiple teams working on multiple projects
- May not use the same tech stack, but might have the same logic
- e.g. company-wide Nexus Repo
- e.g. company-wide Slack channel
- Don't replicate code
- We'll
- Create the Shared Library (SL)
- Make SL available in Jenkins
- Use the SL in Jenkinsfile
Create Shared Library Project/Repository
-
Create repo
-
Write Groovy code
-
Make SL available globally or for project
-
Use the Shared Library in jenkinsfile to extend the Pipeline
-
Structure of Shared Library
varsfolder- functions that we call from Jenkinsfile
- Each function/execution step is its own Groovy file
- e.g. build Jar File
- build Docker Image
- push Docker Image
srcfolder- helper code
resourcesfolder- for external libraries
- non-groovy files
-
You can use this snippet in your Jenkinsfile to let your editor know you're working in a groovy file
#!/usr/bin/env groovy
vars
buildJar.groovy
#!/usr/bin/env groovy
def call() {
echo "building the application..."
sh 'mvn package'
}
buildImage.groovy
#!/usr/bin/env groovy
def call () {
echo "building the docker image..."
withCredentials([usernamePassword(credentialsId: 'docker-hub-repo', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
sh 'docker build -t alfredasare/devops-demo-app:jma-2.0 .'
sh "echo $PASS | docker login -u $USER --password-stdin"
sh 'docker push alfredasare/devops-demo-app:jma-2.0'
}
}
- Create a repository
Make shared library available globally
- In Jenkins
- Manage Jenkins > Configure System
- Add to
Global Pipeline Libraries- Default version could be a branch or commit hash or tag
Use shared library in Jenkinsfile
Jenkinsfile
#!/usr/bin/env groovy
@Library('jenkins-shared-library')_ // Name we called library in Jenkins config
def gv
pipeline {
agent any
tools {
maven 'maven-3.9'
}
stages {
stage("init") {
steps {
script {
gv = load "script.groovy"
}
}
}
stage("build jar") {
steps {
script {
buildJar()
}
}
}
stage("build image") {
steps {
script {
buildImage()
}
}
}
stage("deploy") {
when {
expression {
BRANCH_NAME == 'master'
}
}
steps {
script {
echo "Deploying the application..."
gv.deployApp()
}
}
}
}
}
@Library('jenkins-shared-library')_- The
_is required if we don't have any variable definitions or imports after the library declaration to show the separation.
- The
Using Parameters in Shared Library
buildImage.groovy
#!/usr/bin/env groovy
def call (String imageName) {
echo "building the docker image..."
withCredentials([usernamePassword(credentialsId: 'docker-hub-repo', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
sh "docker build -t $imageName ."
sh "echo $PASS | docker login -u $USER --password-stdin"
sh "docker push $imageName"
}
}
- Access Global Vars
buildJar.groovy
#!/usr/bin/env groovy
def call() {
echo "building the application for branch $BRANCH_NAME"
sh 'mvn package'
}
Extract Logic Into Groovy Classes
- Can do this in src directory
- Create a new package
com.example
Docker.groovy
#!/usr/bin/env groovy
package com.example
class Docker implements Serializable {
def script
Docker(script) {
this.script = script;
}
def buildDockerImage(String imageName) {
script.echo "building the docker image..."
script.withCredentials([script.usernamePassword(credentialsId: 'docker-hub-repo', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
script.sh "docker build -t $imageName ."
script.sh "echo $script.PASS | docker login -u $script.USER --password-stdin"
script.sh "docker push $imageName"
}
}
}
buildImage.groovy
#!/usr/bin/env groovy
import com.example.Docker
def call (String imageName) {
return new Docker(this).buildDockerImage(imageName)
}
Split buildDockerImage into separate steps
Docker.groovy
#!/usr/bin/env groovy
package com.example
class Docker implements Serializable {
def script
Docker(script) {
this.script = script;
}
def buildDockerImage(String imageName) {
script.echo "building the docker image..."
script.sh "docker build -t $imageName ."
}
def dockerLogin() {
script.withCredentials([script.usernamePassword(credentialsId: 'docker-hub-repo', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
script.sh "echo $script.PASS | docker login -u $script.USER --password-stdin"
}
}
def dockerPush(String imageName) {
script.sh "docker push $imageName"
}
}
dockerLogin.groovy
#!/usr/bin/env groovy
import com.example.Docker
def call() {
return new Docker(this).dockerLogin()
}
dockerPush.groovy
#!/usr/bin/env groovy
import com.example.Docker
def call(String imageName) {
return new Docker(this).dockerPush(imageName)
}
Jenkinsfile
stage("build and push image") {
steps {
script {
buildImage "alfredasare/devops-demo-app:jma-3.0"
dockerLogin()
dockerPush 'alfredasare/devops-demo-app:jma-3.0'
}
}
}
Project Scoped Shared Library
- Overriding the version of a Global shared Library
@Library('jenkins-shared-library@2.0')_
- Project scoped
Jenkinsfile
#!/usr/bin/env groovy
// @Library('jenkins-shared-library')_
library identifier: 'jenkins-shared-library@main', retriever: modernSCM(
[
$class: 'GitSCMSource',
remote: 'git@github.com:alfredasare/jenkins-shared-library.git',
credentialsId: 'github-credentials'
]
)
def gv
Webhooks trigger pipeline jobs automatically
- How to trigger Jenkins Build Jobs
- Manual
- Use case may be for production pipelines where you want to ensure certain conditions are met before triggering the build for production
- Automatic
- Trigger automatically when changes happen in the git repository
- Jenkins and Gitlab/GitHub need to be configured for that
- Scheduling
- Trigger a job on scheduled times
- For long-running tests. e.g. Selenium tests
- Manual
Configure Automatic Triggering of Jenkins Job
- Jenkins config
- Install Gitlab (for Gitlab)
- Github
- In pipeline, check
GitHub hook trigger for GITScm polling
- In pipeline, check
- GitHub config
- Add Payload URL
- http://68.183.215.97:8080/github-webhook/
- Content-type: application/json
- Select events
- Add Payload URL
Configure Automatic triggering of Jenkins Jobs for Multi-Branch Pipeline
- Use
Multibranch Scan Webhook Triggerplugin - Go to configuration
- Tick Scan by webhook
- Add trigger token
- Copy URL with token to
github: JENKINS_URL/multibranch-webhook-trigger/invoke?token=[Trigger token]
- Copy URL with token to
Dynamically increment application version In Jenkins Pipeline - 1
- Each build tool or package manager tool keeps a version
- Gradle - build.gradle
- Maven - pom.xml
- npm - package.json
- You decide how to version your app
- Most common
- 3 parts of a version e.g.
4.2.1 Major- Contains big changes
- Breaking changes
- Not backward compatible
Minor- New but backward-compatible changes, API features
Patch- Minor changes and bug fixes, doesn't change API
- Can add suffixes to add more information
- eg.
SNAPSHOT: For development versions RC- Release candidateRELEASE
- eg.
- 3 parts of a version e.g.
- How to increment the version
- Increment the version in e.g.
pom.xmlfile orpackage.json - You should increment the type automatically
- Automatically increase version inside the build automation
- Build Tools have commands to increment the version
- Increment the version in e.g.
Increment version locally (Maven)
mvn build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.nextIncrementalVersion} versions:commit
parsedVersionhas six values- majorVersion
- nextMajorVersion
- minorVersion
- nextMinorVersion
- incrementalVersion
- nextIncrementalVersion
- Same concept runs through for other package manager tools
- You run this command in your pipeline and not locally
Increment Application Version on Jenkins (maven Project)
Jenkinsfile
stage("increment version") {
steps {
script {
gv.incrementVersion()
}
}
}
stage("build jar") {
steps {
script {
gv.buildJar()
}
}
}
script.groovy
def incrementVersion() {
echo "incrementing app version..."
sh "mvn build-helper:parse-version versions:set -DnewVersion=\\\${parsedVersion.majorVersion}.\\\${parsedVersion.minorVersion}.\\\${parsedVersion.nextIncrementalVersion} versions:commit"
}
def buildJar() {
echo "building the application..."
sh 'mvn package'
}
def buildImage() {
echo "building the docker image..."
withCredentials([usernamePassword(credentialsId: 'docker-hub-repo', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
sh 'docker build -t alfredasare/devops-demo-app:jma-2.0 .'
sh "echo $PASS | docker login -u $USER --password-stdin"
sh 'docker push alfredasare/devops-demo-app:jma-2.0'
}
}
def deployApp() {
echo "deploying the application to EC2"
}
return this
Docker Image Version
- Use application version for Docker Image version
Jenkinsfile
stage("increment version") {
steps {
script {
echo "incrementing app version..."
sh "mvn build-helper:parse-version versions:set -DnewVersion=\\\${parsedVersion.majorVersion}.\\\${parsedVersion.minorVersion}.\\\${parsedVersion.nextIncrementalVersion} versions:commit"
def matcher = readFile('pom.xml') =~ '<version>(.+)</version>'
def version = matcher[0][1]
env.IMAGE_NAME = "$version-$BUILD_NUMBER"
}
}
}
script.groovy
def buildJar() {
echo "building the application..."
sh 'mvn package'
}
def buildImage() {
echo "building the docker image..."
withCredentials([usernamePassword(credentialsId: 'docker-hub-repo', passwordVariable: 'PASS', usernameVariable: 'USER')]) {
sh "docker build -t alfredasare/devops-demo-app:${IMAGE_NAME} ."
sh "echo $PASS | docker login -u $USER --password-stdin"
sh "docker push alfredasare/devops-demo-app:${IMAGE_NAME}"
}
}
def deployApp() {
echo "deploying the application to EC2"
}
return this
Replace New Version In Dockerfile
Dockerfile
FROM openjdk:8-jre-alpine
EXPOSE 8080
COPY ./target/java-maven-app-*.jar /usr/app/
WORKDIR /usr/app
CMD java -jar java-maven-app-*.jar
script.groovy
def buildJar() {
echo "building the application..."
sh 'mvn clean package'
}
- Access envs in shell with
${ENV_NAME}
Dynamically increment application version in Jenkinsfile Pipeline 2
Commit version bump from Jenkins
- The version bump only happened in Jenkins
- It's never committed to GitHub
- If a commit is made, devs need to pull changes before committing their code again
- Install
SSH Agentjenkins plugin
Jenkinsfile
stage("commit version update") {
steps {
script {
sshagent(credentials: ['github-credentials']) {
// Need to set this once
// Can also ssh into Jenkins server to set it
sh 'git config --global user.email "jenkins@example.com"'
sh 'git config --global user.name "jenkins"'
sh "git status"
sh "git branch"
sh "git config --list"
sh "git remote set-url origin git@github.com:alfredasare/java-maven-app.git"
sh "git add ."
sh 'git commit -m "ci: version bump"'
sh "git push origin HEAD:jenkins-jobs"
}
}
}
}
- When Jenkins checks out the code from Git, it doesn't checkout the branch, it checks out the commit hash
- It checks out the last commit in that branch
sh "git push origin HEAD:jenkins-jobs"
- Push all commits from that branch into jenkins-jobs
Ignore Jenkins Commit for Jenkins Pipeline Trigger
- We'll end up with a loop of commit + Pipeline Triggering
- Solution
- Detect that commit was made from Jenkins
- Ignore trigger when commit is from Jenkins
- Install Ignore Committer Strategy
- Add
Build strategies onconfigure page
- Add
- Add Ignore committer strategy
- Add jenkins email to blacklist e.g. jenkins@example.com
- Tick the checkbox to allow commits from all other authors