Skip to content

Commit 8ee9ed9

Browse files
authored
Merge pull request #16 from AnishaGharat/feature/include-versions-caching
[JENKINS-69135] Add a "Versions to include" field to the Global Library Cache feature
2 parents bc5d594 + 4d79950 commit 8ee9ed9

File tree

6 files changed

+206
-13
lines changed

6 files changed

+206
-13
lines changed

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import edu.umd.cs.findbugs.annotations.NonNull;
5353
import java.util.regex.Pattern;
5454
import org.apache.commons.io.IOUtils;
55+
import org.apache.commons.lang.StringUtils;
5556
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
5657
import org.jenkinsci.plugins.workflow.cps.GlobalVariable;
5758
import org.jenkinsci.plugins.workflow.cps.GlobalVariableSet;
@@ -210,7 +211,10 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
210211
shouldCache = false;
211212
}
212213

213-
if(shouldCache) {
214+
//If the included versions is blank/null, cache irrespective
215+
//else check if that version is included and then cache only that version
216+
217+
if((shouldCache && cachingConfiguration.isIncluded(version)) || (shouldCache && StringUtils.isBlank(cachingConfiguration.getIncludedVersionsStr()))) {
214218
retrieveLock.readLock().lockInterruptibly();
215219
try {
216220
CacheStatus cacheStatus = getCacheStatus(cachingConfiguration, versionCacheDir);
@@ -220,8 +224,8 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
220224
try {
221225
boolean retrieve = false;
222226
switch (getCacheStatus(cachingConfiguration, versionCacheDir)) {
223-
case VALID:
224-
listener.getLogger().println("Library " + name + "@" + version + " is cached. Copying from home.");
227+
case VALID:
228+
listener.getLogger().println("Library " + name + "@" + version + " is cached. Copying from home.");
225229
break;
226230
case DOES_NOT_EXIST:
227231
retrieve = true;
@@ -236,20 +240,20 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
236240
retrieve = true;
237241
break;
238242
}
239-
243+
240244
if (retrieve) {
241-
listener.getLogger().println("Caching library " + name + "@" + version);
245+
listener.getLogger().println("Caching library " + name + "@" + version);
242246
versionCacheDir.mkdirs();
243247
retriever.retrieve(name, version, changelog, versionCacheDir, run, listener);
244248
}
245249
retrieveLock.readLock().lock();
246250
} finally {
247-
retrieveLock.writeLock().unlock();
251+
retrieveLock.writeLock().unlock();
248252
}
249253
} else {
250-
listener.getLogger().println("Library " + name + "@" + version + " is cached. Copying from home.");
254+
listener.getLogger().println("Library " + name + "@" + version + " is cached. Copying from home.");
251255
}
252-
256+
253257
lastReadFile.touch(System.currentTimeMillis());
254258
versionCacheDir.withSuffix("-name.txt").write(name, "UTF-8");
255259
versionCacheDir.copyRecursiveTo(libDir);

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
import hudson.Extension;
44
import hudson.FilePath;
5+
import hudson.RestrictedSince;
56
import hudson.model.AbstractDescribableImpl;
67
import hudson.model.Descriptor;
78
import hudson.util.FormValidation;
89
import jenkins.model.Jenkins;
10+
import org.kohsuke.accmod.Restricted;
11+
import org.kohsuke.accmod.restrictions.NoExternalUse;
912
import org.kohsuke.stapler.DataBoundConstructor;
13+
import org.kohsuke.stapler.DataBoundSetter;
1014
import org.kohsuke.stapler.QueryParameter;
1115

1216
import java.io.IOException;
@@ -29,6 +33,7 @@ public final class LibraryCachingConfiguration extends AbstractDescribableImpl<L
2933

3034
private int refreshTimeMinutes;
3135
private String excludedVersionsStr;
36+
private String includedVersionsStr;
3237

3338
private static final String VERSIONS_SEPARATOR = " ";
3439
public static final String GLOBAL_LIBRARIES_DIR = "global-libraries-cache";
@@ -37,6 +42,17 @@ public final class LibraryCachingConfiguration extends AbstractDescribableImpl<L
3742
@DataBoundConstructor public LibraryCachingConfiguration(int refreshTimeMinutes, String excludedVersionsStr) {
3843
this.refreshTimeMinutes = refreshTimeMinutes;
3944
this.excludedVersionsStr = excludedVersionsStr;
45+
this.includedVersionsStr = "";
46+
}
47+
48+
/*
49+
* Visible for testing ...
50+
*/
51+
@Restricted(NoExternalUse.class)
52+
LibraryCachingConfiguration(int refreshTimeMinutes, String excludedVersionsStr, String includedVersionsStr) {
53+
this.refreshTimeMinutes = refreshTimeMinutes;
54+
this.excludedVersionsStr = excludedVersionsStr;
55+
this.includedVersionsStr = includedVersionsStr;
4056
}
4157

4258
public int getRefreshTimeMinutes() {
@@ -54,6 +70,17 @@ public Boolean isRefreshEnabled() {
5470
public String getExcludedVersionsStr() {
5571
return excludedVersionsStr;
5672
}
73+
public String getIncludedVersionsStr() {
74+
if(StringUtils.isBlank(includedVersionsStr)){
75+
return null;
76+
}
77+
return includedVersionsStr;
78+
}
79+
80+
@DataBoundSetter
81+
public void setIncludedVersionsStr(String includedVersionsStr) {
82+
this.includedVersionsStr = includedVersionsStr;
83+
}
5784

5885
private List<String> getExcludedVersions() {
5986
if (excludedVersionsStr == null) {
@@ -62,6 +89,13 @@ private List<String> getExcludedVersions() {
6289
return Arrays.asList(excludedVersionsStr.split(VERSIONS_SEPARATOR));
6390
}
6491

92+
private List<String> getIncludedVersions() {
93+
if (includedVersionsStr == null) {
94+
return Collections.emptyList();
95+
}
96+
return Arrays.asList(includedVersionsStr.split(VERSIONS_SEPARATOR));
97+
}
98+
6599
public Boolean isExcluded(String version) {
66100
// exit early if the version passed in is null or empty
67101
if (StringUtils.isBlank(version)) {
@@ -78,6 +112,22 @@ public Boolean isExcluded(String version) {
78112
return false;
79113
}
80114

115+
public Boolean isIncluded(String version) {
116+
// exit early if the version passed in is null or empty
117+
if (StringUtils.isBlank(version)) {
118+
return false;
119+
}
120+
for (String it : getIncludedVersions()) {
121+
// works on empty or null included versions
122+
// and if the version contains the inclusion thus it can be
123+
// anywhere in the string.
124+
if (StringUtils.isNotBlank(it) && version.contains(it)){
125+
return true;
126+
}
127+
}
128+
return false;
129+
}
130+
81131
@Override public String toString() {
82132
return "LibraryCachingConfiguration{refreshTimeMinutes=" + refreshTimeMinutes + ", excludedVersions="
83133
+ excludedVersionsStr + '}';
@@ -129,6 +179,5 @@ public FormValidation doClearCache(@QueryParameter String name, @QueryParameter
129179
}
130180
return FormValidation.ok("The cache dir was deleted successfully.");
131181
}
132-
133182
}
134183
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ THE SOFTWARE.
3131
<f:entry title="${%Versions to exclude}" field="excludedVersionsStr">
3232
<f:textbox />
3333
</f:entry>
34+
<f:entry title="${%Versions to include}" field="includedVersionsStr">
35+
<f:textbox />
36+
</f:entry>
3437
<j:if test="${h.hasPermission(app.ADMINISTER)}">
3538
<f:entry title="${%Force clear cache}" field="forceDelete">
3639
<f:checkbox/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<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
6+
</div>

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public class GlobalLibrariesTest {
5757
assertEquals(Collections.emptyList(), gl.getLibraries());
5858
LibraryConfiguration foo = new LibraryConfiguration("foo", new SCMSourceRetriever(new SubversionSCMSource("foo", "https://phony.jenkins.io/foo/")));
5959
LibraryConfiguration bar = new LibraryConfiguration("bar", new SCMSourceRetriever(new GitSCMSource(null, "https://phony.jenkins.io/bar.git", "", "origin", "+refs/heads/*:refs/remotes/origin/*", "*", "", true)));
60+
LibraryCachingConfiguration cachingConfiguration = new LibraryCachingConfiguration(120, "develop", "master stable");
61+
foo.setCachingConfiguration(cachingConfiguration);
6062
bar.setDefaultVersion("master");
6163
bar.setImplicit(true);
6264
bar.setAllowVersionOverride(false);
@@ -72,6 +74,14 @@ public class GlobalLibrariesTest {
7274
r.assertEqualDataBoundBeans(Arrays.asList(foo, bar), libs);
7375
libs = gl.getLibraries();
7476
r.assertEqualDataBoundBeans(Arrays.asList(foo, bar), libs);
77+
boolean noFoo = true;
78+
for (LibraryConfiguration lib : libs) {
79+
if ("foo".equals(lib.getName())) {
80+
noFoo = false;
81+
r.assertEqualDataBoundBeans(lib.getCachingConfiguration(), cachingConfiguration);
82+
}
83+
}
84+
assertFalse("Missing a library called foo (should not happen)", noFoo);
7585
}
7686

7787
@Issue("SECURITY-1422")

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

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,31 +61,57 @@ public class LibraryCachingConfigurationTest {
6161
private static int NO_REFRESH_TIME_MINUTES = 0;
6262

6363
private static String NULL_EXCLUDED_VERSION = null;
64+
private static String NULL_INCLUDED_VERSION = null;
65+
6466
private static String ONE_EXCLUDED_VERSION = "branch-1";
6567

68+
private static String ONE_INCLUDED_VERSION = "branch-1i";
69+
70+
6671
private static String MULTIPLE_EXCLUDED_VERSIONS_1 = "main";
72+
73+
private static String MULTIPLE_INCLUDED_VERSIONS_1 = "master";
74+
6775
private static String MULTIPLE_EXCLUDED_VERSIONS_2 = "branch-2";
76+
77+
private static String MULTIPLE_INCLUDED_VERSIONS_2 = "branch-2i";
78+
6879
private static String MULTIPLE_EXCLUDED_VERSIONS_3 = "branch-3";
80+
private static String MULTIPLE_INCLUDED_VERSIONS_3 = "branch-3i";
81+
6982

7083
private static String SUBSTRING_EXCLUDED_VERSIONS_1 = "feature/test-substring-exclude";
84+
85+
private static String SUBSTRING_INCLUDED_VERSIONS_1 = "feature_include/test-substring";
86+
7187
private static String SUBSTRING_EXCLUDED_VERSIONS_2 = "test-other-substring-exclude";
88+
private static String SUBSTRING_INCLUDED_VERSIONS_2 = "test-other-substring-include";
89+
7290

7391
private static String MULTIPLE_EXCLUDED_VERSIONS =
7492
MULTIPLE_EXCLUDED_VERSIONS_1 + " " +
7593
MULTIPLE_EXCLUDED_VERSIONS_2 + " " +
7694
MULTIPLE_EXCLUDED_VERSIONS_3;
7795

96+
private static String MULTIPLE_INCLUDED_VERSIONS =
97+
MULTIPLE_INCLUDED_VERSIONS_1 + " " +
98+
MULTIPLE_INCLUDED_VERSIONS_2 + " " +
99+
MULTIPLE_INCLUDED_VERSIONS_3;
100+
78101
private static String SUBSTRING_EXCLUDED_VERSIONS =
79102
"feature/ other-substring";
80103

104+
private static String SUBSTRING_INCLUDED_VERSIONS =
105+
"feature_include/ other-substring";
106+
81107
private static String NEVER_EXCLUDED_VERSION = "never-excluded-version";
82108

83109
@Before
84110
public void createCachingConfiguration() {
85-
nullVersionConfig = new LibraryCachingConfiguration(REFRESH_TIME_MINUTES, NULL_EXCLUDED_VERSION);
86-
oneVersionConfig = new LibraryCachingConfiguration(NO_REFRESH_TIME_MINUTES, ONE_EXCLUDED_VERSION);
87-
multiVersionConfig = new LibraryCachingConfiguration(REFRESH_TIME_MINUTES, MULTIPLE_EXCLUDED_VERSIONS);
88-
substringVersionConfig = new LibraryCachingConfiguration(REFRESH_TIME_MINUTES, SUBSTRING_EXCLUDED_VERSIONS);
111+
nullVersionConfig = new LibraryCachingConfiguration(REFRESH_TIME_MINUTES, NULL_EXCLUDED_VERSION, NULL_INCLUDED_VERSION);
112+
oneVersionConfig = new LibraryCachingConfiguration(NO_REFRESH_TIME_MINUTES, ONE_EXCLUDED_VERSION, ONE_INCLUDED_VERSION);
113+
multiVersionConfig = new LibraryCachingConfiguration(REFRESH_TIME_MINUTES, MULTIPLE_EXCLUDED_VERSIONS, MULTIPLE_INCLUDED_VERSIONS);
114+
substringVersionConfig = new LibraryCachingConfiguration(REFRESH_TIME_MINUTES, SUBSTRING_EXCLUDED_VERSIONS, SUBSTRING_INCLUDED_VERSIONS);
89115
}
90116

91117
@Issue("JENKINS-66045") // NPE getting excluded versions
@@ -125,6 +151,15 @@ public void getExcludedVersionsStr() {
125151
assertThat(substringVersionConfig.getExcludedVersionsStr(), is(SUBSTRING_EXCLUDED_VERSIONS));
126152
}
127153

154+
@Test
155+
@WithoutJenkins
156+
public void getIncludedVersionsStr() {
157+
assertThat(nullVersionConfig.getIncludedVersionsStr(), is(NULL_INCLUDED_VERSION));
158+
assertThat(oneVersionConfig.getIncludedVersionsStr(), is(ONE_INCLUDED_VERSION));
159+
assertThat(multiVersionConfig.getIncludedVersionsStr(), is(MULTIPLE_INCLUDED_VERSIONS));
160+
assertThat(substringVersionConfig.getIncludedVersionsStr(), is(SUBSTRING_INCLUDED_VERSIONS));
161+
}
162+
128163
@Test
129164
@WithoutJenkins
130165
public void isExcluded() {
@@ -155,6 +190,24 @@ public void isExcluded() {
155190
assertFalse(substringVersionConfig.isExcluded(null));
156191
}
157192

193+
@Issue("JENKINS-69135") //"Versions to include" feature for caching
194+
@Test
195+
@WithoutJenkins
196+
public void isIncluded() {
197+
assertFalse(nullVersionConfig.isIncluded(NULL_INCLUDED_VERSION));
198+
assertFalse(nullVersionConfig.isIncluded(""));
199+
200+
assertTrue(oneVersionConfig.isIncluded(ONE_INCLUDED_VERSION));
201+
202+
assertTrue(multiVersionConfig.isIncluded(MULTIPLE_INCLUDED_VERSIONS_1));
203+
assertTrue(multiVersionConfig.isIncluded(MULTIPLE_INCLUDED_VERSIONS_2));
204+
assertTrue(multiVersionConfig.isIncluded(MULTIPLE_INCLUDED_VERSIONS_3));
205+
206+
assertTrue(substringVersionConfig.isIncluded(SUBSTRING_INCLUDED_VERSIONS_1));
207+
assertTrue(substringVersionConfig.isIncluded(SUBSTRING_INCLUDED_VERSIONS_2));
208+
209+
}
210+
158211
@Test
159212
public void clearCache() throws Exception {
160213
sampleRepo.init();
@@ -182,4 +235,72 @@ public void clearCache() throws Exception {
182235
assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingFile()));
183236
}
184237

238+
//Test similar substrings in "Versions to include" & "Versions to exclude"
239+
//Exclusion takes precedence
240+
@Issue("JENKINS-69135") //"Versions to include" feature for caching
241+
@Test
242+
public void clearCacheConflict() throws Exception {
243+
sampleRepo.init();
244+
sampleRepo.write("vars/foo.groovy", "def call() { echo 'foo' }");
245+
sampleRepo.git("add", "vars");
246+
sampleRepo.git("commit", "--message=init");
247+
LibraryConfiguration config = new LibraryConfiguration("library",
248+
new SCMSourceRetriever(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true)));
249+
config.setDefaultVersion("master");
250+
config.setImplicit(true);
251+
// Same version specified in both include and exclude version
252+
//Exclude takes precedence
253+
config.setCachingConfiguration(new LibraryCachingConfiguration(30, "master", "master"));
254+
GlobalLibraries.get().getLibraries().add(config);
255+
// Run build and check that cache gets created.
256+
WorkflowJob p = r.createProject(WorkflowJob.class);
257+
p.setDefinition(new CpsFlowDefinition("foo()", true));
258+
WorkflowRun b = r.buildAndAssertSuccess(p);
259+
LibrariesAction action = b.getAction(LibrariesAction.class);
260+
LibraryRecord record = action.getLibraries().get(0);
261+
FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName());
262+
// Cache should not get created since the version is included in "Versions to exclude"
263+
assertThat(new File(cache.getRemote()), not(anExistingDirectory()));
264+
assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingFile()));
265+
}
266+
267+
@Issue("JENKINS-69135") //"Versions to include" feature for caching
268+
@Test
269+
public void clearCacheIncludedVersion() throws Exception {
270+
sampleRepo.init();
271+
sampleRepo.write("vars/foo.groovy", "def call() { echo 'foo' }");
272+
sampleRepo.git("add", "vars");
273+
sampleRepo.git("commit", "--message=init");
274+
sampleRepo.git("branch", "test/include");
275+
LibraryConfiguration config = new LibraryConfiguration("library",
276+
new SCMSourceRetriever(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true)));
277+
config.setDefaultVersion("master");
278+
config.setAllowVersionOverride(true);
279+
config.setImplicit(false);
280+
config.setCachingConfiguration(new LibraryCachingConfiguration(30, "", "test/include"));
281+
GlobalLibraries.get().getLibraries().add(config);
282+
// Run build and check that cache gets created.
283+
WorkflowJob p = r.createProject(WorkflowJob.class);
284+
p.setDefinition(new CpsFlowDefinition("library identifier: 'library', changelog:false\n\nfoo()", true));
285+
WorkflowRun b = r.buildAndAssertSuccess(p);
286+
WorkflowJob p2 = r.createProject(WorkflowJob.class);
287+
p2.setDefinition(new CpsFlowDefinition("library identifier: 'library@test/include', changelog:false\n\nfoo()", true));
288+
WorkflowRun b2 = r.buildAndAssertSuccess(p2);
289+
LibrariesAction action = b.getAction(LibrariesAction.class);
290+
LibraryRecord record = action.getLibraries().get(0);
291+
LibrariesAction action2 = b2.getAction(LibrariesAction.class);
292+
LibraryRecord record2 = action2.getLibraries().get(0);
293+
FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName());
294+
FilePath cache2 = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record2.getDirectoryName());
295+
assertThat(new File(cache.getRemote()), not(anExistingDirectory()));
296+
assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingFile()));
297+
assertThat(new File(cache2.getRemote()), anExistingDirectory());
298+
assertThat(new File(cache2.withSuffix("-name.txt").getRemote()), anExistingFile());
299+
// Clears cache for the entire library, until the "Delete specific cache version" feature in merged
300+
// Clear the cache. TODO: Would be more realistic to set up security and use WebClient.
301+
ExtensionList.lookupSingleton(LibraryCachingConfiguration.DescriptorImpl.class).doClearCache("library", false);
302+
assertThat(new File(cache2.getRemote()), not(anExistingDirectory()));
303+
assertThat(new File(cache2.withSuffix("-name.txt").getRemote()), not(anExistingFile()));
304+
}
305+
185306
}

0 commit comments

Comments
 (0)