From 3e78c9d40439d3f48b0037a8af2bb904b38ea7a4 Mon Sep 17 00:00:00 2001 From: dev-hanz-ops <97662291+dev-hanz-ops@users.noreply.github.com> Date: Tue, 25 Jan 2022 15:37:41 +0100 Subject: [PATCH] JENKINS-61654: allow customization of used SCMRevision --- .../workflow/multibranch/SCMBinder.java | 43 +++++++++++--- .../SCMRevisionCustomizationTrait.java | 15 +++++ .../FixedSCMRevisionCustomizationTrait.java | 56 +++++++++++++++++++ .../workflow/multibranch/SCMBinderTest.java | 31 ++++++++++ 4 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMRevisionCustomizationTrait.java create mode 100644 src/test/java/org/jenkinsci/plugins/workflow/multibranch/FixedSCMRevisionCustomizationTrait.java diff --git a/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java b/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java index 79c14393..a4f83e63 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java @@ -40,7 +40,10 @@ import hudson.model.TaskListener; import hudson.scm.SCM; import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.Optional; import jenkins.branch.Branch; import jenkins.scm.api.SCMFileSystem; import jenkins.scm.api.SCMHead; @@ -89,16 +92,26 @@ public SCMBinder(String scriptPath) { throw new IllegalStateException("inappropriate context"); } Branch branch = property.getBranch(); - ItemGroup parent = job.getParent(); - if (!(parent instanceof WorkflowMultiBranchProject)) { - throw new IllegalStateException("inappropriate context"); - } - SCMSource scmSource = ((WorkflowMultiBranchProject) parent).getSCMSource(branch.getSourceId()); - if (scmSource == null) { - throw new IllegalStateException(branch.getSourceId() + " not found"); + SCMSource scmSource = getScmSource(job, branch.getSourceId()); + + SCMRevision customizedRevision = Optional.ofNullable(scmSource.getTraits()) + .orElse(Collections.emptyList()).stream() + .filter(SCMRevisionCustomizationTrait.class::isInstance) + .map(SCMRevisionCustomizationTrait.class::cast) + .max(Comparator.comparing(SCMRevisionCustomizationTrait::getPrecedence)) + .map(trait -> trait.customize(build, listener)) + .orElse(null); + + SCMHead head; + SCMRevision tip; + if (customizedRevision != null) { + head = customizedRevision.getHead(); + tip = customizedRevision; + } else { + head = branch.getHead(); + tip = scmSource.fetch(head, listener); } - SCMHead head = branch.getHead(); - SCMRevision tip = scmSource.fetch(head, listener); + SCM scm; if (tip != null) { build.addAction(new SCMRevisionAction(scmSource, tip)); @@ -145,6 +158,18 @@ public SCMBinder(String scriptPath) { return new CpsScmFlowDefinition(scm, scriptPath).create(handle, listener, actions); } + private SCMSource getScmSource(WorkflowJob job, String scmSourceId) { + ItemGroup parent = job.getParent(); + if (!(parent instanceof WorkflowMultiBranchProject)) { + throw new IllegalStateException("inappropriate context"); + } + SCMSource scmSource = ((WorkflowMultiBranchProject) parent).getSCMSource(scmSourceId); + if (scmSource == null) { + throw new IllegalStateException(scmSourceId + " not found"); + } + return scmSource; + } + @Extension public static class DescriptorImpl extends FlowDefinitionDescriptor { @NonNull diff --git a/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMRevisionCustomizationTrait.java b/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMRevisionCustomizationTrait.java new file mode 100644 index 00000000..05585017 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMRevisionCustomizationTrait.java @@ -0,0 +1,15 @@ +package org.jenkinsci.plugins.workflow.multibranch; + +import hudson.model.TaskListener; +import jenkins.scm.api.SCMRevision; +import jenkins.scm.api.trait.SCMSourceTrait; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; + +public abstract class SCMRevisionCustomizationTrait extends SCMSourceTrait { + + /** Customize the SCMRevision. */ + public abstract SCMRevision customize(WorkflowRun build, TaskListener listener); + + /** If multiple traits of this type are found, the highest precedence will be taken. */ + public abstract int getPrecedence(); +} diff --git a/src/test/java/org/jenkinsci/plugins/workflow/multibranch/FixedSCMRevisionCustomizationTrait.java b/src/test/java/org/jenkinsci/plugins/workflow/multibranch/FixedSCMRevisionCustomizationTrait.java new file mode 100644 index 00000000..536994a8 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/workflow/multibranch/FixedSCMRevisionCustomizationTrait.java @@ -0,0 +1,56 @@ +package org.jenkinsci.plugins.workflow.multibranch; + +import hudson.Extension; +import hudson.model.TaskListener; +import jenkins.plugins.git.GitSCMBuilder; +import jenkins.plugins.git.GitSCMSource; +import jenkins.plugins.git.GitSCMSourceContext; +import jenkins.scm.api.SCMRevision; +import jenkins.scm.api.SCMRevisionAction; +import jenkins.scm.api.SCMSource; +import jenkins.scm.api.trait.SCMBuilder; +import jenkins.scm.api.trait.SCMSourceContext; +import jenkins.scm.api.trait.SCMSourceTraitDescriptor; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; + +/** Sample {@link SCMRevisionCustomizationTrait} that has a fixed revision to build. Git only in this example. */ +public class FixedSCMRevisionCustomizationTrait extends SCMRevisionCustomizationTrait { + + private final SCMSource scmSource; + private final SCMRevision revision; + + FixedSCMRevisionCustomizationTrait(SCMSource scmSource, SCMRevision revision) { + this.scmSource = scmSource; + this.revision = revision; + } + + @Override + public SCMRevision customize(WorkflowRun build, TaskListener listener) { + build.addAction(new SCMRevisionAction(scmSource, revision)); + return revision; + } + + @Override + public int getPrecedence() { + return 0; + } + + @Extension + public static class DescriptorImpl extends SCMSourceTraitDescriptor { + + @Override + public Class getContextClass() { + return GitSCMSourceContext.class; + } + + @Override + public Class getSourceClass() { + return GitSCMSource.class; + } + + @Override + public Class getBuilderClass() { + return GitSCMBuilder.class; + } + } +} diff --git a/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java b/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java index 49a03e65..ce8b8c04 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java @@ -42,6 +42,7 @@ import java.util.TreeSet; import jenkins.branch.BranchSource; import jenkins.model.Jenkins; +import jenkins.plugins.git.GitBranchSCMHead; import jenkins.plugins.git.GitBranchSCMRevision; import jenkins.plugins.git.GitSCMSource; import jenkins.plugins.git.GitSampleRepoRule; @@ -49,6 +50,7 @@ import jenkins.scm.api.SCMRevision; import jenkins.scm.api.SCMRevisionAction; import jenkins.scm.api.SCMSourceDescriptor; +import jenkins.scm.api.trait.SCMSourceTrait; import jenkins.scm.impl.subversion.SubversionSCMFileSystem; import jenkins.scm.impl.subversion.SubversionSCMSource; import static org.hamcrest.Matchers.*; @@ -116,6 +118,35 @@ public class SCMBinderTest { assertFalse(iterator.hasNext()); } + @Test public void customRevision() throws Exception { + sampleGitRepo.init(); + sampleGitRepo.write("Jenkinsfile", "semaphore 'wait'; node {checkout scm; echo readFile('file')}"); + sampleGitRepo.write("file", "initial content"); + sampleGitRepo.git("add", "Jenkinsfile"); + sampleGitRepo.git("commit", "--all", "--message=flow"); + String firstCommit = sampleGitRepo.head(); + sampleGitRepo.write("file", "subsequent content"); + sampleGitRepo.git("commit", "--all", "--message=tweaked"); // will not be built + GitBranchSCMHead head = new GitBranchSCMHead("master"); + GitBranchSCMRevision firstRevision = new GitBranchSCMRevision(head, firstCommit); + WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); + GitSCMSource gitSCMSource = new GitSCMSource(null, sampleGitRepo.toString(), "", "*", "", false); + List scmTraits = gitSCMSource.getTraits(); + scmTraits.add(new FixedSCMRevisionCustomizationTrait(gitSCMSource, firstRevision)); + gitSCMSource.setTraits(scmTraits); + mp.getSourcesList().add(new BranchSource(gitSCMSource)); + WorkflowJob p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, "master"); + SemaphoreStep.waitForStart("wait/1", null); + WorkflowRun b1 = p.getLastBuild(); + assertNotNull(b1); + assertEquals(1, b1.getNumber()); + assertRevisionAction(b1); + r.assertLogContains("Obtained Jenkinsfile from ", b1); + SemaphoreStep.success("wait/1", null); + r.assertLogContains("initial content", r.waitForCompletion(b1)); + r.assertLogNotContains("SUBSEQUENT CONTENT", b1); + } + public static void assertRevisionAction(WorkflowRun build) { SCMRevisionAction revisionAction = build.getAction(SCMRevisionAction.class); assertNotNull(revisionAction);