Sample Application for CI/CD using Jenkins with declarative Pipelines as Continuous Integration server.
Jenkins Pipeline Stages in Blue Ocean
Area | Tools |
---|---|
CI | Jenkins |
Source Code | GitHub (Cloud) |
Issue Management | GitHub (Cloud) |
Tech stack | ASP.Net MVC, EF, JQuery, Bootstrap, Webpack |
Database | SQL Server |
Web Server | IIS |
Testing | MSTest, Specflow, Selenium, Moq |
Build Tools | MSBuild, MSDeploy |
Dependencies | Nuget, npm |
- github
- git
- global-slack-notifier
- pollscm
- timestamper
- mstest
- msbuild
- nodejs
- vstestrunner
- workflow-aggregator
Added web.config files for Dev, QA and Prod.
Need to add a webhook that points to the Jenkins server.
Github webhooks setup
Sets RELEASE_VERSION and tools paths.
environment{
RELEASE_VERSION = "1.0.1"
VSTest = tool 'vstest'
MSBuild = tool 'msbuild'
Nuget = 'C:\\Program Files (x86)\\Jenkins\\nuget.exe'
MSDeploy = "C:\\Program Files (x86)\\IIS\\Microsoft Web Deploy V3\\msdeploy.exe"
}
Gets source code and notifies to Slack and Github.
stage('Get Source'){
steps{
slackSend (color: '#FFFF00', message: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
setBuildStatus("PENDING: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})", "PENDING");
checkout([$class: 'GitSCM', branches: [[name: 'master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'github', url: 'https://github.com/alanmacgowan/WebApplication.git']]])
}
}
Github Pending status
This stage runs 3 parallel stages: Nuget Restore, NodeJS and Set Assembly Version.
Restores nuget packages, runs in parallel.
stage('Restore'){
steps{
bat "\"${Nuget}\" restore WebApplication.sln"
}
}
Installs npm dependencies and runs webpack, runs in parallel.
stage('NodeJS'){
steps{
nodejs(nodeJSInstallationName: 'Node') {
dir('WebApplication')
{
bat 'npm install && npm run build:prod'
}
}
}
}
Changes Assemblyversion number with current Build number, using Powershell script, runs in parallel.
stage('Set Assembly Version') {
steps {
dir('WebApplication\\Properties')
{
setAssemblyVersion()
}
}
}
...
void setAssemblyVersion(){
powershell ("""
\$PatternVersion = '\\[assembly: AssemblyVersion\\("(.*)"\\)\\]'
\$AssemblyFiles = Get-ChildItem . AssemblyInfo.cs -rec
Foreach (\$File in \$AssemblyFiles)
{
(Get-Content \$File.PSPath) | ForEach-Object{
If(\$_ -match \$PatternVersion){
'[assembly: AssemblyVersion("{0}")]' -f "$RELEASE_VERSION.$BUILD_NUMBER"
} Else {
\$_
}
} | Set-Content \$file.PSPath
}
""")
}
Version number shown in footer
Build and packages WebApplication for latter deployment. The artifact is saved as part of the build in Jenkins.
stage('Build & Package') {
steps {
bat "\"${MSBuild}\" WebApplication.sln /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /t:build /p:Configuration=QA /p:Platform=\"Any CPU\" /p:DesktopBuildPackageLocation=\"%WORKSPACE%\\artifacts\\WebApp_${env.RELEASE_VERSION}.${env.BUILD_NUMBER}.zip\""
}
}
Runs unit tests.
stage('Unit test') {
steps {
dir('WebApplication.Tests.Unit\\bin\\Release')
{
bat "\"${VSTest}\" \"WebApplication.Tests.Unit.dll\" /Logger:trx;LogFileName=Results_${env.BUILD_NUMBER}.trx /Framework:Framework45"
}
step([$class: 'MSTestPublisher', testResultsFile:"**/*.trx", failOnError: true, keepLongStdio: true])
}
}
Unit and Acceptance Tests results
Deploys package to QA IIS site, web.config is replaced by config file in Jenkins.
stage('Deploy to QA') {
steps {
bat "\"${MSDeploy}\" -source:package=\"%WORKSPACE%\\artifacts\\WebApp_${env.RELEASE_VERSION}.${env.BUILD_NUMBER}.zip\" -verb:sync -dest:auto -allowUntrusted=true -setParam:name=\"IIS Web Application Name\",value=\"TestAppQA\""
configFileProvider([configFile(fileId: 'web.QA.config', targetLocation: 'C:\\Jenkins_builds\\sites\\qa\\web.config')]) {}
}
}
Smoke Test QA IIS site, using Powershell script.
stage('Smoke Test QA') {
steps {
smokeTest("http://localhost:8091/")
}
}
...
void smokeTest(String url){
def status = powershell (returnStatus: true, script: powershell ("""
\$result = Invoke-WebRequest $url
if (\$result.StatusCode -ne 200) {
Write-Error \"Did not get 200 OK\"
exit 1
} else{
Write-Host \"Successfully connect.\"
}
""")
if (status != 0) {
error "This pipeline stops here!"
}
}
Runs acceptance tests using QA site.
stage('Acceptance test') {
steps {
dir('WebApplication.Tests.Acceptance\\bin\\Release')
{
bat "\"${VSTest}\" \"WebApplication.Tests.Acceptance.dll\" /Logger:trx;LogFileName=Results_${env.BUILD_NUMBER}.trx /Framework:Framework45"
}
step([$class: 'MSTestPublisher', testResultsFile:"**/*.trx", failOnError: true, keepLongStdio: true])
}
}
This stage waits for input from user to deploy. Deploys package to Prod IIS site, web.config is replaced by config file in Jenkins.
stage('Deploy to Prod') {
input {
message 'Deploy to Prod?'
ok 'Yes'
}
steps {
bat "\"${MSDeploy}\" -source:package=\"%WORKSPACE%\\artifacts\\WebApp_${env.RELEASE_VERSION}.${env.BUILD_NUMBER}.zip\" -verb:sync -dest:auto -allowUntrusted=true -setParam:name=\"IIS Web Application Name\",value=\"TestAppProd\""
configFileProvider([configFile(fileId: 'web.Prod.config', targetLocation: 'C:\\Jenkins_builds\\sites\\prod\\web.config')]) {}
}
}
Smoke Test Prod IIS site, using Powershell script.
stage('Smoke Test Prod') {
steps {
smokeTest("http://localhost:8092/")
}
}
Notification and archiving.
post {
failure {
slackSend (color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
setBuildStatus("FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})", "FAILURE");
}
success{
slackSend (color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
setBuildStatus("SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})", "SUCCESS");
}
always {
archiveArtifacts "artifacts\\WebApp_${env.RELEASE_VERSION}.${env.BUILD_NUMBER}.zip"
}
}
Artifacts stored in Jenkins
Github successful notification
Slack notifications
- Jenkinsfile: for declarative pipeline on Jenkins, triggers from SCM on push to master branch, builds and deploys to local IIS Server.
- web-deploy.ps1: powershell script that builds, deploys to file system and package web application.
- web-publish.ps1: powershell script that builds, packages and deploys to IIS web application.
- Docker/Dockerfile: Dockerfile for linux image with jenkins installed and plugins.
- Docker/Dockerfile_Windows: Dockerfile for windows image(based on: https://blog.alexellis.io/continuous-integration-docker-windows-containers/)
Using Declarative Jenkins Pipelines