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"
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 😣
So I am sure this is something very dumb mistake, but I need your help since I am not a gradle expert.
TASK:
read versionCode from file add +1 to it and save it back.
task executeOrderSixtySix {
def versionPropsFile = file('versionCodes.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def versionNumber = versionProps['DEV_VERSION'].toInteger() + 1
versionProps['DEV_VERSION'] = versionNumber.toString()
versionProps.store(versionPropsFile.newWriter(), null)
// 'assembleDebug'
} else {
throw new GradleException("Nyeeeh on versionCodes.properties!")
}}
So when I have to do an internal drop I would like to run this task first, increase the devVersion number by 1 and then run the 'assemble' task to build all artifacts.
PROBLEM:
This task executes itself, even if I just sync the cradle file causing increased versionCode all the time.
I don't want to increase the versionCode during sync, development build only just for QAdrop, when I also have to assemble every APK.
Could you please help me out and tell me why is this task getting called/executed and who can I prevent it?
You need a doLast block inside of your task block. build.gradle file is a configuration script so it reads as declare the task when on configuration and declare the action on the execution.
Anything done in the task either before or after the doLast block would be run during configuration time. The code in the doLast block itself runs at execution time.
task executeOrderSixtySix {
doLast {
def versionPropsFile = file('versionCodes.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def versionNumber = versionProps['DEV_VERSION'].toInteger() + 1
versionProps['DEV_VERSION'] = versionNumber.toString()
versionProps.store(versionPropsFile.newWriter(), null)
// 'assembleDebug'
} else {
throw new GradleException("Nyeeeh on versionCodes.properties!")
}
}
}
Ref: https://www.oreilly.com/learning/write-your-own-custom-tasks-in-gradle
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.
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.
i have a new problem in image magick that look strange ..
i'm using mac osx snow leopard and i've installed image magick on it and it's working fine on command ..
but when i call it from the grails class like the following snippet it gives me
"Cannot run program "convert": error=2, No such file or directory"
the code is :-
public static boolean resizeImage(String srcPath, String destPath,String size) {
ArrayList<String> command = new ArrayList<String>(10);
command.add("convert");
command.add("-geometry");
command.add(size);
command.add("-quality");
command.add("100" );
command.add(srcPath);
command.add(destPath);
System.out.println(command);
return exec((String[])command.toArray(new String[1]));
}
private static boolean exec(String[] command) {
Process proc;
try {
//System.out.println("Trying to execute command " + Arrays.asList(command));
proc = Runtime.getRuntime().exec(command);
} catch (IOException e) {
System.out.println("IOException while trying to execute " );
for(int i =0 ; i<command.length; i++) {
System.out.println(command[i]);
}
return false;
}
//System.out.println("Got process object, waiting to return.");
int exitStatus;
while (true) {
try {
exitStatus = proc.waitFor();
break;
} catch (java.lang.InterruptedException e) {
System.out.println("Interrupted: Ignoring and waiting");
}
}
if (exitStatus != 0) {
System.out.println("Error executing command: " + exitStatus);
}
return (exitStatus == 0);
}
i've tried normal command like ls and it's ok so the problem is that grails can't find convert command itself.. is it a os problem or something?
(see lower for the answer)
I have run into the same problem. The issue appears to be something with Mac OS X specifically, as we have several Linux instances running without error. The error looks similar to the following:
java.io.IOException: Cannot run program "/usr/bin/ImageMagick-6.7.3/bin/convert /a/temp/in/tmpPic3143119797006817740.png /a/temp/out/100000726.png": error=2, No such file or directory
All the files are there, and in chmod 777 directories - and as you pointed out, running the exact command from the shell works fine.
My theory at this point is that imagemgick can not load some sort of library itself, and the "no such file" is in reference to an dylib or something along those lines.
I have tried setting LD_LIBRARY_PATH and a few others to no avail.
I finally got this working. Here is how I have it setup. I hope this helps.
The crux of the fix, for me, was I wrapped the 'convert' into a shell script, set a bunch of environment variables, and then call that shell script instead of convert directly:
(convertWrapper.sh)
export MAGICK_HOME=/usr/local/ImageMagick-6.7.5
export MAGICK_CONFIGURE_PATH=${MAGICK_HOME}/etc/ImageMagick:${MAGICK_HOME}/share/doc/ImageMagick/www/source
export PATH=${PATH}:${MAGICK_HOME}/bin
export LD_LIBRARY_PATH=${MAGICK_HOME}/lib:${LD_LIBRARY_PATH}
export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${MAGICK_HOME}/lib
export MAGICK_TMPDIR=/private/tmp
echo "$#" >> /private/tmp/m.log 2>&1
/usr/local/ImageMagick-6.7.5/bin/convert -verbose "$#" >> /private/tmp/m.log 2>&1
(convertWrapper.sh)
Additionally, the convert call was doing some rather complicated stuff, so I added the parameter '-respect-parenthesis' (which may or may not have had an effect).
I am not sure how much of the environment variable setting is needed as I was stabbing in the dark for a while, but since this is only for my development box...
You need to work out what your PATH is set to when you run a command from Java. It must be different to the one you have when running from the terminal.
Are you running Grails (via Tomcat?) as a different user? It might have a different path to your normal user.
you might want to try one of the Image Plugins that are part of the grails ecosystem
http://www.grails.org/ImageTools+plugin
the grails path when the app is running in the server is probably different from running java from the command line
I do so:
Put "convert" file to /usr/bin
Then add to Config.groovy:
gk {
imageMagickPath = "/usr/bin/convert"
}
Then in my ImageService.groovy:
import org.springframework.web.context.request.RequestContextHolder as RCH
[..]
def grailsApplication = RCH.requestAttributes.servletContext.grailsApplication
def imPath = grailsApplication.config.gk.imageMagickPath
def command = imPath + " some_properties"
def proc = Runtime.getRuntime().exec(command)
So this way you get command like: /usr/bin/convert some_properties
And it works, but don't forget to put file "convert" to you location and use it with this location.