Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hacktoberfest [JENKINS-69731] Enhance @Library annotation to load same version (feature branch name) of library as pipeline script from SCM #19

Open
wants to merge 78 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
a937477
LibraryConfiguration: extend with allowBRANCH_NAME for literal use of…
jimklimov Sep 28, 2022
15a252a
LibraryConfigurationTest.java: add tests for LibraryConfiguration.def…
jimklimov Sep 28, 2022
bf228e4
SCMSourceRetrieverTest.java: add checkDefaultVersion() for interactio…
jimklimov Sep 28, 2022
61db728
SCMSourceRetrieverTest: checkDefaultVersion(): also test non-checkout…
jimklimov Sep 28, 2022
2e3c855
LibraryConfiguration: extend with allowBRANCH_NAME_PR to let CHANGE_B…
jimklimov Sep 29, 2022
0f89b22
LibraryConfiguration: defaultedVersion(): for WorkflowRun check getSC…
jimklimov Sep 29, 2022
cf3d140
LibraryConfiguration: defaultedVersion(): check for BRANCH_NAME first…
jimklimov Sep 29, 2022
eb73ce4
SCMSourceRetrieverTest: rename checkDefaultVersion() to checkDefaultV…
jimklimov Sep 29, 2022
3ef66ae
SCMSourceRetrieverTest: add checkDefaultVersion_BRANCH_NAME_MBP() [JE…
jimklimov Sep 29, 2022
7ddf8e6
SCMSourceRetrieverTest.java: add checkDefaultVersion_MBP() and checkD…
jimklimov Sep 29, 2022
4f792f6
SCMSourceRetrieverTest.java: optimize tests using MBP to not build sa…
jimklimov Sep 29, 2022
9d95881
SCMSourceRetrieverTest.java: adjust naming of tests using MBP [JENKIN…
jimklimov Sep 29, 2022
0694dd1
SCMSourceRetrieverTest: add checkDefaultVersion_singleBranch() [JENKI…
jimklimov Sep 29, 2022
824a204
SCMSourceRetrieverTest.java: report "Jobs generated by MBP" tests [JE…
jimklimov Sep 30, 2022
553aba3
SCMSourceRetrieverTest.java: add PoC checkDefaultVersion_MBP_singleBr…
jimklimov Sep 30, 2022
bcba445
LibraryConfiguration: add optional traceBRANCH_NAME toggle, and lots …
jimklimov Sep 30, 2022
6b5255c
LibraryConfigurationTest: enable traceBRANCH_NAME for test runs [JENK…
jimklimov Sep 30, 2022
a661a13
SCMSourceRetrieverTest: enable traceBRANCH_NAME for test runs [JENKIN…
jimklimov Sep 30, 2022
9700565
SCMSourceRetrieverTest: update comments [JENKINS-69731]
jimklimov Sep 30, 2022
30d21b9
SCMSourceRetrieverTest: rename some tests to follow a common pattern …
jimklimov Sep 30, 2022
34718a4
SCMSourceRetrieverTest: separate checkDefaultVersion_inline_BRANCH_NA…
jimklimov Sep 30, 2022
8e92f64
SCMSourceRetrieverTest: add checkDefaultVersion_MBPsingleBranch_BRANC…
jimklimov Sep 30, 2022
5dc85dd
LibraryConfiguration: defaultedVersion(): unblock WorkflowRun handlin…
jimklimov Sep 30, 2022
a9aa8f1
SCMSourceRetrieverTest: checkDefaultVersion_inline_BRANCH_NAME(): tes…
jimklimov Sep 30, 2022
418c572
SCMSourceRetrieverTest: add checkDefaultVersion_singleBranch_BRANCH_N…
jimklimov Sep 30, 2022
54bb3e9
LibraryConfiguration: defaultedVersion(): add allowVersionEnvvar for …
jimklimov Sep 30, 2022
d47dad5
SCMSourceRetrieverTest: add PoC for checkDefaultVersion_inline_allowV…
jimklimov Sep 30, 2022
811fb82
help-allowVersionEnvvar.html, help-allowBRANCH_NAME.html: stress that…
jimklimov Oct 1, 2022
43d45b8
SCMSourceRetrieverTest: add checkDefaultVersion_singleBranch_BRANCH_N…
jimklimov Oct 1, 2022
38f380f
LibraryConfiguration/config.jelly: fix markup [JENKINS-69731]
jimklimov Oct 1, 2022
68902fd
LibraryConfiguration: doCheckDefaultVersion(): fix markup [JENKINS-69…
jimklimov Oct 1, 2022
dddd3ac
SCMSourceRetrieverTest: make checkDefaultVersion_inline_allowVersionE…
jimklimov Oct 1, 2022
48ad0fe
LibraryConfiguration: defaultedVersion(): if there were no SCMs assoc…
jimklimov Oct 1, 2022
eeba94c
SCMSourceRetrieverTest: extend checkDefaultVersion_inline_allowVersio…
jimklimov Oct 1, 2022
ee36d66
LibraryConfiguration: defaultedVersion(): first check if WorkflowJob …
jimklimov Oct 1, 2022
83cea07
SCMSourceRetrieverTest: checkDefaultVersion_MBPsingleBranch_*(): try …
jimklimov Oct 2, 2022
cf2c069
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 2, 2022
6d74aea
LibraryConfiguration: refactor irky defaultedVersionSCM() helper out …
jimklimov Oct 2, 2022
6097f6a
LibraryConfiguration and tests: rename traceBRANCH_NAME to more gener…
jimklimov Oct 2, 2022
85e96e3
SCMSourceRetrieverTest: remove currently unused imports [JENKINS-69731]
jimklimov Oct 2, 2022
4ce1164
SCMSourceRetrieverTest: complete the checkDefaultVersion_MBPsingleBra…
jimklimov Oct 2, 2022
ce8b820
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 9, 2022
2d4bd48
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 9, 2022
6767dc2
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 9, 2022
edd1ddb
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 9, 2022
5190214
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 9, 2022
06a3e09
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 9, 2022
f4c53c7
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 9, 2022
d529421
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 9, 2022
71c51c8
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 10, 2022
9e20d78
SCMSourceRetrieverTest: checkDefaultVersion_inline_allowVersionEnvvar…
jimklimov Oct 10, 2022
3cb60c0
SCMSourceRetrieverTest: drop separate checkDefaultVersion_inline_allo…
jimklimov Oct 10, 2022
ba01128
SCMSourceRetrieverTest: add checkDefaultVersion_singleBranch_BRANCH_N…
jimklimov Oct 11, 2022
863b170
LibraryConfiguration: defaultedVersionSCM(): avoid WorkflowJob.getSCM…
jimklimov Oct 13, 2022
dea552f
LibraryConfiguration: defaultedVersionSCM(): drop fallback WorkflowJo…
jimklimov Oct 13, 2022
70b029c
LibraryConfiguration/config.jelly: update comment for allowVersionOve…
jimklimov Oct 17, 2022
786092d
SCMSourceRetrieverTest: refactor baseline with sampleRepo1ContentMast…
jimklimov Oct 17, 2022
be0c051
SCMSourceRetrieverTest: refactor new tests with sampleRepo1Content*()…
jimklimov Oct 17, 2022
d67c2d1
SCMSourceRetrieverTest: clarify in surefire log that checkoutDirector…
jimklimov Oct 17, 2022
db74431
SCMSourceRetrieverTest.java: cleanup imports
jimklimov Oct 17, 2022
812d12d
SCMSourceRetrieverTest.java: add checkDefaultVersion_singleBranch_BRA…
jimklimov Oct 17, 2022
6ab574a
LibraryConfiguration.java: defaultedVersionSCM(): avoid get()ing from…
jimklimov Oct 17, 2022
2d098a5
LibraryConfiguration.java: defaultedVersionSCM(): check for nuances o…
jimklimov Oct 17, 2022
6379716
LibraryConfiguration.java: further refactor defaultedVersionSCM() to …
jimklimov Oct 18, 2022
ac9f40a
LibraryConfiguration.java: attempt to first extractDefaultedVersionSC…
jimklimov Oct 18, 2022
b95c46a
LibraryConfiguration.java: drop currently unnecessary imports
jimklimov Oct 18, 2022
a943713
LibraryConfiguration.java: extractDefaultedVersionSCMFS(): avoid redu…
jimklimov Oct 19, 2022
d039e50
LibraryConfiguration.java: extractDefaultedVersionSCMFS(): placeholde…
jimklimov Oct 19, 2022
48548ae
Merge branch 'master' into JENKINS-69731-scm-BRANCH_NAME
jimklimov Nov 30, 2022
5ab9713
Merge remote-tracking branch 'upstream/master' into JENKINS-69731-scm…
jimklimov Mar 12, 2023
65a7d02
Merge branch 'master' into JENKINS-69731-scm-BRANCH_NAME
jimklimov Apr 21, 2023
c2cf4b5
Merge remote-tracking branch 'upstream/master' into JENKINS-69731-scm…
jimklimov Aug 26, 2023
6668195
SCMSourceRetrieverTest: clarify messages for "SKIP by pre-test assump…
jimklimov Aug 26, 2023
c1082b9
SCMSourceRetrieverTest: provide directly a sampleRepoNotifyCommit() t…
jimklimov Aug 26, 2023
02cf65b
Merge branch 'master' into JENKINS-69731-scm-BRANCH_NAME
jimklimov Oct 11, 2023
7926f8c
Merge remote-tracking branch 'upstream/master' into JENKINS-69731-scm…
jimklimov Nov 23, 2023
7329664
Merge remote-tracking branch 'upstream/master' into JENKINS-69731-scm…
jimklimov Jan 21, 2024
b8c4a88
Merge remote-tracking branch 'upstream/master' into JENKINS-69731-scm…
jimklimov Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
listener.getLogger().println("Only using first definition of library " + name);
continue;
}
String version = cfg.defaultedVersion(libraryVersions.remove(name));
String version = cfg.defaultedVersion(libraryVersions.remove(name), build, listener);
Boolean changelog = cfg.defaultedChangelogs(libraryChangelogs.remove(name));
String source = kind.getClass().getName();
if (cfg instanceof LibraryResolver.ResolvedLibraryConfiguration) {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,21 @@ THE SOFTWARE.
<f:entry field="implicit" title="${%Load implicitly}">
<f:checkbox/>
</f:entry>
<f:entry field="allowVersionOverride" title="${%Allow default version to be overridden}">
<f:entry field="allowVersionOverride" title="${%Allow default version to be overridden by a fixed string - branch name etc.}">
<f:checkbox default="true"/>
</f:entry>
<f:entry field="allowVersionEnvvar" title="${%Allow literal use of @Library 'libname@${env.VARNAME}' pattern in pipeline scripts; falls back to default version if such custom VARNAME is not available among environment variables for the build or not found as branch name in SCM}">
<f:checkbox default="false"/>
</f:entry>
<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}">
<f:checkbox default="false"/>
</f:entry>
<f:entry field="allowBRANCH_NAME_PR" title="${%If you allow literal use of @Library 'libname@${BRANCH_NAME}' above, you can also allow fallback to CHANGE_BRANCH and/or CHANGE_TARGET for pull request builds}">
<f:checkbox default="false"/>
</f:entry>
<f:entry field="traceDefaultedVersion" title="${%Request tracing of 'libname@${BRANCH_NAME}' resolution into build log}">
<f:checkbox default="false"/>
</f:entry>
<f:entry field="includeInChangesets" title="${%Include @Library changes in job recent changes}">
<f:checkbox default="true"/>
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div>
If checked, scripts may select a custom version of the library
by appending literally <code>@${BRANCH_NAME}</code> in the
<code>@Library</code> annotation, to use same SCM branch name
of the library codebase as that of the pipeline being built.<br/>
If such branch name is not resolvable as an environment variable
or not present in library storage (SCM, release artifacts...),
it would fall back to default version you select here.<br/>
Keep in mind that while the markup for such variable version
specification is intentionally similar to what you would use
in pipeline Groovy code, for simpler use and maintenance, the
Comment on lines +9 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you could just use the library step except in cases where you need to refer to library types (src/) using static typing, which forces use of the annotation. Definitely not the normal usage pattern.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src and types (classes) defined there to wrap our SUTs (generic lockable resources passed around from stage to stage or even job to job => DBs => Mysql/Postgres/Oracle... and test VMs => vmware/qemu/...), some global static for shared/default config, and classes for custom tools + data-generator wrappers, etc. is indeed what we use. So the library step proved worse than useless after a week of trying, too fragile, depends on order we call steps and class methods/data, many of those are just not visible through proxy objects, etc.

Also, part of the equation is that at least here people part-time doing devops are not Java developers. Maybe what is used could go into libs or plugins. No idea really. Would it be easier arranged if it were spread around a dozen repos instead of one? No idea either. Their bread is elsewhere, in other languages and environments, and for versioned libs they need the path of least resistance to make things "just work", including that for qa/stage/prod environments :)

Had a hard time with @Library('libname@somebranch') differing in all the branches - kept spilling over as somebody branched their new feature from stable production code, and then the PR landed into qa or stage... it does not well allow for just fast-forward-merging the PRs.

According to IRC discussion, not so much a fringe case - several posters of the few online and active in a day sounded enthusiastic about just this sort of use-case for libs that they failed to make use of too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking of "marginal" use cases, some of our libraries dealing with infrastructure and CI farm management (housekeeping like freeing locked resources grabbed by devs for too long and their associated PR no longer exists) do integrate pretty close with Jenkins internals, so running them as a trusted global library is a lot easier than writing straight infra pipelines and keeping permitting methods through sandboxing protection, one run at a time.

I do not know how many people really do that sort of code, going into Jenkins Items, tickling the LockableResourceManager, etc. - but since there is a ready "paste this into JENKINS_URL/script" solution for just about anything, I suppose many do. And such libs (and front-end pipelines to tap into them) are something that can in some shops benefit from evolving in sync (same branch where possible) even if spread across repos.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also thinking of the library step to pass a groovy variable BRANCH_NAME there - as I found with non-multibranch pipelines, at this time existence of this variable is not a given (see caveats and workarounds I posted just before your review). So one would have to copy-paste that bit around, or set up a library with one step to ensure it, just so the library step can know the "deemed correct" version to use in all cases? Sounds like a bigger hassle, if it can all be nailed here, and with all the benefits of the annotation :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evolving in sync (same branch where possible) even if spread across repos

This is one way, though better I think is to have a consumer (repo with Jenkinsfile) temporarily consume the patched library repo with an explicit reference to the library branch, until that patch is well tested and merged into its default branch. Ideally with a way of mechanically blocking the PR to the consumer repo from being merged until the @Library patch is reverted, so that you do not forget to follow the process. (I am using GitHub-oriented terms here but I think the style applies generally.) For the same reason, I would never advocate use of the resolveScm step (jenkinsci/workflow-multibranch-plugin#46 (review) 🤷).

One thing I have wanted for a while is a Dependabot module for Jenkins Pipeline libraries, so you could specify an (immutable!) tag in @Library but still keep up to date with improvements. This type of workflow is well established for GitHub Actions.

At any rate, different installations are going to have different workflows for reasons good or bad. What I am mainly wondering is whether this new logic actually needs to be implemented here, since it is a pretty big change and maintainers of this plugin (if any; not me) can hardly keep up with basic stuff like fixes to the cache system. I suspect this could be implemented entirely as a new LibraryRetriever implementation, probably delegating to SCMSourceRetriever like SCMRetriever does.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pipeline may be from CVS and library from Git or Nexus

IMO the entire notion of matching up branch names between main repo and library only makes sense if they are using the same SCM, so I would not worry about such exotic cases.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, different tech might have been too far-fetched... or not...

Earlier I've worked with a project that had components built by Jenkinsfiles and most sources out on Github and built by FOSS CI farms, with a public version of JSL driving that. However the commercially supported product was built and tested by an in-house farm, using different toolkits and services, with a completely different "API-compatible" implementation of JSL served by an on-prem SCM. True, both SCMs happened to talk Git, but it was not granted (had corporate IT enable that "for simplicity" in addition to repo protocols their earlier projects used).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid that Jenkins is popular enough that, statistically, any requirement will be critical for somebody! The advantage of the library step is that you can script whatever logic makes sense for your setup without negotiating with a plugin developer over the details. I am wondering whether the step actually could be used effectively even for libraries which rely heavily on named types and static typing. Ultimately both LibraryStep and ClasspathAdder wind up calling GroovyClassLoader.addURL, just at different times. If you want to refer to these types from code loaded from Jenkinsfile at build initialization time then you must use @Library. But AFAIK any Pipeline script sources loaded after library has been run can also refer to those types. So it might work to use load (or evaluate + readTrusted) to split the Groovy in the project repo into two parts, one small bootstrap section that determines a library to load, and the bulk of it in another section (in another source file?) that is parsed & compiled against that library. Could be checked in a functional test as a proof of concept. Awkward but at least a possibility for the advanced use cases.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the pipeline source may not fully use classes defined by a library-step loaded lib, but can load another source which would be able to use those?

Or is the idea to use @Library for a boot-strapper of sorts, which would decide which real library/version to load/evaluate/library-step further - in context of the trusted (non-sandbox) library code?

Copy link
Author

@jimklimov jimklimov Oct 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jglick @dwnusbaum : regarding security... maybe I'm missing something, but I've slept on the idea and concluded that the library step might allow use of arbitrary URLs for the library source (including the URL to repo with pipeline, possibly a malicious one), hence the step only provides untrusted sandboxing.

In case of @Library the URL to repo is bolted by Jenkins admins in global config, at least for the trusted context (folders might let "users" add their own layers of libs, but untrusted - right?)

So one attack vector I'd assume here is that on SCM platform (GitHub etc.) random untrusted people are not forbidden to create branches in the library repository (originally or by attacker combined to include pipeline script) - e.g. some "main" branches are "protected" to only let maintainers update them, and other branches are available to any contributor. Can it be said that the job of security in this case is on the SCM platform (and on Jenkins side - the checkbox to allowBRANCH_NAME for this library - or rather not if the population of writers into the repo is not under control)?

Regarding a new checkbox, to allow or forbid using "this library" if pipeline seems to come from same repo, I suppose it can be done but with different ways to specify a repo URL (git-ssh, git://, https... just for git protocol alone) it can be circumvented... (or do SCM classes allow to test for "real equality" of repo URLs?)

In general though, you need to be very careful that your proposed feature and its implementation are not vulnerable to the same issue described in SECURITY-1951, which previously only applied to the library step but I think now could be reachable with @Library as well. Specifically, when the library is part of the same repository that the multibranch pipeline is building, untrusted users must not be able to modify the SCM branch used by a trusted library to one that they control, otherwise anyone who can trigger builds against a branch of the repository that they control (e.g. file a PR from a fork) can bypass the sandbox. You either need to reject this behavior outright, or run the library in the sandbox in this scenario.

actual strings are expanded by the plugin as it pre-processes
the pipeline script before compilation. Tokens spelled in the
<code>@Library</code> annotation are not Groovy variables!
The values substituted here are not influenced by run-time
interpretation of the pipeline script source text!
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div>
If checked, and if scripts are allowed to select a custom version
of the library by appending literally <code>@${BRANCH_NAME}</code>
in the <code>@Library</code> annotation, then for pull request
builds additional fall-back library branches would include the
names used as <code>CHANGE_BRANCH</code> (name of source branch
of pipeline pull request) and <code>CHANGE_TARGET</code> (name
of target branch of pipeline pull request), if such branch names
already exist in the trusted shared library repository.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div>
If checked, scripts may select a custom version of the library
by appending literally <code>@${env.VARNAME}</code> pattern in
the <code>@Library</code> annotation, to use current value of
chosen environment variable named <code>VARNAME</code> if it
is defined in job properties or on the build agent.<br/>
If such branch name is not resolvable as an environment variable
or not present in library storage (SCM, release artifacts...),
it would fall back to default version you select here.<br/>
Keep in mind that while the markup for such variable version
specification is intentionally similar to what you would use
in pipeline Groovy code, for simpler use and maintenance, the
actual strings are expanded by the plugin as it pre-processes
the pipeline script before compilation. Tokens spelled in the
<code>@Library</code> annotation are not Groovy variables!
The values substituted here are not influenced by run-time
interpretation of the pipeline script source text!
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
If checked, the <code>defaultedVersion</code> method would print
its progress trying to resolve <code>@${BRANCH_NAME}</code> in the
<code>@Library</code> annotation into the build log.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@

import java.util.Collections;

import hudson.AbortException;
import hudson.plugins.git.GitSCM;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.Assert;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.jvnet.hudson.test.Issue;
Expand Down Expand Up @@ -92,6 +94,109 @@ public class LibraryConfigurationTest {
assertNull(cfg.getDefaultVersion());
}

@Issue("JENKINS-69731")
@Test public void nullPresentDefaultedVersion() {
String libraryName = "valid-name";
String defaultVersion = "master";

LibraryConfiguration cfg = new LibraryConfiguration(libraryName, new SCMRetriever(new GitSCM("https://phony.jenkins.io/bar.git")));
cfg.setDefaultVersion(defaultVersion);

assertEquals("master", cfg.getDefaultVersion());
try {
assertEquals("master", cfg.defaultedVersion(null));
} catch(AbortException ae) {
Assert.fail("LibraryConfiguration.defaultedVersion() threw an AbortException when it was not expected: " + ae.getMessage());
}
}

@Issue("JENKINS-69731")
@Test public void nullAbsentDefaultedVersion() {
String libraryName = "valid-name";

LibraryConfiguration cfg = new LibraryConfiguration(libraryName, new SCMRetriever(new GitSCM("https://phony.jenkins.io/bar.git")));

assertEquals(null, cfg.getDefaultVersion());
assertThrows(AbortException.class, () -> cfg.defaultedVersion(null));
}

@Issue("JENKINS-69731")
@Test public void forbiddenOverrideDefaultedVersion() {
String libraryName = "valid-name";

LibraryConfiguration cfg = new LibraryConfiguration(libraryName, new SCMRetriever(new GitSCM("https://phony.jenkins.io/bar.git")));
cfg.setAllowVersionOverride(false);

assertEquals(false, cfg.isAllowVersionOverride());
assertThrows(AbortException.class, () -> cfg.defaultedVersion("branchname"));
}

@Issue("JENKINS-69731")
@Test public void allowedOverrideDefaultedVersion() {
String libraryName = "valid-name";

LibraryConfiguration cfg = new LibraryConfiguration(libraryName, new SCMRetriever(new GitSCM("https://phony.jenkins.io/bar.git")));
cfg.setAllowVersionOverride(true);

assertEquals(true, cfg.isAllowVersionOverride());
try {
assertEquals("branchname", cfg.defaultedVersion("branchname"));
} catch(AbortException ae) {
Assert.fail("LibraryConfiguration.defaultedVersion() threw an AbortException when it was not expected: " + ae.getMessage());
}
}

@Issue("JENKINS-69731")
@Test public void notAllowedOverrideDefaultedVersionWhenBRANCH_NAME() {
String libraryName = "valid-name";

LibraryConfiguration cfg = new LibraryConfiguration(libraryName, new SCMRetriever(new GitSCM("https://phony.jenkins.io/bar.git")));
cfg.setAllowVersionOverride(true);
cfg.setAllowBRANCH_NAME(false);
cfg.setTraceDefaultedVersion(true);

assertEquals(true, cfg.isAllowVersionOverride());
assertEquals(false, cfg.isAllowBRANCH_NAME());
assertEquals(true, cfg.isTraceDefaultedVersion());
assertThrows(AbortException.class, () -> cfg.defaultedVersion("${BRANCH_NAME}"));
/* This SHOULD NOT return a version string that literally remains '${BRANCH_NAME}'! */
}

@Issue("JENKINS-69731")
@Test public void allowedBRANCH_NAMEnoRunPresentDefaultedVersion() {
String libraryName = "valid-name";
String defaultVersion = "master";

LibraryConfiguration cfg = new LibraryConfiguration(libraryName, new SCMRetriever(new GitSCM("https://phony.jenkins.io/bar.git")));
cfg.setDefaultVersion(defaultVersion);
cfg.setAllowBRANCH_NAME(true);
cfg.setTraceDefaultedVersion(true);

assertEquals(true, cfg.isAllowBRANCH_NAME());
try {
assertEquals("master", cfg.defaultedVersion("${BRANCH_NAME}", null, null));
} catch(AbortException ae) {
Assert.fail("LibraryConfiguration.defaultedVersion() threw an AbortException when it was not expected: " + ae.getMessage());
}
}

@Issue("JENKINS-69731")
@Test public void allowedBRANCH_NAMEnoRunAbsentDefaultedVersion() {
String libraryName = "valid-name";

LibraryConfiguration cfg = new LibraryConfiguration(libraryName, new SCMRetriever(new GitSCM("https://phony.jenkins.io/bar.git")));
cfg.setAllowBRANCH_NAME(true);
cfg.setTraceDefaultedVersion(true);

assertEquals(true, cfg.isAllowBRANCH_NAME());
assertThrows(AbortException.class, () -> cfg.defaultedVersion("${BRANCH_NAME}", null, null));
}

/* Note: further tests for JENKINS-69731 behaviors with allowBRANCH_NAME
* would rely on having a Run with or without a BRANCH_NAME envvar, and
* a TaskListener, and a (mock?) LibraryRetriever that would confirm or
* deny existence of a requested "version" (e.g. Git branch) of the lib.
* For examples, see e.g. SCMSourceRetrieverTest codebase.
*/

}
Loading