Jenkinsfile for Oracle deployments
Published 11 October 2021
This example CI/CD pipeline uses the installed Flyway, SQL Compare, and Code Analysis for Oracle command lines on a Windows agent. This is implemented using a Pipeline as Code Jenkinsfile. If you are familiar with 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.
This example produces the following pipeline. More or less environments can be added for your proof of concept. We are using the open-source Blue Ocean extension from Jenkins to provide this visual view of the pipeline.
Jenkinsfile Toggle source code
- node {
- def OutputDirectory = "C:/Program Files (x86)/Jenkins/jobs/Oracle DevOps (migrations)"
- def CiDatabaseJdbc = "jdbc:oracle:thin:@//localhost:1521/ci"
- def AcceptanceDatabaseJdbc = "jdbc:oracle:thin:@//localhost:1521/acceptance"
- def ProductionDatabaseJdbc = "jdbc:oracle:thin:@//localhost:1521/production"
- def Schema = "HR"
- def User = "HR"
- def Password = "Redgate1"
- def SQLCOMPARE='"C:\\Program Files\\Red Gate\\Schema Compare for Oracle 5\\sco.exe"'
- def SQLDATACOMPARE='"C:\\Program Files\\Red Gate\\Data Compare for Oracle 5\\dco.exe"'
- def CODEANALYSIS='"C:\\Program Files\\Red Gate\\Code Analysis for Oracle\\cao.cmd"'
- stage ('Prep') {
- echo "Ensure the pipeline is configured to clean the Jenkins workspace before checkout"
- //bat('set')
- deleteDir()
- checkout scm
- }
- stage ('Build') {
- echo "DB Build"
- def status
- // Clean the CI DB to get a fresh build
- status = bat returnStatus: true, label: "Clean", script:" FLYWAY clean info -url=${CiDatabaseJdbc} -user=${User} -password=${Password} -schemas=${Schema}"
- echo "Status of Running CI build: $status"
- if (status != 0) { error('Running CI build failed') }
- // Generate the dry run script for later review
- status = bat returnStatus: true, label: "DryRun", script:" FLYWAY migrate info -dryRunOutput=\"${OutputDirectory}/workspace/Build.sql\" -url=${CiDatabaseJdbc} -user=${User} -password=${Password} -schemas=${Schema}"
- echo "Status of Running CI build: $status"
- if (status != 0) { error('Running CI build failed') }
- archiveArtifacts allowEmptyArchive: true, artifacts: 'Build.sql'
- // Migrate the CI db
- status = bat returnStatus: true, label: "Build", script:" FLYWAY migrate info -url=${CiDatabaseJdbc} -user=${User} -password=${Password} -schemas=${Schema}"
- echo "Status of Running CI build: $status"
- if (status != 0) { error('Running CI build failed') }
- // Execute the code analysis to check for issues
- status = bat returnStatus: true, label: "Code Analysis", script:"${CODEANALYSIS} /source:migrations /outfile:CodeAnalysisResults.html /IAgreeToTheEula"
- archiveArtifacts allowEmptyArchive: true, artifacts:'CodeAnalysisResults.html'
- echo "Status of Running CI build: $status"
- if (status != 0) {
- if (status == 1) { error('Redgate Code Analysis for Oracle failed to complete due to a fatal error') }
- else if (status == 2) {
- status = 1
- echo "Redgate Code Analysis for Oracle only partially completed - often this is because one or more SQL files could not be parsed (such files are currently excluded from the generated reports)"
- }
- else if (status == 3) { error('Redgate Code Analysis for Oracle ran successfully but reported one or more errors in the analyzed SQL files') }
- else if (status == 4) {
- status = 1
- echo "Redgate Code Analysis for Oracle ran successfully but reported one or more warnings in the analyzed SQL files"
- }
- else { error('Code Analysis failed') }
- }
- // Take a snapshot of the CI db after deploying to it to use in change reports for downstream environments
- // Comparing the target environment to this snapshot will show an object level report of the expected changes
- status = bat returnStatus: true, label: "Snapshot", script:"${SQLCOMPARE} /i:sdwgvac /b:hdrg /forceservicename /source:${User}/${Password}@localhost/CI{${Schema}} /snp:BuildStatePostDeploy.onp"
- if (status != 0) { error('Failed to create a snapshot of the CI database after deploying to it') }
- archiveArtifacts allowEmptyArchive: true, artifacts:'BuildStatePostDeploy.onp'
- }
- stage ('Unit Tests') {
- echo "Running utPLSQL database Unit Tests"
- /*
- // status = bat returnStatus: true, label: "Unit Tests", script: "exec ut.Run();"
- echo "Status of utPLSQL database Unit Tests: $status"
- if (status != 0) { error('ERROR: Running Unit Tests failed') }
- status = junit allowEmptyResults: true, testResults: 'testResults.xml'
- echo "Failed JUnit tests: $status.failCount"
- archiveArtifacts allowEmptyArchive: true, artifacts: 'testResults.xml'
- zip zipFile: 'codeCoverage.zip', archive: true, glob: 'codeCoverage.html, codeCoverage.html_assets/*'
- archiveArtifacts allowEmptyArchive: true, artifacts: 'codeCoverage.zip', fingerprint: true
- */
- }
- stage ('Deploy to Acceptance') {
- echo "Deploying project to Acceptance Database"
- status = bat returnStatus: true, label: "Acceptance Deployment report", script:"${SQLCOMPARE} /i:sdwgvac /b:hdrg /forceservicename /source:BuildStatePostDeploy.onp{${Schema}} /target:${User}/${Password}@localhost/Acceptance{${Schema}} /report:Acceptance_schema_changes.html /reportType=interactive"
- archiveArtifacts allowEmptyArchive: true, artifacts: 'Acceptance_schema_changes.html'
- status = bat returnStatus: true, label: "Acceptance DryRun", script:" FLYWAY migrate -dryRunOutput=\"${OutputDirectory}/workspace/Acceptance.sql\" -url=${AcceptanceDatabaseJdbc} -user=${User} -password=${Password} -schemas=${Schema}"
- echo "Generating the dryrun script for Acceptance: $status"
- if (status != 0) { error('Generating Dryrun script for Acceptance failed') }
- status = bat returnStatus: true, label: "Acceptance Deployment", script:" FLYWAY migrate info -url=${AcceptanceDatabaseJdbc} -user=${User} -password=${Password} -schemas=${Schema}"
- echo "deploying to Acceptance: $status"
- if (status != 0) { error('Deployment to Acceptance failed') }
- archiveArtifacts allowEmptyArchive: true, artifacts: 'Acceptance.sql'
- archiveArtifacts allowEmptyArchive: true, artifacts: '**/Acceptance_schema_changes_images/**'
- }
- stage ('Approval Gate'){
- def message = "Approve release to Production?"
- // wrapping in a time out so it does not block the agent and simply fails the build if there is no user intervention.
- 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') {
- echo "Deploying to Production Database"
- // Create a pre-deployment snapshot of Production
- status = bat returnStatus: true, label: "Production snapshot", script:"${SQLCOMPARE} /i:sdwgvac /b:hdrg /forceservicename /source:${User}/${Password}@localhost/Production{${Schema}} /snp:ProductionStatePreDeploy.onp"
- if (status != 0) { error('Generating Production snapshot failed') }
- archiveArtifacts allowEmptyArchive: true, artifacts: 'ProductionStatePreDeploy.onp'
- // Checking for Production drift by using the Build snapshot from the previous build
- // more logic needed to get latest successful build or possibly skip with warning/input from user if not found
- Integer previousBuildNumber = 0
- previousBuildNumber = "$BUILD_NUMBER" as Integer
- previousBuildNumber = previousBuildNumber - 1
- status = bat returnStatus: true, label: "Production drift check", script:"${SQLCOMPARE} /i:sdwgvac /b:hdrg /forceservicename /source:../builds/$previousBuildNumber/archive/BuildStatePostDeploy.onp{${Schema}} /target:ProductionStatePreDeploy.onp{${Schema}} /report=ProductionDriftReport.html /reportType=interactive"
- echo "Checking for Production Drift using /builds/$previousBuildNumber/archive/BuildStatePostDeploy.onp: $status"
- if (status == 61) {
- // Stop the build because Production has drifted or get ok from user to continue
- archiveArtifacts allowEmptyArchive: true, artifacts: 'ProductionDriftReport.html'
- archiveArtifacts allowEmptyArchive: true, artifacts: '**/ProductionDriftReport_images/**'
- error('Production has drifted since the last deployment')
- }
- // Create a change report for Production
- status = bat returnStatus: true, label: "Production Deployment report", script:"${SQLCOMPARE} /i:sdwgvac /b:hdrg /forceservicename /source:BuildStatePostDeploy.onp{${Schema}} /target:ProductionStatePreDeploy.onp{${Schema}} /report:Production_schema_changes.html /reportType=interactive"
- echo "Generating the change report for Production: $status"
- if (status != 0 && status != 61) { error('ERROR: Generating Production change report failed') } // 0 = no diffs found; 61 = diffs found
- archiveArtifacts allowEmptyArchive: true, artifacts: 'Production_schema_changes.html'
- archiveArtifacts allowEmptyArchive: true, artifacts: '**/Production_schema_changes_images/**'
- // Create the dry run script
- status = bat returnStatus: true, label: "Production DryRun", script:" FLYWAY migrate -dryRunOutput=\"${OutputDirectory}/workspace/Production.sql\" -url=${ProductionDatabaseJdbc} -user=${User} -password=${Password} -schemas=${Schema}"
- echo "Generating the dryrun script for Production: $status"
- if (status != 0) { error('Generating Dryrun script for Production failed') }
- archiveArtifacts allowEmptyArchive: true, artifacts: 'Production.sql'
- // Deploy to Production
- status = bat returnStatus: true, label: "Production Deployment", script:" FLYWAY migrate info -url=${ProductionDatabaseJdbc} -user=${User} -password=${Password} -schemas=${Schema}"
- echo "deploying to Production: $status"
- if (status != 0) { error('Deployment to Production failed') }
- // Generate a rollback script for Production
- status = bat returnStatus: true, label: "Production rollback script", script:"${SQLCOMPARE} /i:sdwgvac /b:hdrg /forceservicename /source:ProductionStatePreDeploy.onp{${Schema}} /target:BuildStatePostDeploy.onp{${Schema}} /scriptfile:Production_rollback_script.sql"
- echo "Generating the rollback script for Production: $status"
- //if (status != 0 && status != 61) { error('ERROR: Generating Production rollback script report failed') }
- archiveArtifacts allowEmptyArchive: true, artifacts: 'Production_rollback_script.sql'
- }
- }