11package org.labkey.gradle.task
22
3+ import org.apache.commons.io.IOUtils
34import org.apache.commons.lang3.StringUtils
45import org.apache.hc.client5.http.classic.methods.HttpDelete
56import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
@@ -8,70 +9,170 @@ import org.apache.hc.client5.http.impl.classic.HttpClients
89import org.apache.hc.core5.http.HttpStatus
910import org.gradle.api.DefaultTask
1011import org.gradle.api.GradleException
11- import org.gradle.api.Project
12+ import org.gradle.api.provider.Property
13+ import org.gradle.api.tasks.Input
14+ import org.gradle.api.tasks.Optional
1215import org.gradle.api.tasks.TaskAction
13- import org.labkey.gradle.plugin.Api
14- import org.labkey.gradle.plugin.FileModule
15- import org.labkey.gradle.plugin.JavaModule
16- import org.labkey.gradle.plugin.Module
17- import org.labkey.gradle.util.BuildUtils
16+
17+ import java.nio.file.Paths
1818
1919class PurgeArtifacts extends DefaultTask
2020{
2121 public static final String SNAPSHOT_REPOSITORY_NAME = ' libs-snapshot-local'
2222 public static final String RELEASE_REPOSITORY_NAME = ' libs-release-local'
2323 public static final String VERSION_PROPERTY = ' purgeVersion'
24+ public static final String VERSIONS_FILE_PROPERTY = ' purgeVersions'
25+ public static final String PURGE_LIST_FILE_PROPERTY = ' purgeList' ;
26+ public static final String DRY_RUN_PROPERTY = ' dryRun' ;
2427
25- static boolean isPreSplitVersion (String version )
26- {
27- return BuildUtils . compareVersions( version, " 19.3" ) < 0
28- }
28+ @Input @Optional
29+ final abstract Property<String > purgeVersion = project. objects. property(String ). convention(project. hasProperty(VERSION_PROPERTY ) ? (String ) project. property(VERSION_PROPERTY ) : " " )
30+ @Input @Optional
31+ final abstract Property<String > purgeVersions = project. objects. property(String ). convention(project. hasProperty(VERSIONS_FILE_PROPERTY ) ? (String ) project. property(VERSIONS_FILE_PROPERTY ) : " " )
32+ @Input @Optional
33+ final abstract Property<String > purgeListFile = project. objects. property(String ). convention(project. hasProperty(PURGE_LIST_FILE_PROPERTY ) ? (String ) project. property(PURGE_LIST_FILE_PROPERTY ) : " " )
34+ @Input
35+ final abstract Property<Boolean > isDryRun = project. objects. property(Boolean ). convention(project. hasProperty(DRY_RUN_PROPERTY ))
36+
37+ @Input
38+ final abstract Property<String > artifactoryUrl = project. objects. property(String ). convention((String ) project. property(' artifactory_contextUrl' ))
39+ @Input
40+ final abstract Property<String > artifactoryUser = project. objects. property(String ). convention((String ) project. property(' artifactory_user' ))
41+ @Input
42+ final abstract Property<String > artifactoryPassword = project. objects. property(String ). convention((String ) project. property(' artifactory_password' ))
2943
3044 enum Response {
3145 SUCCESS ,
3246 NOT_FOUND ,
3347 ERROR
3448 }
49+ private static final String NUM_NOT_FOUND = " numNotFound"
50+ private static final String NUM_DELETED = " numDeleted"
51+ private static final String UNDELETED_VERSIONS = " undeletedVersions"
3552
3653 @TaskAction
3754 void purgeVersions ()
3855 {
39- String purgeVersion
40- if (! project. hasProperty(VERSION_PROPERTY ))
41- throw new GradleException (" No value provided for ${ VERSION_PROPERTY} ." )
42- purgeVersion = project. property(VERSION_PROPERTY )
43- boolean isPreSplitVersion = isPreSplitVersion(purgeVersion)
44- String [] undeletedVersions = []
45- int numDeleted = 0
46- int numNotFound = 0
47- project. allprojects({ Project p ->
48- def plugins = p. getPlugins()
49- if (plugins. hasPlugin(Module . class) || plugins. hasPlugin(JavaModule . class) || plugins. hasPlugin(FileModule . class)) {
50- logger. quiet(" Considering ${ p.path} ..." )
51- Response response = makeDeleteRequest(p. name, purgeVersion, " module" )
52- if (response == Response . NOT_FOUND )
53- numNotFound++
54- else if (response == Response . ERROR ) {
55- undeletedVersions + = " ${ p.path} - module: ${ purgeVersion} "
56+ String version = purgeVersion. get()
57+ String purgeModulesFileName = purgeListFile. get()
58+ if (StringUtils . isEmpty(purgeModulesFileName))
59+ throw new GradleException (" Use -P${ PURGE_LIST_FILE_PROPERTY} =<moduleNames.txt> to provide a list of modules to work with." )
60+ List<String > moduleNames = readInputFile(purgeListFile. get(), " modules" )
61+ if (moduleNames. isEmpty())
62+ throw new GradleException (" No module names found in file ${ purgeListFile.get()} " )
63+ if (! StringUtils . isEmpty(version))
64+ purgeVersion(version, moduleNames)
65+ else
66+ {
67+ Map<String , Integer > overallStats = new HashMap<> ()
68+ overallStats. put(NUM_NOT_FOUND , 0 )
69+ overallStats. put(NUM_DELETED , 0 )
70+ String purgeVersionsFileName = purgeVersions. get()
71+ if (StringUtils . isEmpty(purgeVersionsFileName))
72+ throw new GradleException (" Either -P${ VERSION_PROPERTY} =<versionToPurge> or -P${ VERSIONS_FILE_PROPERTY} =<versionsFile.txt> must be provided" )
73+ List<String > versions = readInputFile(purgeVersionsFileName, " versions" )
74+ if (versions. isEmpty())
75+ throw new GradleException (" No versions found for file ${ purgeVersionsFileName} ." )
76+ if (versions. size() > 1 ) {
77+ for (String moduleName : moduleNames) {
78+ Map<String , Object > deleteStats = purgeModuleVersions(moduleName, versions)
79+ overallStats. put(NUM_NOT_FOUND , overallStats. get(NUM_NOT_FOUND ) + (Integer ) deleteStats. get(NUM_NOT_FOUND ))
80+ overallStats. put(NUM_DELETED , overallStats. get(NUM_DELETED ) + (Integer ) deleteStats. get(NUM_DELETED ))
5681 }
57- else
58- numDeleted++
59- if (! isPreSplitVersion && (plugins. hasPlugin(Api . class) || project. path == BuildUtils . getApiProjectPath(project. gradle))) {
60- response = makeDeleteRequest(p. name, purgeVersion, " api" )
61- if (response == Response . NOT_FOUND )
62- numNotFound++
63- else if (response == Response . ERROR ) {
64- undeletedVersions + = " ${ p.path} - api: ${ purgeVersion} "
65- }
66- else
67- numDeleted++
82+ if (moduleNames. size() > 1 )
83+ logger. quiet(" \n Summary:\n\t Deleted ${ overallStats.get(NUM_DELETED)} artifacts.\n\t ${ overallStats.get(NUM_NOT_FOUND)} artifacts not found." )
84+ }
85+ else {
86+ for (String v : versions) {
87+ Map<String , Object > deleteStats = purgeVersion(v, moduleNames)
88+ overallStats. put(NUM_NOT_FOUND , overallStats. get(NUM_NOT_FOUND ) + (Integer ) deleteStats. get(NUM_NOT_FOUND ))
89+ overallStats. put(NUM_DELETED , overallStats. get(NUM_DELETED ) + (Integer ) deleteStats. get(NUM_DELETED ))
6890 }
91+ if (versions. size() > 1 )
92+ logger. quiet(" \n Summary\n\t Deleted ${ overallStats.get(NUM_DELETED)} artifacts.\n\t ${ overallStats.get(NUM_NOT_FOUND)} artifacts not found." )
6993 }
70- })
94+ }
95+
96+ }
97+
98+ List<String > readInputFile (String fileName , String type )
99+ {
100+ if (! StringUtils . isEmpty(fileName)) {
101+ File listing = Paths . get(fileName). toFile();
102+ if (listing. exists()) {
103+ logger. quiet(" Reading ${ type} purge list from file ${ listing.getAbsolutePath()} ." )
104+ try (BufferedReader reader = new BufferedReader (new InputStreamReader (new FileInputStream (listing)))) {
105+ List<String > lines = IOUtils . readLines(reader). stream(). filter(line -> ! line. startsWith(" #" )). toList()
106+ logger. quiet(" ... found ${ lines.size()} uncommented lines for purging" )
107+ return lines
108+ }
109+ } else {
110+ throw new GradleException (" No such file or directory: ${ fileName} " )
111+ }
112+ } else {
113+ throw new GradleException (" No file name provided for ${ type} input" )
114+ }
115+ }
116+
117+ Map<String , Object > purgeModuleVersions (String moduleName , List<String > versions )
118+ {
119+ Map<String , Object > deleteStats = new HashMap ();
120+ deleteStats. put(NUM_DELETED , 0 )
121+ deleteStats. put(NUM_NOT_FOUND , 0 )
122+ deleteStats. put(UNDELETED_VERSIONS , new ArrayList<> ())
123+
124+ logger. quiet(" ### Begin purge for module ${ moduleName} for ${ versions.size()} versions\n " )
71125
72- logger. quiet(" Deleted ${ numDeleted} artifacts; ${ numNotFound} artifacts not found." )
73- if (undeletedVersions. size() > 0 && ! project. hasProperty(" dryRun" ))
74- throw new GradleException (" The following ${ undeletedVersions.size()} versions were not deleted.\n ${ StringUtils.join(undeletedVersions, "\n")} \n Check the log for more information." )
126+ for (String version : versions) {
127+ makeRequests(moduleName, moduleName, version, deleteStats, true )
128+ }
129+
130+ logger. quiet(" Deleted ${ deleteStats.get(NUM_DELETED)} artifacts; ${ deleteStats.get(NUM_NOT_FOUND)} artifacts not found." )
131+ if (((List<String > ) deleteStats. get(UNDELETED_VERSIONS )). size() > 0 && ! isDryRun. get())
132+ throw new GradleException (" The following ${ ((List<String>) deleteStats.get(UNDELETED_VERSIONS)).size()} versions were not deleted.\n ${ StringUtils.join(deleteStats.get(UNDELETED_VERSIONS), "\n")} \n Check the log for more information." )
133+ logger. quiet(" \n ### End purge for module ${ moduleName} \n " )
134+ return deleteStats
135+ }
136+
137+ Map<String , Object > purgeVersion (String version , List<String > moduleNames )
138+ {
139+ Map<String , Object > deleteStats = new HashMap ();
140+ deleteStats. put(NUM_DELETED , 0 )
141+ deleteStats. put(NUM_NOT_FOUND , 0 )
142+ deleteStats. put(UNDELETED_VERSIONS , new ArrayList<> ())
143+
144+ logger. quiet(" ### Begin purge of version ${ version} for ${ moduleNames.size()} modules\n " )
145+
146+ for (String moduleName : moduleNames) {
147+ makeRequests(moduleName, moduleName, version, deleteStats, true )
148+ }
149+
150+ logger. quiet(" Deleted ${ deleteStats.get(NUM_DELETED)} artifacts; ${ deleteStats.get(NUM_NOT_FOUND)} artifacts not found." )
151+ if (((List<String > ) deleteStats. get(UNDELETED_VERSIONS )). size() > 0 && ! isDryRun. get())
152+ throw new GradleException (" The following ${ ((List<String>) deleteStats.get(UNDELETED_VERSIONS)).size()} versions were not deleted.\n ${ StringUtils.join(deleteStats.get(UNDELETED_VERSIONS), "\n")} \n Check the log for more information." )
153+ logger. quiet(" \n ### End purge for version ${ version} \n " )
154+ return deleteStats
155+ }
156+
157+ void makeRequests (String moduleName , String loggingName , String purgeVersion , Map<String , Object > statsMap , boolean tryApi )
158+ {
159+ logger. quiet(" Considering ${ loggingName} ${ purgeVersion} ..." )
160+ Response response = makeDeleteRequest(moduleName, purgeVersion, " module" )
161+ if (response == Response . NOT_FOUND )
162+ statsMap. put(NUM_NOT_FOUND , statsMap. get(NUM_NOT_FOUND )+1 );
163+ else if (response == Response . ERROR )
164+ statsMap. get(UNDELETED_VERSIONS ). add(" ${ loggingName} - module: ${ purgeVersion} " )
165+ else
166+ statsMap. put(NUM_DELETED , statsMap. get(NUM_DELETED )+1 )
167+ if (tryApi) {
168+ response = makeDeleteRequest(moduleName, purgeVersion, " api" )
169+ if (response == Response . NOT_FOUND )
170+ statsMap. put(NUM_NOT_FOUND , statsMap. get(NUM_NOT_FOUND )+1 )
171+ else if (response == Response . ERROR )
172+ statsMap. get(UNDELETED_VERSIONS ). add(" ${ loggingName} - api: ${ purgeVersion} " )
173+ else
174+ statsMap. put(NUM_DELETED , statsMap. get(NUM_DELETED )+1 )
175+ }
75176 }
76177
77178 /**
@@ -85,29 +186,26 @@ class PurgeArtifacts extends DefaultTask
85186 */
86187 Response makeDeleteRequest (String artifactName , String version , String type )
87188 {
88- if (project . hasProperty( " dryRun " )) {
189+ if (isDryRun . get( )) {
89190 logger. quiet(" \t Removing version ${ version} of ${ artifactName} ${ type} -- Skipped for dry run" )
90191 return
91192 }
92193
93194 CloseableHttpClient httpClient = HttpClients . createDefault()
94- String endpoint = project . property( ' artifactory_contextUrl ' )
195+ String endpoint = artifactoryUrl . get( )
95196 Response responseStatus = Response . SUCCESS
96197 if (! endpoint. endsWith(" /" ))
97198 endpoint + = " /"
98199
99200 String repo = version. contains(" SNAPSHOT" ) ? SNAPSHOT_REPOSITORY_NAME : RELEASE_REPOSITORY_NAME
100- if (isPreSplitVersion(version))
101- endpoint + = repo + " /org/labkey/" + artifactName + " /" + version
102- else
103- endpoint + = repo + " /org/labkey/" + type + " /" + artifactName + " /" + version
201+ endpoint + = repo + " /org/labkey/" + type + " /" + artifactName + " /" + version
104202 logger. quiet(" \t Making delete request for ${ type} artifact ${ artifactName} and version ${ version} via endpoint ${ endpoint} " )
105203
106204 try
107205 {
108206 HttpDelete httpDelete = new HttpDelete (endpoint)
109207 // N.B. Using Authorization Bearer with an API token does not currently work
110- httpDelete. setHeader(" Authorization" , " Basic " + Base64 . getEncoder(). encodeToString(" ${ project.property('artifactory_user' )} :${ project.property('artifactory_password' )} " . getBytes()))
208+ httpDelete. setHeader(" Authorization" , " Basic " + Base64 . getEncoder(). encodeToString(" ${ artifactoryUser.get( )} :${ artifactoryPassword.get( )} " . getBytes()))
111209 CloseableHttpResponse response = httpClient. execute(httpDelete)
112210 int statusCode = response. getCode()
113211
0 commit comments