Example Jenkins CI/CD Pipeline
Published 10 March 2020
This example CI/CD pipeline uses the installed SQL Change Automation PowerShell cmdlets on a Windows agent. This is implemented using a Pipeline as Code Jenkinsfile. If you are familiar with using a Jenkinsfile, we hope you can drop this into your repository and customize the variables at the top to start a proof of concept quickly in your environment.
Jenkinsfile
node { def PROJECT_PATH = 'VoiceOfTheDBA\\VoiceOfTheDBA' def TEST_PROJECT_PATH = 'VoiceOfTheDBA\\VoiceOfTheDBA.Tests' def BUILD_ARTIFACT_PACKAGE_ID = 'VoiceOfTheDBA' def BUILD_ARTIFACT_PACKAGE_VERSION = "1.0.${env.BUILD_NUMBER}" def BUILD_ARTIFACT_FILE = "${BUILD_ARTIFACT_PACKAGE_ID}.${BUILD_ARTIFACT_PACKAGE_VERSION}.nupkg" def INTEGRATION_INSTANCE = 'WIN2016' def INTEGRATION_DATABASE = 'VoiceOfTheDBA_Integration' def ACCEPTANCE_INSTANCE = 'WIN2016' def ACCEPTANCE_DATABASE = 'VoiceOfTheDBA_Acceptance' def PRODUCTION_INSTANCE = 'WIN2016' def PRODUCTION_DATABASE = 'VoiceOfTheDBA_Production' def CLONE_SERVER_URL = 'http://WIN2016:14145' def CLONE_IMAGE_LOCATION = '\\\\WIN2016\\LocalCloneImages' def RELEASE_ARTIFACT_PATH = 'Release' stage ('Build') { checkout scm powershell(label: 'Database build', script: """ \$ErrorActionPreference = "Stop" \$validatedProject = Invoke-DatabaseBuild -InputObject ${PROJECT_PATH} \$buildArtifact = New-DatabaseBuildArtifact -InputObject \$validatedProject -PackageId ${BUILD_ARTIFACT_PACKAGE_ID} -PackageVersion ${BUILD_ARTIFACT_PACKAGE_VERSION} Export-DatabaseBuildArtifact -InputObject \$buildArtifact -Path . """) archiveArtifacts label: 'Archive build artifact', artifacts: "${BUILD_ARTIFACT_FILE}" } stage ('Unit tests') { powershell(label: 'Unit tests', script: """ \$ErrorActionPreference = "Stop" \$testResults = Invoke-DatabaseTests -InputObject ${TEST_PROJECT_PATH} Export-DatabaseTestResults \$testResults -OutputFile _test_results.xml """) junit "_test_results.xml" } stage ('Deploy to integration') { powershell(label: 'Deploy to integration', script: """ \$ErrorActionPreference = "Stop" \$buildArtifact = Import-DatabaseBuildArtifact -Path ${BUILD_ARTIFACT_FILE} \$integrationDatabaseConnection = New-DatabaseConnection -ServerInstance ${INTEGRATION_INSTANCE} -Database ${INTEGRATION_DATABASE} \$releaseArtifact = New-DatabaseReleaseArtifact -Source \$buildArtifact -Target \$integrationDatabaseConnection Use-DatabaseReleaseArtifact -InputObject \$releaseArtifact -DeployTo \$integrationDatabaseConnection """) } stage ('Deploy to acceptance') { powershell(label: 'Use SQL Clone to build acceptance environment from production', script: """ \$ErrorActionPreference = "Stop" Connect-SqlClone -ServerUrl ${CLONE_SERVER_URL} \$ImageName = "${PRODUCTION_DATABASE}-\$(Get-Date -Format yyyyMMddHHmmss)" \$ProductionSqlServerInstance = Get-SqlCloneSqlServerInstance -MachineName ${PRODUCTION_INSTANCE} -InstanceName '' \$ImageDestination = Get-SqlCloneImageLocation -Path ${CLONE_IMAGE_LOCATION} New-SqlCloneImage -Name \$ImageName -SqlServerInstance \$ProductionSqlServerInstance -DatabaseName ${PRODUCTION_DATABASE} -Destination \$ImageDestination | Wait-SqlCloneOperation Get-SqlClone -Name ${ACCEPTANCE_DATABASE} | Remove-SqlClone | Wait-SqlCloneOperation \$AcceptanceSqlServerInstance = Get-SqlCloneSqlServerInstance -MachineName ${ACCEPTANCE_INSTANCE} -InstanceName '' \$ProductionImage = Get-SqlCloneImage -Name \$ImageName New-SqlClone -Name ${ACCEPTANCE_DATABASE} -Location \$AcceptanceSqlServerInstance -Image \$ProductionImage | Wait-SqlCloneOperation """) powershell(label: 'Create release artifact', script: """ \$ErrorActionPreference = "Stop" \$buildArtifact = Import-DatabaseBuildArtifact -Path ${BUILD_ARTIFACT_FILE} \$acceptanceDatabaseConnection = New-DatabaseConnection -ServerInstance ${ACCEPTANCE_INSTANCE} -Database ${ACCEPTANCE_DATABASE} \$releaseArtifact = New-DatabaseReleaseArtifact -Source \$buildArtifact -Target \$acceptanceDatabaseConnection Export-DatabaseReleaseArtifact -InputObject \$releaseArtifact -Path ${RELEASE_ARTIFACT_PATH} """) powershell(label: 'Deploy to acceptance', script: """ \$ErrorActionPreference = "Stop" \$releaseArtifact = Import-DatabaseReleaseArtifact -Path ${RELEASE_ARTIFACT_PATH} \$acceptanceDatabaseConnection = New-DatabaseConnection -ServerInstance ${ACCEPTANCE_INSTANCE} -Database ${ACCEPTANCE_DATABASE} Use-DatabaseReleaseArtifact -InputObject \$releaseArtifact -DeployTo \$acceptanceDatabaseConnection """) //archiveArtifacts label: 'Archive release artifact', artifacts: "${RELEASE_ARTIFACT_PATH}\\**" archiveArtifacts allowEmptyArchive: true, artifacts:"${RELEASE_ARTIFACT_PATH}\\TargetedDeploymentScript.sql", fingerprint: true archiveArtifacts allowEmptyArchive: true, artifacts:"${RELEASE_ARTIFACT_PATH}\\DriftRevertScript.sql", fingerprint: true archiveArtifacts allowEmptyArchive: true, artifacts:"${RELEASE_ARTIFACT_PATH}\\Reports\\Changes.html", fingerprint: true archiveArtifacts allowEmptyArchive: true, artifacts:"${RELEASE_ARTIFACT_PATH}\\Reports\\Drift.html", fingerprint: true } stage ('Approval gate') { def message = 'Approve release to production?' timeout(time: 30, unit: 'MINUTES') { def userInput = input( id: 'userInput', message: "$message", parameters: [ [$class: 'TextParameterDefinition', defaultValue: 'I approve the deployment', description: 'To proceed, type I approve the deployment', name: 'Review deployment artifacts before proceeding'] ] ) if (userInput.indexOf('I approve the deployment') == -1) { currentBuild.result = 'ABORTED' error('Deployment aborted') } } } stage ('Deploy to production') { powershell(label: 'Deploy to production', script: """ \$ErrorActionPreference = "Stop" \$releaseArtifact = Import-DatabaseReleaseArtifact -Path ${RELEASE_ARTIFACT_PATH} \$productionDatabaseConnection = New-DatabaseConnection -ServerInstance ${PRODUCTION_INSTANCE} -Database ${PRODUCTION_DATABASE} Use-DatabaseReleaseArtifact -InputObject \$releaseArtifact -DeployTo \$productionDatabaseConnection """) } }