-
-
Save sasjo/1a883c074c760975f00cb356979b308c to your computer and use it in GitHub Desktop.
Clean up nexus artifacts with API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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