Tutorial: Jenkinsfile for Oracle deployments
Published 23 September 2021
In v5, Redgate Change Control has been renamed to Flyway Desktop. Check out the latest documentation at https://documentation.red-gate.com/fd
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
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' } }