Today i encountered an anomaly when setting up a Jenkins pipeline script which checks both the quality gate
and annotates pull requests with any new issues.
Our setup contains of:
SonarQube 6.2
BitBucket Stash
Jenkins 2 (with 2 slaves)
AmadeusITGroup stash plugin
Part of the pipeline script:
node(node_label) {
stage("SonarQube analysis") {
withSonarQubeEnv('SonarQube') {
def sonarQubeCommand = "org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar " +
"-Dsonar.host.url=https://sonar-url " +
"-Dsonar.login=sonarqube " +
"-Dsonar.password=token " +
"-Dsonar.language=java " +
"-Dsonar.sources=. " +
"-Dsonar.inclusions=**/src/main/java/**/*"
if (pr.id != '') {
sonarQubeCommand = sonarQubeCommand +
" -Dsonar.analysis.mode=preview" +
" -Dsonar.stash.notification=true " +
" -Dsonar.stash.project=" + pr.project_key +
" -Dsonar.stash.repository=" + pr.repository_slug +
" -Dsonar.stash.pullrequest.id=" + pr.id +
" -Dsonar.stash.password=token"
}
pipeline.mvn(sonarQubeCommand)
}
}
}
stage("Check Quality Gate") {
timeout(time: 1, unit: 'HOURS') {
def qg = waitForQualityGate()
waitUntil {
// Sometimes an analysis will get the status PENDING meaning it still needs to be analysed.
if (qg.status == 'PENDING') {
qg = waitForQualityGate()
return false
} else {
return true
}
}
node(node_label) {
if (qg.status != 'OK') {
bitbucket.comment(pr, "_${env.JOB_NAME}#${env.BUILD_NUMBER}:_ **[✖ BUILD FAILURE](${build_url})**")
bitbucket.approve(pr, false)
pipeline.cleanWorkspace()
error "Pipeline aborted due to quality gate failure: ${qg.status}"
} else {
bitbucket.comment(pr, "_${env.JOB_NAME}#${env.BUILD_NUMBER}:_ **[✔ BUILD SUCCESS](${build_url})**")
bitbucket.approve(pr, true)
}
}
}
}
Btw: Without the waitUntil the pipeline failed because the task status was PENDING in SonarQube.
So the example in the SonarSource blog didn't quite work for me.
Now for the details how this pipeline fails:
When using sonar.analysis.mode=preview as parameter on the maven command
the jenkins job log will not contain the SonarQube analysis task id.
This will result in a failure in the pipeline script on command waitForQualityGate.
The message reads:
Unable to get SonarQube task id and/or server name. Please use the 'withSonarQubeEnv' wrapper to run your analysis.
As soon as i remove the sonar.analysis.mode=preview parameter the jenkins log reads a line like:[INFO] More about the report processing at https://sonar-url/api/ce/task?id=AVyHXjcsesZZZhqzzCSf
This line makes the waitForQualityGate command succeed normally.
However, this has an unwanted side effect besides the polution of the project in SonarQube with PR results.
The side effect is that when an issue was added in the pull request this won't be reported on the pull request in stash.
It always reports zero new issues and this is clearly wrong.
As it's not a preview analysis anymore i can see the new issue on the SonarQube server.
So somehow i have to make a choice now between having pull requests annotated with new issues or
checking the quality gate.
Obviously i would like to do both.
I have chosen to let pull request annotation work properly and skip the check on the quality gate for now.
Question remains am i doing something wrong here or do i have to wait for new versions of scanner and/or stash plugin to have this resolved?
At sonar's server go to:
Administration > Configuration > General Settings > Webhooks:
Name: Jenkins or something like that
URL: http://127.0.0.1:8080/sonarqube-webhook/
Where, URL is Jenkins host.
Cause:
By default webkooks on sonar are https, and my jenkins was working over http.
Related
Using Octopus Deploy to deploy a simple API.
The first step of our deployment process is to generate an HTML report with the delta of the scripts run vs the scripts required to run. I used this tutorial to create the step.
The relevant code in my console application is:
var reportLocationSection = appConfiguration.GetSection(previewReportCmdLineFlag);
if (reportLocationSection.Value is not null)
{
// Generate a preview file so Octopus Deploy can generate an artifact for approvals
try
{
var report = reportLocationSection.Value;
var fullReportPath = Path.Combine(report, deltaReportName);
Console.WriteLine($"Generating upgrade report at {fullReportPath}");
upgrader.GenerateUpgradeHtmlReport(fullReportPath);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return operationError;
}
}
The Powershell which I am using in the script step is:
# Get the extracted path for the package
$packagePath = $OctopusParameters["Octopus.Action.Package[DatabaseUpdater].ExtractedPath"]
$connectionString = $OctopusParameters["Project.Database.ConnectionString"]
$reportPath = $OctopusParameters["Project.HtmlReport.Location"]
Write-Host "Report Path: $($reportPath)"
$exeToRun = "$($packagePath)\DatabaseUpdater.exe"
$generatedReport = "$($reportPath)\UpgradeReport.html"
Write-Host "Generated Report: $($generatedReport)"
if ((test-path $reportPath) -eq $false){
New-Item "Creating new directory..."
} else {
New-Item "Directory already exists."
}
# Run this .NET app, passing in the Connection String and a flag
# which tells the app to create a report, but not update the database
& $exeToRun --connectionString="$($connectionString)" --previewReportPath="$($reportPath)"
New-OctopusArtifact -Path "$($generatedReport)"
The error reported by Octopus is:
'Could not find file 'C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html'.'
I'm guessing that is being thrown when this powershell line is hit: New-OctopusArtifact ...
And that seems to indicate that the report was never created.
I've used a bit of logging to log out certain variables and the values look sound:
Report Path: C:\DeltaReports\Some API\2.9.15-DbUp-Test-9
Generated Report: C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html
Generating upgrade report at C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html
As you can see in the C#, the relevant code is wrapped in a try/catch block, but I'm not sure whether the error is being written out there or at a later point by Octopus (I'd need to do a pull request to add a marker in the code).
Can anyone see a way forward win resolving this? Has anyone else encountered this?
Cheers
I recently redid some of the work from that article for this video up on YouTube. I did run into some issues with the .SQL files not being included in the assembly. I think it was after I upgraded to .NET 6. But that might be a coincidence.
Anyway, because the files weren't being included in the assembly, when I ran the command line app via Octopus, it wouldn't properly generate the file for me. I ended up configuring the project to copy the .SQL files to a folder in the output directory instead of embedding them in the assembly. You can view a sample package here.
One thing that helped me is running the app in a debugger with the same parameters just to make sure it was actually generating the file. I'm sure you already thought of that, but I'd be remiss if I forgot to include it in my answer. :)
FWIW, this is my updated scripts.
First, the Octopus Script:
$packagePath = $OctopusParameters["Octopus.Action.Package[Trident.Database].ExtractedPath"]
$connectionString = $OctopusParameters["Project.Connection.String"]
$environmentName = $OctopusParameters["Octopus.Environment.Name"]
$reportPath = $OctopusParameters["Project.Database.Report.Path"]
cd $packagePath
$appToRun = ".\Octopus.Trident.Database.DbUp"
$generatedReport = "$reportPath\UpgradeReport.html"
& $appToRun --ConnectionString="$connectionString" --PreviewReportPath="$reportPath"
New-OctopusArtifact -Path "$generatedReport" -Name "$environmentName.UpgradeReport.html"
My C# code can be found here but for ease of use, you can see it all here (I'm not proud of how I parse the parameters).
static void Main(string[] args)
{
var connectionString = args.FirstOrDefault(x => x.StartsWith("--ConnectionString", StringComparison.OrdinalIgnoreCase));
connectionString = connectionString.Substring(connectionString.IndexOf("=") + 1).Replace(#"""", string.Empty);
var executingPath = Assembly.GetExecutingAssembly().Location.Replace("Octopus.Trident.Database.DbUp", "").Replace(".dll", "").Replace(".exe", "");
Console.WriteLine($"The execution location is {executingPath}");
var deploymentScriptPath = Path.Combine(executingPath, "DeploymentScripts");
Console.WriteLine($"The deployment script path is located at {deploymentScriptPath}");
var postDeploymentScriptsPath = Path.Combine(executingPath, "PostDeploymentScripts");
Console.WriteLine($"The deployment script path is located at {postDeploymentScriptsPath}");
var upgradeEngineBuilder = DeployChanges.To
.SqlDatabase(connectionString, null)
.WithScriptsFromFileSystem(deploymentScriptPath, new SqlScriptOptions { ScriptType = ScriptType.RunOnce, RunGroupOrder = 1 })
.WithScriptsFromFileSystem(postDeploymentScriptsPath, new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 2 })
.WithTransactionPerScript()
.LogToConsole();
var upgrader = upgradeEngineBuilder.Build();
Console.WriteLine("Is upgrade required: " + upgrader.IsUpgradeRequired());
if (args.Any(a => a.StartsWith("--PreviewReportPath", StringComparison.InvariantCultureIgnoreCase)))
{
// Generate a preview file so Octopus Deploy can generate an artifact for approvals
var report = args.FirstOrDefault(x => x.StartsWith("--PreviewReportPath", StringComparison.OrdinalIgnoreCase));
report = report.Substring(report.IndexOf("=") + 1).Replace(#"""", string.Empty);
if (Directory.Exists(report) == false)
{
Directory.CreateDirectory(report);
}
var fullReportPath = Path.Combine(report, "UpgradeReport.html");
if (File.Exists(fullReportPath) == true)
{
File.Delete(fullReportPath);
}
Console.WriteLine($"Generating the report at {fullReportPath}");
upgrader.GenerateUpgradeHtmlReport(fullReportPath);
}
else
{
var result = upgrader.PerformUpgrade();
// Display the result
if (result.Successful)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Success!");
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(result.Error);
Console.WriteLine("Failed!");
}
}
}
I hope that helps!
After long and detailed investigation, we discovered the answer was quite obvious.
We assumed the existing deploy process configuration was sound. Because we never had a problem with it (until now). As it transpires, there was a problem which led to the Development deployments being deployed twice.
Hence, the errors like the one above and others which talked about file handles being held by another process.
It was actually obvious in hindsight, but we were blind to it as we thought the existing process was sound 😣
I have established Quality Gate for my Jenkins project via SonarQube. One of my projects have no tests at all, so in the analysis I see that the code coverage is 0%. By the quality gate rules (<60% coverage = fail) my pipeline should return an error. However, this does not happen. The quality gate says that the analysis was a success and quality gate is 'OK'. In another project, I removed some tests to make coverage be <60% and the quality gate passed once again, even though it was meant to fail.
I had an error related to the analysis always returning 0% coverage before, but managed to fix it (with help from this link). Found a lot of articles with the similar questions but with no answers on any of them. This post looks promising but I cannot find the suitable alternative to its suggestion.
It is worth mentioning that the analysis stage is done in parallel with another stage (to save some time). The Quality Gate stage comes shortly afterwards.
The relevant code I use to initialise the analysis for my project is (org.jacoco... bit is the solution to 0% coverage error I mentioned above):
sh "mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent verify sonar:sonar -Dsonar.host.url=${env.SONAR_HOST_URL} -Dsonar.login=${env.SONAR_AUTH_TOKEN} -Dsonar.projectKey=${projectName} -Dsonar.projectName=${projectName} -Dsonar.sources=. -Dsonar.java.binaries=**/* -Dsonar.language=java -Dsonar.exclusions=$PROJECT_DIR/src/test/java/** -f ./$PROJECT_DIR/pom.xml"
The full quality gate code (to clarify how my quality gate starts and finishes):
stage("Quality Gate") {
steps {
timeout(time: 15, unit: 'MINUTES') { // If analysis takes longer than indicated time, then build will be aborted
withSonarQubeEnv('ResearchTech SonarQube'){
script{
// Workaround code, since we cannot have a global webhook
def reportFilePath = "target/sonar/report-task.txt"
def reportTaskFileExists = fileExists "${reportFilePath}"
if (reportTaskFileExists) {
def taskProps = readProperties file: "${reportFilePath}"
def authString = "${env.SONAR_AUTH_TOKEN}"
def taskStatusResult =
sh(script: "curl -s -X GET -u ${authString} '${taskProps['ceTaskUrl']}'", returnStdout: true)
//echo "taskStatusResult[${taskStatusResult}]"
def taskStatus = new groovy.json.JsonSlurper().parseText(taskStatusResult).task.status
echo "taskStatus[${taskStatus}]"
if (taskStatus == "SUCCESS") {
echo "Background tasks are completed"
} else {
while (true) {
sleep 10
taskStatusResult =
sh(script: "curl -s -X GET -u ${authString} '${taskProps['ceTaskUrl']}'", returnStdout: true)
//echo "taskStatusResult[${taskStatusResult}]"
taskStatus = new groovy.json.JsonSlurper().parseText(taskStatusResult).task.status
echo "taskStatus[${taskStatus}]"
if (taskStatus != "IN_PROGRESS" && taskStatus != "PENDING") {
break;
}
}
}
} else {
error "Haven't found report-task.txt."
}
def qg = waitForQualityGate() // Waiting for analysis to be completed
if(qg.status != 'OK'){ // If quality gate was not met, then present error
error "Pipeline aborted due to quality gate failure: ${qg.status}"
}
}
}
}
}
}
What is shown in the SonarQube UI for the project? Does it show that the quality gate failed, or not?
I don't quite understand what you're doing in that pipeline script. It sure looks like you're calling "waitForQualityGate()" twice, but only checking for error on the second call. I use scripted pipeline, so I know it would look slightly different.
Update:
Based on your additional comment, if the SonarQube UI says that it passed the quality gate, then that means there's nothing wrong with your pipeline code (at least with respect to the quality gate). The problem will be in the definition of your quality gate.
However, I would also point out one other error in how you're checking for the background task results.
The possible values of "taskStatus" are "SUCCESS", "ERROR", "PENDING", and "IN_PROGRESS". If you need to determine whether the task is still running, you have to check for either of the last two values. If you need to determine whether the task is complete, you need to check for either of the first two values. You're checking for completion, but you're only checking for "SUCCESS". That means if the task failed, which it would if the quality gate failed (which isn't happening here), you would continue to wait for it until you timed out.
Here is my custom command:
exports.command = function (element, time, debug) {
let waitTime = time || 10000
if (debug) {
return this
.log('waiting ' + waitTime + 'ms for: ' + element)
.waitForElementVisible(element, waitTime)
}
return this
.waitForElementVisible(element, waitTime)
}
I have also set this variable in the globalModules: abortOnFailure: true.
When I call this in a pageObject though like this:
findElement() {
this.waitFor('#driversLicenseNumbers');
return this
}
The object isn't found (which is expected and intended since I'm upgrading to Nightwatch v1.0.14) and the error message is logged to the console, but the test doesn't fail.
× Timed out while waiting for element <#driversLicenseNumbers> to be
present for 10000 milliseconds. - expected "visible" but got: "not
found"
Does anyone know what I'm doing wrong here?
There is already an open issue on the Nightwatch issues board regarding this specific problem. Here it is!
This behavior affects custom_commands in nightwatch#1.0.15 & nightwatch#0.9.21 (according to the BUG report, yet I am running nightwatch#0.9.21 & this behavior is not reproducible for me).
Basically your test fails, but it does so silently, at the end of the test, where you get the timeout error.
Proposed fix: Install a different version (npm install --save-dev nightwatch#0.9.x), or a suitable version that hasn't introduced the defect yet.
Cheers!
I am in the process of cleaning up Jenkins (it was setup incorrectly) and I need to delete builds that are older than the latest 20 builds for every job.
Is there any way to automate this using a script or something?
I found many solutions to delete certain builds for specific jobs, but I can't seem to find anything for all jobs at once.
Any help is much appreciated.
You can use the Jenkins Script Console to iterate through all jobs, get a list of the N most recent and perform some action on the others.
import jenkins.model.Jenkins
import hudson.model.Job
MAX_BUILDS = 20
for (job in Jenkins.instance.items) {
println job.name
def recent = job.builds.limit(MAX_BUILDS)
for (build in job.builds) {
if (!recent.contains(build)) {
println "Preparing to delete: " + build
// build.delete()
}
}
}
The Jenkins Script Console is a great tool for administrative maintenance like this and there's often an existing script that does something similar to what you want.
I got an issue No such property: builds for class: com.cloudbees.hudson.plugins.folder.Folder on Folders Plugin 6.6 while running #Dave Bacher's script
Alter it to use functional api
import jenkins.model.Jenkins
import hudson.model.Job
MAX_BUILDS = 5
Jenkins.instance.getAllItems(Job.class).each { job ->
println job.name
def recent = job.builds.limit(MAX_BUILDS)
for (build in job.builds) {
if (!recent.contains(build)) {
println "Preparing to delete: " + build
build.delete()
}
}
}
There are lots of ways to do this
Personally I would use the 'discard old builds' in the job config
If you have lots of jobs you could use the CLI to step through all the jobs to add it
Alternatively there is the configuration slicing plugin which will also do this for you on a large scale
For Multibranch Pipelines, I modified the script by Dave Bacher a bit. Use this to delete builds older than the latest 20 build of "master" branches:
MAX_BUILDS = 20
for (job in Jenkins.instance.items) {
if(job instanceof jenkins.branch.MultiBranchProject) {
job = job.getJob("master")
def recent = job.builds.limit(MAX_BUILDS)
for (build in job.builds) {
if (!recent.contains(build)) {
println "Preparing to delete: " + build
// build.delete()
}
}
}
}
This can be done in many ways. You can try the following
get all your job names in a textfile by going to the jobs location in jenkins and run the following
ls >jobs.txt
Now you can write a shell script with a for loop
#!/bin/bash
##read the jobs.txt
for i in 'cat <pathtojobs.txt>'
do
curl -X POST http://jenkins-host.tld:8080/jenkins/job/$i/[1-9]*/doDeleteAll
done
the above deletes all the jobs
you can also refer here for more answers
I had issues running the suggestions on my Jenkins instance. It could be because it is dockerized. In any case, removing the folder beforehand using the underlying bash interpreter fixes the issue. I also modified the script to keep 180 days of build logs and keep a minimum of 7 build logs:
import jenkins.model.Jenkins
import hudson.model.Job
MIN_BUILD_LOGS = 7
def sixMonthsAgo = new Date() - 180
Jenkins.instance.getAllItems(Job.class).each { job ->
println job.getFullDisplayName()
def recent = job.builds.limit(MIN_BUILD_LOGS)
def buildsToDelete = job.builds.findAll {
!recent.contains(it) && ! (it.getTime() > sixMonthsAgo)
}
if (!buildsToDelete) {
println "nothing to do"
}
for (build in buildsToDelete) {
println "Preparing to delete: " + build + build.getTime()
["bash", "-c", "rm -r " + build.getRootDir()].execute()
build.delete()
}
}
"done"
I am using Parameterized Trigger Plugin to trigger a downstream build.
How do I specify that my upstream job should fail if the downstream fails? The upstream job is actually is dummy job with parameters being passed to the downstream.
Make sure you are using the correct step to execute your downstream jobs; I discovered that since I was executing mine as a "post build step", I didn't have the "Block until the triggered projects finish their builds" option. Changing that to "build task" as opposed to a "post build task", allowed me to find the options you are looking for within the Parameterized Trigger Plugin.
this code will mark the upstream build unstable/failed based on downstream job status.
/*************************************************
Description: This script needs to put in Groovy
Postbuild plugin of Jenkins as a Post Build task.
*************************************************/
import hudson.model.*
void log(msg) {
manager.listener.logger.println(msg)
}
def failRecursivelyUsingCauses(cause) {
if (cause.class.toString().contains("UpstreamCause")) {
def projectName = cause.upstreamProject
def number = cause.upstreamBuild
upstreamJob = hudson.model.Hudson.instance.getItem(projectName)
if(upstreamJob) {
upbuild = upstreamJob.getBuildByNumber(number)
if(upbuild) {
log("Setting to '" + manager.build.result + "' for Project: " + projectName + " | Build # " + number)
//upbuild.setResult(hudson.model.Result.UNSTABLE)
upbuild.setResult(manager.build.result);
upbuild.save()
// fail other builds
for (upCause in cause.upstreamCauses) {
failRecursivelyUsingCauses(upCause)
}
}
} else {
log("No Upstream job found for " + projectName);
}
}
}
if(manager.build.result.isWorseOrEqualTo(hudson.model.Result.UNSTABLE)) {
log("****************************************");
log("Must mark upstream builds fail/unstable");
def thr = Thread.currentThread()
def build = thr.executable
def c = build.getAction(CauseAction.class).getCauses()
log("Current Build Status: " + manager.build.result);
for (cause in c) {
failRecursivelyUsingCauses(cause)
}
log("****************************************");
}
else {
log("Current build status is: Success - Not changing Upstream build status");
}
Have a look at the following response: Fail hudson build with groovy script. You can get access to the upstream job and fail its build BUT... be careful with the fact that Hudson/Jenkins post-build actions right now do not allow to specify any ordering: if your groovy script is specified besides other post-build actions, and those actions affect the result of the build (i.e.: parsing of test results), then you won't be able to update the status of the upstream job if Jenkins decides to run them after your groovy script.
Under Build step configure Trigger/Call builds on other projects, choose the downstream job. Select "Block until triggered project finish their build". Save the default settings under it. This settings will make upstream job failed is downstream is failed.