Skip to content

Instantly share code, notes, and snippets.

@JPRuskin
Last active November 27, 2023 20:03
Show Gist options
  • Save JPRuskin/04726fcbf1b9855acc374ff100b70e07 to your computer and use it in GitHub Desktop.
Save JPRuskin/04726fcbf1b9855acc374ff100b70e07 to your computer and use it in GitHub Desktop.
Remove old releases from a Nexus3-repository, originally from PhilSwiss/nexus-cleanup

Nexus Cleanup Logo

Nexus-Cleanup

Remove old releases from a Nexus3-repository

This Groovy-script will purge old versions (sorted by version-number) from a repository hosted on Nexus 3.x.

You can set the desired repository and a maximum amount of versions at the begining of this script.

Optional: define a retention (in days), so that old versions are only deleted if they are older than the retention.

Information

After upgrading from Nexus 2.x to Nexus 3.x, the build-in function for keeping the latest X releases was sadly gone. Ofcourse there is an ongoing feature-request for that.

I tried the script by Matt Harrison from StackOverflow for this problem, but the migration-tool in Nexus had reset all last_updated-values to the date of the migration, sad again.

Now I tried to sort the releases by version via 'ORDER BY version DESC', but that resulted in a mess, where a version 3.9.0 is newer than 3.11.0 and so on, not a suitable scenario.

In the end, I forked Matt Harrison's script and the nice logoutput by Neil201

Added some helper-lists and the VersionComperator (a version sorter) by Rob Friesel

Finally, I linted the script with npm-groovy-lint to get a cleaner code, somehow

I later included the retention-feature kindly provided by agronlun and some cleanup/optimization by emetriqChris

Installation

  1. add the line nexus.scripts.allowCreation=true to $data-dir/etc/nexus.properties
  2. restart the Nexus-service, to apply the changes
  3. in the webinterface of Nexus, go to Administration > Tasks and create a new Execute script-task
  4. configure the task by filling out the required fields (Task name, Task frequency, etc.)
  5. copy 'n' paste all lines from nexus-cleanup.groovy into the Source-field
  6. disable further script creation, by adding a leading #-character to the line from step 1.
  7. restart the Nexus-service, once again

More information about setting up custom scripts, can be found in this article from the Sonatype Support Knowledge Base.

Disclaimer

There is no warranty for the script or it's functionality, use it at your own risk.

No licensing at the moment, because this script is put together from various sites with different licensing schemes.

The icon/logo consists of the Nexus Logo © by Sonatype Inc. & a Broom Symbol © by HiClipart.

Bug tracker

If you have any suggestions, bug reports or annoyances please report them to the issue tracker at:

https://github.com/PhilSwiss/nexus-cleanup/issues

Contributing

Development of nexus-cleanup happens at GitHub: https://github.com/PhilSwiss/nexus-cleanup

//
// nexus-cleanup.groovy by P.Schweizer / last changed 22.06.2021
//
// A script to remove old releases from a Nexus3-repository,
// - https://www.sonatype.com/nexus-repository-oss
// A fork of purge-old-release-from-nexus-3 by Matt Harrison
// - https://stackoverflow.com/a/45894920
// Logging forked from the script by Neil201
// - https://stackoverflow.com/a/57604767
// VersionComparator (a sorter for version-numbers) by Rob Friesel
// - https://gist.github.com/founddrama/971284)
//
// Changelog:
// - ignoring the sorting "ORDER BY last_updated" because dates where reset by
// Nexus during migration from Nexus 2.x to Nexus 3.x
// - added 3 lists (foundVersions, sortedVersions & removeVersions) for keeping
// the versions per component in a sortable format
// - added sorting with Rob Friesel's version comperator, this sorter handles
// textstrings and sorts a release "1.0.14" higher than "1.0.2"
// - components will now be deleted if they appear in the removeVersions-list
// - added more logoutput, forked from the script by Neil201
// - added more documentation to the code
// - code cleaned up (as much as possible) by using npm-groovy-lint
// - fixed the links for the scripts where this script was forked from
// - added function for retention by agronlun
// - optimized the code, thanks to agronlun and emetriqChris
//
// Imports for the API
import org.sonatype.nexus.repository.storage.StorageFacet
import org.sonatype.nexus.repository.storage.Query
import org.joda.time.DateTime
// Configuration
String[] repositoriesToMaintain = ['choco-internal-testing', 'chocolatey-website'] // Name of your nexus-repository(s)
def maxArtifactCount = ['choco-internal-testing': 15, 'chocolatey-website': 30]
def retentionDays = 0 // Delete surplus artifacts only when older than X (0 = disable)
def ignoreStable = true // If set, ignores versions without a semver suffix (-+)
def dryRun = true // If set, does not delete anything
// VersionComperator (Sorter) by Rob Friesel
def versionComparator = { comperatorA, comperatorB ->
def VALID_TOKENS = /.-_/
a = comperatorA.tokenize(VALID_TOKENS)
b = comperatorB.tokenize(VALID_TOKENS)
for (i in 0..<Math.max(a.size(), b.size())) {
if (i == a.size()) {
return b[i].isInteger() ? -1 : 1
} else if (i == b.size()) {
return a[i].isInteger() ? 1 : -1
}
if (a[i].isInteger() && b[i].isInteger()) {
int c = (a[i] as int) <=> (b[i] as int)
if (c != 0) {
return c
}
} else if (a[i].isInteger()) {
return 1
} else if (b[i].isInteger()) {
return -1
} else {
int c = a[i] <=> b[i]
if (c != 0) {
return c
}
}
}
return 0
}
// Get a date
def retentionDate = DateTime.now().minusDays(retentionDays).dayOfMonth().roundFloorCopy()
log.info('==================================================')
log.info(':::Cleanup script started...')
for ( repositoryName in repositoriesToMaintain ) {
log.info('==================================================')
log.info(":::Operating on Repository: ${repositoryName}")
log.info('==================================================')
// Get a repository
def repo = repository.repositoryManager.get(repositoryName)
// Get a database transaction
def tx = repo.facet(StorageFacet).txSupplier().get()
try {
// Begin the transaction
tx.begin()
// Init
int totalDelCompCount = 0
def previousComponent = null
def uniqueComponents = []
// Get a collection of all components incl. their group
tx.findComponents(Query.builder().suffix(' ORDER BY group, name').build(), [repo]).each { component ->
if (previousComponent == null || (!component.group().equals(previousComponent.group()) || !component.name().
equals(previousComponent.name()))) {
uniqueComponents.add(component)
}
previousComponent = component
}
// Get a collection of all informations from a component
uniqueComponents.each { uniqueComponent ->
def componentVersions = tx.findComponents(Query.builder().
where('name = ').param(uniqueComponent.name()).suffix(' ORDER BY last_updated DESC').build(), [repo])
log.info("Processing Component: ${uniqueComponent.group()}, ${uniqueComponent.name()}")
// Get a list of all version-numbers from the collection
def foundVersions = []
componentVersions.eachWithIndex { component, index ->
if ( "${component.version()}" ==~ /.*-.*/ || !ignoreStable ) {
foundVersions.add(component.version())
}
}
log.info("Found Versions: ${foundVersions}")
// Get a sorted list of all version-numbers (with the VersionComperator)
sortedVersions = foundVersions.sort(versionComparator)
log.info("Sorted Versions: ${sortedVersions}")
// Get a list of all surplus version-numbers
surplusVersions = sortedVersions.dropRight(maxArtifactCount[repositoryName])
// Get a list of all surplus version-numbers older than retention date by agronlun
def removeVersions = []
componentVersions.eachWithIndex { component, index ->
if (component.version() in surplusVersions) {
def lastUpdateDate = component.lastUpdated()
if (lastUpdateDate == null) {
log.warn("lastUpdated not found: ${component.name()} ${component.version()}")
} else {
if (lastUpdateDate.isBefore(retentionDate)) {
removeVersions.add(component.version())
} else {
log.info("Version before retention: ${component.version()} - ${component.lastUpdated()}")
}
}
}
}
log.info("Remove Versions: ${removeVersions}")
// Count total amount of surplus version-numbers
totalDelCompCount = totalDelCompCount + removeVersions.size()
log.info("Component Total Count: ${componentVersions.size()}")
log.info("Component Remove Count: ${removeVersions.size()}")
// If there are surplus versions for the component, delete them
if (removeVersions.size() > 0) {
componentVersions.eachWithIndex { comp, index ->
if (comp.version() in removeVersions) {
def lastUpdated = comp.lastUpdated()
log.info("Deleting Component (${!dryRun}): ${comp.name()} ${comp.version()} - ${lastUpdated}")
if ( !dryRun ) {
tx.deleteComponent(comp);
}
}
}
}
log.info('==================================================')
}
// Show statistics at the end
log.info(" *** Total Deleted Component Count: ${totalDelCompCount} *** ")
log.info('==================================================')
} finally {
// End the transaction and close it
tx.commit()
tx.close()
}
}
@gep13
Copy link

gep13 commented Aug 12, 2021

Minor typo - begining

@gep13
Copy link

gep13 commented Aug 12, 2021

Minor typo - Ofcourse

@gep13
Copy link

gep13 commented Aug 12, 2021

Minor type - StackOverflow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment