Skip to content

Commit 433d676

Browse files
authored
Merge pull request #1 from AnishaGharat/feature/delete-specific-cache-version-remotely
Updated code for deleting specific cache version
2 parents f2ae5b7 + f29d6cd commit 433d676

File tree

12 files changed

+167
-111
lines changed

12 files changed

+167
-111
lines changed

src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
202202
FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName());
203203
Boolean shouldCache = cachingConfiguration != null;
204204
final FilePath versionCacheDir = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), record.getDirectoryName());
205-
ReentrantReadWriteLock retrieveLock = getReadWriteLockFor(record.getDirectoryName());
205+
ReentrantReadWriteLock retrieveLock = getReadWriteLockFor(record.getName());
206206
final FilePath lastReadFile = new FilePath(versionCacheDir, LibraryCachingConfiguration.LAST_READ_FILE);
207207

208208
if(shouldCache && cachingConfiguration.isExcluded(version)) {
@@ -238,7 +238,7 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
238238
}
239239

240240
if (retrieve) {
241-
listener.getLogger().println("Caching library " + name + "@" + version);
241+
listener.getLogger().println("Caching library " + name + "@" + version);
242242
versionCacheDir.mkdirs();
243243
retriever.retrieve(name, version, changelog, versionCacheDir, run, listener);
244244
}
@@ -251,7 +251,7 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
251251
}
252252

253253
lastReadFile.touch(System.currentTimeMillis());
254-
versionCacheDir.withSuffix("-name.txt").write(name, "UTF-8");
254+
versionCacheDir.withSuffix("-name.txt").write(name + "@" + version, "UTF-8");
255255
versionCacheDir.copyRecursiveTo(libDir);
256256
} finally {
257257
retrieveLock.readLock().unlock();

src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@ public LibraryCachingCleanup() {
2828
@Override protected void execute(TaskListener listener) throws IOException, InterruptedException {
2929
FilePath globalCacheDir = LibraryCachingConfiguration.getGlobalLibrariesCacheDir();
3030
for (FilePath library : globalCacheDir.list()) {
31-
if (!removeIfExpiredCacheDirectory(library)) {
32-
// Prior to the SECURITY-2586 fix, library caches had a two-level directory structure.
33-
// These caches will never be used again, so we delete any that we find.
34-
for (FilePath version: library.list()) {
35-
if (version.child(LibraryCachingConfiguration.LAST_READ_FILE).exists()) {
36-
library.deleteRecursive();
37-
break;
31+
for (FilePath versionDir : library.listDirectories()) {
32+
if (!removeIfExpiredCacheDirectory(versionDir)) {
33+
FilePath parent = versionDir.getParent();
34+
if (parent != null) {
35+
parent.deleteRecursive();
3836
}
37+
break;
3938
}
4039
}
4140
}
@@ -48,14 +47,15 @@ public LibraryCachingCleanup() {
4847
*/
4948
private boolean removeIfExpiredCacheDirectory(FilePath library) throws IOException, InterruptedException {
5049
final FilePath lastReadFile = new FilePath(library, LibraryCachingConfiguration.LAST_READ_FILE);
51-
if (lastReadFile.exists()) {
50+
if (lastReadFile.exists() && library.withSuffix("-name.txt").exists()) {
5251
ReentrantReadWriteLock retrieveLock = LibraryAdder.getReadWriteLockFor(library.getName());
5352
retrieveLock.writeLock().lockInterruptibly();
5453
try {
5554
if (System.currentTimeMillis() - lastReadFile.lastModified() > TimeUnit.DAYS.toMillis(EXPIRE_AFTER_READ_DAYS)) {
56-
57-
library.deleteRecursive();
58-
library.withSuffix("-name.txt").delete();
55+
FilePath parent = library.getParent();
56+
if (parent != null) {
57+
parent.deleteRecursive();
58+
}
5959
}
6060
} finally {
6161
retrieveLock.writeLock().unlock();

src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java

Lines changed: 50 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public final class LibraryCachingConfiguration extends AbstractDescribableImpl<L
3131
private String excludedVersionsStr;
3232
private String includedVersionsStr;
3333

34+
3435
private static final String VERSIONS_SEPARATOR = " ";
3536
public static final String GLOBAL_LIBRARIES_DIR = "global-libraries-cache";
3637
public static final String LAST_READ_FILE = "last_read";
@@ -39,9 +40,10 @@ public final class LibraryCachingConfiguration extends AbstractDescribableImpl<L
3940
this.refreshTimeMinutes = refreshTimeMinutes;
4041
this.excludedVersionsStr = excludedVersionsStr;
4142
this.includedVersionsStr = includedVersionsStr;
42-
}
4343

4444

45+
}
46+
4547
public int getRefreshTimeMinutes() {
4648
return refreshTimeMinutes;
4749
}
@@ -57,8 +59,10 @@ public Boolean isRefreshEnabled() {
5759
public String getExcludedVersionsStr() {
5860
return excludedVersionsStr;
5961
}
60-
public String getIncludedVersionsStr() { return includedVersionsStr; }
6162

63+
public String getIncludedVersionsStr() {
64+
return includedVersionsStr;
65+
}
6266

6367
private List<String> getExcludedVersions() {
6468
if (excludedVersionsStr == null) {
@@ -117,78 +121,61 @@ public static FilePath getGlobalLibrariesCacheDir() {
117121
}
118122

119123
@Extension public static class DescriptorImpl extends Descriptor<LibraryCachingConfiguration> {
120-
public FormValidation doClearCache(@QueryParameter String name, @QueryParameter boolean forceDelete) throws InterruptedException {
124+
public FormValidation doClearCache(@QueryParameter String name, @QueryParameter String cachedLibraryRef, @QueryParameter boolean forceDelete) throws InterruptedException {
121125
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
122-
126+
String cacheDirName = null;
123127
try {
124128
if (LibraryCachingConfiguration.getGlobalLibrariesCacheDir().exists()) {
125-
for (FilePath libraryNamePath : LibraryCachingConfiguration.getGlobalLibrariesCacheDir().list("*-name.txt")) {
126-
// Libraries configured in distinct locations may have the same name. Since only admins are allowed here, this is not a huge issue, but it is probably unexpected.
127-
String cacheName;
128-
try (InputStream stream = libraryNamePath.read()) {
129-
cacheName = IOUtils.toString(stream, StandardCharsets.UTF_8);
130-
}
131-
if (cacheName.equals(name)) {
132-
FilePath libraryCachePath = LibraryCachingConfiguration.getGlobalLibrariesCacheDir()
133-
.child(libraryNamePath.getName().replace("-name.txt", ""));
134-
if (forceDelete) {
135-
LOGGER.log(Level.FINER, "Force deleting cache for {0}", name);
136-
libraryCachePath.deleteRecursive();
137-
libraryNamePath.delete();
138-
} else {
139-
LOGGER.log(Level.FINER, "Safe deleting cache for {0}", name);
140-
ReentrantReadWriteLock retrieveLock = LibraryAdder.getReadWriteLockFor(libraryCachePath.getName());
141-
if (retrieveLock.writeLock().tryLock(10, TimeUnit.SECONDS)) {
142-
try {
143-
libraryCachePath.deleteRecursive();
144-
libraryNamePath.delete();
145-
} finally {
146-
retrieveLock.writeLock().unlock();
129+
outer: for (FilePath libraryCache : LibraryCachingConfiguration.getGlobalLibrariesCacheDir().listDirectories()) {
130+
for (FilePath libraryNamePath : libraryCache.list("*-name.txt")) {
131+
if (libraryNamePath.readToString().startsWith(name + "@")) {
132+
FilePath libraryCachePath = libraryNamePath.getParent();
133+
if (libraryCachePath != null) {
134+
FilePath versionCachePath = new FilePath(libraryCachePath, libraryNamePath.getName().replace("-name.txt", ""));
135+
LOGGER.log(Level.FINER, "Safe deleting cache for {0}", name);
136+
ReentrantReadWriteLock retrieveLock = LibraryAdder.getReadWriteLockFor(libraryCachePath.getName());
137+
if (forceDelete || retrieveLock.writeLock().tryLock(10, TimeUnit.SECONDS)) {
138+
if (forceDelete) {
139+
LOGGER.log(Level.FINER, "Force deleting cache for {0}", name);
140+
} else {
141+
LOGGER.log(Level.FINER, "Safe deleting cache for {0}", name);
142+
}
143+
try {
144+
if (StringUtils.isNotEmpty(cachedLibraryRef)) {
145+
if (libraryNamePath.readToString().equals(name + "@" + cachedLibraryRef)) {
146+
cacheDirName = name + "@" + cachedLibraryRef;
147+
libraryNamePath.delete();
148+
versionCachePath.deleteRecursive();
149+
break outer;
150+
}
151+
} else {
152+
cacheDirName = name;
153+
libraryCachePath.deleteRecursive();
154+
break outer;
155+
}
156+
} finally {
157+
if (!forceDelete) {
158+
retrieveLock.writeLock().unlock();
159+
}
160+
}
161+
} else {
162+
return FormValidation.error("The cache dir could not be deleted because it is currently being used by another thread. Please try again.");
147163
}
148-
} else {
149-
return FormValidation.error("The cache dir could not be deleted because it is currently being used by another thread. Please try again.");
150164
}
151165
}
152166
}
153167
}
154168
}
155169
} catch (IOException ex) {
156-
return FormValidation.error(ex, "The cache dir was not deleted successfully");
170+
return FormValidation.error(ex, String.format("The cache dir %s was not deleted successfully", cacheDirName));
157171
}
158-
return FormValidation.ok("The cache dir was deleted successfully.");
159-
}
160-
}
161-
162-
/**
163-
* Method can be called from an external source to delete cache of a particular version of the shared library
164-
* Example: delete cache from through pipeline script once the build is successful
165-
* @param jenkinsLibrary Name of the shared library
166-
* @param version Name of the version to delete the cache
167-
* @throws IOException
168-
* @throws InterruptedException
169-
*/
170-
public static void deleteLibraryCacheForBranch(String jenkinsLibrary, String version) throws IOException, InterruptedException {
171-
if (LibraryCachingConfiguration.getGlobalLibrariesCacheDir().exists()) {
172-
for (FilePath libraryNamePath : LibraryCachingConfiguration.getGlobalLibrariesCacheDir().list("*-name.txt")) {
173-
String cacheName;
174-
try (InputStream stream = libraryNamePath.read()) {
175-
cacheName = IOUtils.toString(stream, StandardCharsets.UTF_8);
176-
177-
if (cacheName.equals(jenkinsLibrary)) {
178-
FilePath libraryCachePath = LibraryCachingConfiguration.getGlobalLibrariesCacheDir()
179-
.child(libraryNamePath.getName().replace("-name.txt", ""));
180-
ReentrantReadWriteLock retrieveLock = LibraryAdder.getReadWriteLockFor(libraryCachePath.getName());
181-
if (retrieveLock.writeLock().tryLock(10, TimeUnit.SECONDS)) {
182-
try {
183-
libraryCachePath.deleteRecursive();
184-
libraryNamePath.delete();
185-
} finally {
186-
retrieveLock.writeLock().unlock();
187-
}
188-
}
189-
}
190-
}
172+
173+
if (cacheDirName == null) {
174+
return FormValidation.ok(String.format("The version %s was not found for library %s.", cachedLibraryRef, name));
175+
} else {
176+
return FormValidation.ok(String.format("The cache dir %s was deleted successfully.", cacheDirName));
191177
}
192178
}
179+
193180
}
194-
}
181+
}

src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryRecord.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
package org.jenkinsci.plugins.workflow.libs;
2626

27+
import java.io.File;
2728
import java.util.Collections;
2829
import java.util.Set;
2930
import java.util.TreeSet;
@@ -62,7 +63,7 @@ public final class LibraryRecord {
6263
this.trusted = trusted;
6364
this.changelog = changelog;
6465
this.cachingConfiguration = cachingConfiguration;
65-
this.directoryName = directoryNameFor(name, version, String.valueOf(trusted), source);
66+
this.directoryName = directoryNameFor(name, String.valueOf(trusted), source) + File.separator + directoryNameFor(version);
6667
}
6768

6869
@Exported

src/main/resources/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration/config.jelly

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@ THE SOFTWARE.
3232
<f:textbox />
3333
</f:entry>
3434
<f:entry title="${%Versions to include}" field="includedVersionsStr">
35-
<f:textbox />
35+
<f:textbox />
3636
</f:entry>
3737
<j:if test="${h.hasPermission(app.ADMINISTER)}">
38-
<f:entry title="${%Force clear cache}" field="forceDelete">
38+
<f:entry title="${%Clear cache for ref}" field="cachedLibraryRef">
39+
<f:textbox />
40+
</f:entry>
41+
<f:entry title="${%Force clear cache}" field="forceDelete">
3942
<f:checkbox/>
4043
</f:entry>
41-
<f:validateButton title="${%Clear cache}" progress="${%Clearing...}" method="clearCache" with="name,forceDelete" />
44+
<f:validateButton title="${%Clear cache}" progress="${%Clearing...}" method="clearCache" with="name,cachedLibraryRef,forceDelete" />
4245
</j:if>
4346
</j:jelly>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div>
2+
Specifies a specific version to clear the cache for. An empty value will clear the cache for all versions.
3+
</div>
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
<div>
2-
Space separated list of versions to include to allow caching via substring search using .contains() method. Ex: "release/ master".
3-
</div>
4-
<div>
5-
Note: Excluded versions will always take precedence over included versions
2+
Space separated list of versions to include to allow caching via substring search using .contains() method. Ex: "release/ master".
63
</div>

src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ public void parallelBuildsDontInterfereWithExpiredCache() throws Throwable {
493493
new SCMSourceRetriever(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true)));
494494
config.setDefaultVersion("master");
495495
config.setImplicit(true);
496-
config.setCachingConfiguration(new LibraryCachingConfiguration(30, null,null));
496+
config.setCachingConfiguration(new LibraryCachingConfiguration(30, null, null));
497497
GlobalLibraries.get().getLibraries().add(config);
498498
WorkflowJob p1 = r.createProject(WorkflowJob.class);
499499
WorkflowJob p2 = r.createProject(WorkflowJob.class);

src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,16 @@ public void smokes() throws Throwable {
7171
assertThat(new File(cache.getRemote()), anExistingDirectory());
7272
// Run LibraryCachingCleanup and show that cache is not deleted.
7373
ExtensionList.lookupSingleton(LibraryCachingCleanup.class).execute(StreamTaskListener.fromStderr());
74+
assertThat(new File(cache.getParent().getRemote()), anExistingDirectory());
7475
assertThat(new File(cache.getRemote()), anExistingDirectory());
7576
assertThat(new File(cache.withSuffix("-name.txt").getRemote()), anExistingFile());
7677
// Run LibraryCachingCleanup after modifying LAST_READ_FILE to be an old date and and show that cache is deleted.
7778
long oldMillis = ZonedDateTime.now().minusDays(LibraryCachingCleanup.EXPIRE_AFTER_READ_DAYS + 1).toInstant().toEpochMilli();
7879
cache.child(LibraryCachingConfiguration.LAST_READ_FILE).touch(oldMillis);
7980
ExtensionList.lookupSingleton(LibraryCachingCleanup.class).execute(StreamTaskListener.fromStderr());
80-
assertThat(new File(cache.getRemote()), not(anExistingDirectory()));
81-
assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingDirectory()));
81+
assertThat(new File(cache.getParent().getRemote()), not(anExistingDirectory()));
82+
assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingFile()));
83+
8284
}
8385

8486
@Test

0 commit comments

Comments
 (0)