Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sasjo/1a883c074c760975f00cb356979b308c to your computer and use it in GitHub Desktop.
Save sasjo/1a883c074c760975f00cb356979b308c to your computer and use it in GitHub Desktop.
Clean up nexus artifacts with API
@GrabResolver(name='mirror', root='http://repository/nexus/content/groups/public')
@Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.7.1')
import groovyx.net.http.*
import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*
import org.apache.http.impl.conn.PoolingClientConnectionManager
import org.apache.http.params.HttpParams
import org.apache.http.client.HttpClient
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.commons.cli.Option
import java.util.concurrent.*
@Grab(group='org.codehaus.gpars', module='gpars', version='1.2.1')
import groovyx.gpars.GParsPool
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
/**
* Script to find and delete artifacts in Nexus.
*
* <p>To scan for artifacts use:</p>
* <pre>
* $ groovy NexusArtifactCleanup.groovy -o file.json -p se/extenda/stdco/ --password secret
* </pre>
* <p>By default, the script will find all release candidate releases in the
* releases repository. Use the <pre>-v</pre> flag to specify the regular
* expression for the version.</p>
*
* <p>To delete artifacts, use a scan results file:</p>
* <pre>
* $ groovy NexusArtifactCleanup.groovy -i file.json --password secret
* </pre>
*/
class NexusArtifactCleanup {
// Settings in which to run script.
def settings = [
baseUrl: 'http://repository/nexus',
repositoryId: 'releases',
nexusUsername: 'admin'
]
// Concurrent RESTClient
def RESTClient nexus
def poolSize = (Runtime.getRuntime().availableProcessors() * 4) - 1
/** CLI main method. */
def static main(def args) {
def cli = new CliBuilder(usage: 'NexusArtifactCleanup.groovy -[oipvaeP]')
cli.with {
o longOpt: 'out', args: 1, argName: 'file', 'Output file for discovered URLs'
i longOpt: 'in', args: 1, argName: 'file', 'File with URLs to delete'
p longOpt: 'path', args: 1, argName: 'path', 'Nexus base path'
v longOpt: 'version', args: 1, argName: 'regex', 'Version pattern'
a longOpt: 'ago', args: 1, argName: 'int', 'Artifact min age since epoc'
e longOpt: 'exclude', args: Option.UNLIMITED_VALUES, argName: 'version', 'Version to exclude'
}
cli._(longOpt: 'password', args: 1, argName: 'pwd', 'Nexus admin password')
def options = cli.parse(args)
if (!options) {
cli.usage()
return
}
if (!options.password) {
println "Missing --password"
cli.usage()
return
}
NexusArtifactCleanup cleanup = new NexusArtifactCleanup(options.password)
if (options.o) {
if (!options.p) {
println "Missing -p"
cli.usage()
return
}
def path = options.p
if (!path.endsWith('/')) {
path += '/'
}
def version = options.v ?: '.*(\\.|-)(RC|BETA|DUMMY)(\\d+)?'
def age = options.a ?: 0
def excludes = options.es ?: []
cleanup.scanArtifacts(options.o, path, version, age, excludes)
} else if (options.i) {
cleanup.cleanupArtifacts(options.i)
}
}
NexusArtifactCleanup(String nexusPassword) {
// Create a concurrent RESTClient.
nexus = new RESTClient(settings.baseUrl) {
protected HttpClient createClient(HttpParams params) {
PoolingClientConnectionManager cm = new PoolingClientConnectionManager()
// Increase max total connection
cm.setMaxTotal(300)
// Increase default max connection per route
cm.setDefaultMaxPerRoute(30)
return new DefaultHttpClient(cm, params)
}
}
nexus.auth.basic(settings.nexusUsername, nexusPassword)
}
/**
* Scan Nexus for matching artifacts.
*/
def scanArtifacts(String output, String basePath, String pattern, int age, List<String> excludes) {
def nexusRepositoriesServiceUrl = settings.baseUrl + '/service/local/repositories/' + settings.repositoryId + '/content/' + basePath
def nexusMetadataServiceUrl = settings.baseUrl + '/service/local/metadata/repositories/' + settings.repositoryId + '/content/' + basePath
println """
BasePath: ${basePath}
Pattern: ${pattern}
Excludes: ${excludes}
Age: ${age}
Output: ${output}
"""
// Find all repository items that match the regular expression pattern
def urls = findArtifacts(nexusRepositoriesServiceUrl, pattern, age, excludes)
println "Found ${urls.size()} artifacts."
new File(output).write(
new JsonBuilder([
metaDataUrl: nexusMetadataServiceUrl,
artifactUrls: urls
]).toPrettyString())
}
/**
* Delete artifacts in passed list. Deletion is two steps.
* 1) Delete Artifacts
* 2) Rebuild Nexus Repository Metadata
*/
def cleanupArtifacts(String input) {
def json = new JsonSlurper().parseText(new File(input).text)
println "Delete ${json.artifactUrls.size} artifacts."
// Delete each artifact via the Nexus Rest API.
GParsPool.withPool {
json.artifactUrls.eachParallel { url ->
println "[${Thread.currentThread().name[-1]}] Deleting $url"
try {
deleteContent(url)
} catch (groovyx.net.http.HttpResponseException he) {
if (he.statusCode != 404) {
println "[ERROR] $url: ${he.message}"
}
} catch (Exception ex) {
println "[ERROR] $url"
ex.printStackTrace()
}
}
}
// Rebuild Nexus repository metadata, if we have deleted artifacts.
if (json.metaDataUrl) {
println "Rebuilding repository metadata... ${json.metaDataUrl}"
rebuildRepoMetadata(json.metaDataUrl);
}
}
/**
* Finds artifacts that match the input regex pattern, and meet the age requirement.
*/
def findArtifacts(String url, String pattern, int age, List<String> excludes) {
println "Concurrent pool size: ${poolSize}"
def artifactUrls = new ConcurrentLinkedDeque()
def t0 = System.currentTimeMillis()
GParsPool.withPool(poolSize) {
def visits = GParsPool.runForkJoin(url, artifactUrls) { u, result ->
println("[${Thread.currentThread().name[-1]}] Scan $u")
def xml = fetchContent(u)
def visits = 0
xml.data.'content-item'.each { item ->
visits += 1
def resourceURI = item.resourceURI.text()
if (item.text.text() ==~ pattern && !excludes.contains(item.text.text())) {
def lastModifiedDate = new Date().parse('yyyy-MM-dd HH:mm:ss.SSS z', item.lastModified.text())
if ((new Date() - age) > lastModifiedDate) {
result.addLast(resourceURI)
}
}
if (item.leaf.text() == 'false') {
forkOffChild(resourceURI, result)
}
}
return visits + childrenResults.sum(0)
}
println "Scanned $visits resources."
}
println "Scan time: ${System.currentTimeMillis() - t0}ms."
def sortedUrls = new java.util.LinkedList(artifactUrls)
java.util.Collections.sort(sortedUrls)
return sortedUrls
}
/**
* Queries Nexus Repository.
*/
def fetchContent(String url) {
def response = nexus.get(uri: url, contentType: XML)
return response.data
}
/**
* Deletes Nexus Artifact.
*/
def deleteContent(String url) {
def response = nexus.delete(uri: url, contentType: ANY)
}
/**
* Rebuilds Nexus Metadata.
*/
def rebuildRepoMetadata(String url) {
def response = nexus.delete(uri: url, contentType: ANY)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment