Skip to content

Commit c498815

Browse files
committed
LibraryConfiguration: extend with allowBRANCH_NAME for literal use of @Library('libname@${BRANCH_NAME}') [JENKINS-69731]
To simplify co-existence of feature-branched pipeline scripts with feature-branched Jenkins Shared Libraries, allow literal use of @Library('libname@${BRANCH_NAME}') _ lines to load a variant of a trusted (global) library with one case of arbitrary branch naming. This should be permitted by allowBRANCH_NAME checkbox (independent of generic allowVersionOverride setting) in the first place. If enabled, the value of BRANCH_NAME environment variable set by the current build's Run object would be queried, and if resolvable - verified with retriever.validateVersion() to match the backend (Legacy SCM, Modern SCM, and other retrievers like HTTP/ZIP from workflow-cps-global-lib-http-plugin).
1 parent 84da9c5 commit c498815

File tree

4 files changed

+101
-3
lines changed

4 files changed

+101
-3
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
listener.getLogger().println("Only using first definition of library " + name);
127127
continue;
128128
}
129-
String version = cfg.defaultedVersion(libraryVersions.remove(name));
129+
String version = cfg.defaultedVersion(libraryVersions.remove(name), build, listener);
130130
Boolean changelog = cfg.defaultedChangelogs(libraryChangelogs.remove(name));
131131
String source = kind.getClass().getName();
132132
if (cfg instanceof LibraryResolver.ResolvedLibraryConfiguration) {

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

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import hudson.model.Descriptor;
3333
import hudson.model.DescriptorVisibilityFilter;
3434
import hudson.model.Item;
35+
import hudson.model.Run;
36+
import hudson.model.TaskListener;
3537
import hudson.util.FormValidation;
3638
import jenkins.model.Jenkins;
3739
import org.kohsuke.accmod.Restricted;
@@ -58,6 +60,7 @@ public class LibraryConfiguration extends AbstractDescribableImpl<LibraryConfigu
5860
private String defaultVersion;
5961
private boolean implicit;
6062
private boolean allowVersionOverride = true;
63+
private boolean allowBRANCH_NAME = false;
6164
private boolean includeInChangesets = true;
6265
private LibraryCachingConfiguration cachingConfiguration = null;
6366

@@ -112,6 +115,20 @@ public boolean isAllowVersionOverride() {
112115
this.allowVersionOverride = allowVersionOverride;
113116
}
114117

118+
/**
119+
* Whether jobs should be permitted to specify literally @Library('libname@${BRANCH_NAME}')
120+
* in pipeline files (from SCM) to try using the same branch name of library as of the job
121+
* definition. If such branch name does not exist, fall back to retrieve() defaultVersion.
122+
*/
123+
124+
public boolean isAllowBRANCH_NAME() {
125+
return allowBRANCH_NAME;
126+
}
127+
128+
@DataBoundSetter public void setAllowBRANCH_NAME(boolean allowBRANCH_NAME) {
129+
this.allowBRANCH_NAME = allowBRANCH_NAME;
130+
}
131+
115132
/**
116133
* Whether to include library changes in reported changes in a job {@link #getIncludeInChangesets}.
117134
*/
@@ -140,14 +157,61 @@ public LibraryCachingConfiguration getCachingConfiguration() {
140157
}
141158

142159
@NonNull String defaultedVersion(@CheckForNull String version) throws AbortException {
160+
return defaultedVersion(version, null, null);
161+
}
162+
163+
@NonNull String defaultedVersion(@CheckForNull String version, Run<?, ?> run, TaskListener listener) throws AbortException {
143164
if (version == null) {
144165
if (defaultVersion == null) {
145166
throw new AbortException("No version specified for library " + name);
146167
} else {
147168
return defaultVersion;
148169
}
149-
} else if (allowVersionOverride) {
170+
} else if (allowVersionOverride && !"${BRANCH_NAME}".equals(version)) {
150171
return version;
172+
} else if (allowBRANCH_NAME && "${BRANCH_NAME}".equals(version)) {
173+
String runVersion = null;
174+
Item runParent = null;
175+
if (run != null && listener != null) {
176+
try {
177+
runParent = run.getParent();
178+
runVersion = run.getEnvironment(listener).get("BRANCH_NAME", null);
179+
} catch (Exception x) {
180+
// no-op, keep null
181+
}
182+
}
183+
184+
if (runParent == null || runVersion == null || "".equals(runVersion)) {
185+
// Current build does not know a BRANCH_NAME envvar,
186+
// or it's an empty string, or this request has null
187+
// args for run/listener needed for validateVersion()
188+
// below, or some other problem occurred.
189+
// Fall back if we can:
190+
if (defaultVersion == null) {
191+
throw new AbortException("No version specified for library " + name);
192+
} else {
193+
return defaultVersion;
194+
}
195+
}
196+
197+
// Check if runVersion is resolvable by LibraryRetriever
198+
// implementation (SCM, HTTP, etc.); fall back if not:
199+
if (retriever != null) {
200+
FormValidation fv = retriever.validateVersion(name, runVersion, runParent);
201+
202+
if (fv != null && fv.kind == FormValidation.Kind.OK) {
203+
return runVersion;
204+
}
205+
}
206+
207+
// No retriever, or its validateVersion() did not confirm
208+
// usability of BRANCH_NAME string value as the version...
209+
if (defaultVersion == null) {
210+
throw new AbortException("BRANCH_NAME version " + runVersion +
211+
" was not found, and no default version specified, for library " + name);
212+
} else {
213+
return defaultVersion;
214+
}
151215
} else {
152216
throw new AbortException("Version override not permitted for library " + name);
153217
}
@@ -172,16 +236,38 @@ public FormValidation doCheckName(@QueryParameter String name) {
172236
}
173237

174238
@RequirePOST
175-
public FormValidation doCheckDefaultVersion(@AncestorInPath Item context, @QueryParameter String defaultVersion, @QueryParameter boolean implicit, @QueryParameter boolean allowVersionOverride, @QueryParameter String name) {
239+
public FormValidation doCheckDefaultVersion(@AncestorInPath Item context, @QueryParameter String defaultVersion, @QueryParameter boolean implicit, @QueryParameter boolean allowVersionOverride, @QueryParameter boolean allowBRANCH_NAME, @QueryParameter String name) {
176240
if (defaultVersion.isEmpty()) {
177241
if (implicit) {
178242
return FormValidation.error("If you load a library implicitly, you must specify a default version.");
179243
}
244+
if (allowBRANCH_NAME) {
245+
return FormValidation.error("If you allow use of literal '@${BRANCH_NAME}' for overriding a default version, you must define that version as fallback.");
246+
}
180247
if (!allowVersionOverride) {
181248
return FormValidation.error("If you deny overriding a default version, you must define that version.");
182249
}
183250
return FormValidation.ok();
184251
} else {
252+
if ("${BRANCH_NAME}".equals(defaultVersion)) {
253+
if (!allowBRANCH_NAME) {
254+
return FormValidation.error("Use of literal '@${BRANCH_NAME}' not allowed in this configuration.");
255+
}
256+
257+
// The context is not a particular Run (might be a Job)
258+
// so we can't detect which BRANCH_NAME is relevant:
259+
String msg = "Cannot validate default version: " +
260+
"literal '@${BRANCH_NAME}' is reserved " +
261+
"for pipeline files from SCM";
262+
if (implicit) {
263+
// Someone might want to bind feature branches of
264+
// job definitions and implicit libs by default?..
265+
return FormValidation.warning(msg);
266+
} else {
267+
return FormValidation.error(msg);
268+
}
269+
}
270+
185271
for (LibraryResolver resolver : ExtensionList.lookup(LibraryResolver.class)) {
186272
for (LibraryConfiguration config : resolver.fromConfiguration(Stapler.getCurrentRequest())) {
187273
if (config.getName().equals(name)) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ THE SOFTWARE.
3737
<f:entry field="allowVersionOverride" title="${%Allow default version to be overridden}">
3838
<f:checkbox default="true"/>
3939
</f:entry>
40+
<f:entry field="allowBRANCH_NAME" title="${%Allow literal use of @Library 'libname@$${BRANCH_NAME}' in pipeline scripts from SCM; falls back to default version if BRANCH_NAME is not available or not found}">
41+
<f:checkbox default="false"/>
42+
</f:entry>
4043
<f:entry field="includeInChangesets" title="${%Include @Library changes in job recent changes}">
4144
<f:checkbox default="true"/>
4245
</f:entry>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div>
2+
If checked, scripts may select a custom version of the library
3+
by appending literally <code>@${BRANCH_NAME}</code> in the
4+
<code>@Library</code> annotation, to use same SCM branch name
5+
of the library codebase as that of the pipeline being built.<br/>
6+
If such branch name is not resolvable as an environment variable
7+
or not present in library storage (SCM, release artifacts...),
8+
it would fall back to default version you select here.
9+
</div>

0 commit comments

Comments
 (0)