Skip to content

Commit

Permalink
Merge pull request #49 from jglick/SCMFileSystem-JENKINS-33273
Browse files Browse the repository at this point in the history
[JENKINS-33273] Use SCMFileSystem where available
  • Loading branch information
jglick committed Jan 31, 2017
2 parents c26d3dc + 91584fb commit 8aa119c
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 83 deletions.
Expand Up @@ -24,21 +24,25 @@

package org.jenkinsci.plugins.workflow.multibranch;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.AbortException;
import hudson.Extension;
import hudson.FilePath;
import hudson.Functions;
import hudson.model.Computer;
import hudson.model.ItemGroup;
import hudson.model.Job;
import hudson.model.Node;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.TopLevelItem;
import hudson.scm.SCM;
import hudson.slaves.WorkspaceList;
import java.io.IOException;
import javax.inject.Inject;
import jenkins.branch.Branch;
import jenkins.model.Jenkins;
import jenkins.scm.api.SCMFileSystem;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMRevisionAction;
Expand Down Expand Up @@ -81,9 +85,35 @@ public static class Execution extends AbstractSynchronousNonBlockingStepExecutio
@StepContextParameter private transient Run<?,?> build;
@StepContextParameter private transient TaskListener listener;

@SuppressFBWarnings(value="RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE", justification="FB thinks standaloneSCM is known to be null, but this seems like a FB bug")
@Override protected String run() throws Exception {
Job<?, ?> job = build.getParent();
// Adapted from CpsScmFlowDefinition:
// Portions adapted from SCMBinder, SCMVar, and CpsScmFlowDefinition:
SCM standaloneSCM = null;
BranchJobProperty property = job.getProperty(BranchJobProperty.class);
if (property == null) {
if (job instanceof WorkflowJob) {
FlowDefinition defn = ((WorkflowJob) job).getDefinition();
if (defn instanceof CpsScmFlowDefinition) {
// JENKINS-31386: retrofit to work with standalone projects, without doing any trust checks.
standaloneSCM = ((CpsScmFlowDefinition) defn).getScm();
try (SCMFileSystem fs = SCMFileSystem.of(job, standaloneSCM)) {
if (fs != null) { // JENKINS-33273
try {
String text = fs.child(step.path).contentAsString();
listener.getLogger().println("Obtained " + step.path + " from " + standaloneSCM.getKey());
return text;
} catch (IOException | InterruptedException x) {
listener.error("Could not do lightweight checkout, falling back to heavyweight").println(Functions.printThrowable(x).trim());
}
}
}
}
}
throw new AbortException("‘readTrusted’ is only available when using “" +
Jenkins.getActiveInstance().getDescriptorByType(WorkflowMultiBranchProject.DescriptorImpl.class).getDisplayName() +
"” or “" + Jenkins.getActiveInstance().getDescriptorByType(CpsScmFlowDefinition.DescriptorImpl.class).getDisplayName() + "”");
}
Node node = Jenkins.getActiveInstance();
FilePath dir;
if (job instanceof TopLevelItem) {
Expand All @@ -103,78 +133,79 @@ public static class Execution extends AbstractSynchronousNonBlockingStepExecutio
if (computer == null) {
throw new IOException(node.getDisplayName() + " may be offline");
}
WorkspaceList.Lease lease = computer.getWorkspaceList().acquire(dir);
try {
// Adapted from SCMBinder:
BranchJobProperty property = job.getProperty(BranchJobProperty.class);
if (property == null) {
// As in SCMVar:
if (job instanceof WorkflowJob) {
FlowDefinition defn = ((WorkflowJob) job).getDefinition();
if (defn instanceof CpsScmFlowDefinition) {
// JENKINS-31386: retrofit to work with standalone projects, without doing any trust checks.
SCMStep delegate = new GenericSCMStep(((CpsScmFlowDefinition) defn).getScm());
delegate.setPoll(true);
delegate.setChangelog(true);
delegate.checkout(build, dir, listener, node.createLauncher(listener));
if (!file.exists()) {
throw new AbortException(file + " not found");
}
return file.readToString();
}
}
throw new AbortException("‘readTrusted’ is only available when using “" +
Jenkins.getActiveInstance().getDescriptorByType(WorkflowMultiBranchProject.DescriptorImpl.class).getDisplayName() +
"” or “" + Jenkins.getActiveInstance().getDescriptorByType(CpsScmFlowDefinition.DescriptorImpl.class).getDisplayName() + "”");
}
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");
}
SCMHead head = branch.getHead();
SCMRevision tip;
SCMRevisionAction action = build.getAction(SCMRevisionAction.class);
if (action != null) {
tip = action.getRevision();
} else {
tip = scmSource.fetch(head, listener);
if (tip == null) {
throw new AbortException("Could not determine exact tip revision of " + branch.getName());
}
build.addAction(new SCMRevisionAction(tip));
}
SCMRevision trusted = scmSource.getTrustedRevision(tip, listener);
String untrustedFile = null;
if (!tip.equals(trusted)) {
SCMStep delegate = new GenericSCMStep(scmSource.build(head, tip));
delegate.setPoll(false);
delegate.setChangelog(false);
if (standaloneSCM != null) {
try (WorkspaceList.Lease lease = computer.getWorkspaceList().acquire(dir)) {
SCMStep delegate = new GenericSCMStep(standaloneSCM);
delegate.setPoll(true);
delegate.setChangelog(true);
delegate.checkout(build, dir, listener, node.createLauncher(listener));
if (!file.exists()) {
throw new AbortException(file + " not found");
}
untrustedFile = file.readToString();
return file.readToString();
}
SCMStep delegate = new GenericSCMStep(scmSource.build(head, trusted));
delegate.setPoll(true);
delegate.setChangelog(true);
delegate.checkout(build, dir, listener, node.createLauncher(listener));
if (!file.exists()) {
throw new AbortException(file + " not found");
}
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");
}
SCMHead head = branch.getHead();
SCMRevision tip;
SCMRevisionAction action = build.getAction(SCMRevisionAction.class);
if (action != null) {
tip = action.getRevision();
} else {
tip = scmSource.fetch(head, listener);
if (tip == null) {
throw new AbortException("Could not determine exact tip revision of " + branch.getName());
}
String content = file.readToString();
if (untrustedFile != null && !untrustedFile.equals(content)) {
throw new AbortException(Messages.ReadTrustedStep__has_been_modified_in_an_untrusted_revis(step.path));
build.addAction(new SCMRevisionAction(tip));
}
SCMRevision trusted = scmSource.getTrustedRevision(tip, listener);
boolean trustCheck = !tip.equals(trusted);
String untrustedFile = null;
String content;
try (SCMFileSystem tipFS = trustCheck ? SCMFileSystem.of(scmSource, head, tip) : null;
SCMFileSystem trustedFS = SCMFileSystem.of(scmSource, head, trusted)) {
if (trustedFS != null && (!trustCheck || tipFS != null)) {
if (trustCheck) {
untrustedFile = tipFS.child(step.path).contentAsString();
}
content = trustedFS.child(step.path).contentAsString();
listener.getLogger().println("Obtained " + step.path + " from " + trusted);
} else {
listener.getLogger().println("Checking out " + head.getName() + " to read " + step.path);
try (WorkspaceList.Lease lease = computer.getWorkspaceList().acquire(dir)) {
if (trustCheck) {
SCMStep delegate = new GenericSCMStep(scmSource.build(head, tip));
delegate.setPoll(false);
delegate.setChangelog(false);
delegate.checkout(build, dir, listener, node.createLauncher(listener));
if (!file.exists()) {
throw new AbortException(file + " not found");
}
untrustedFile = file.readToString();
}
SCMStep delegate = new GenericSCMStep(scmSource.build(head, trusted));
delegate.setPoll(true);
delegate.setChangelog(true);
delegate.checkout(build, dir, listener, node.createLauncher(listener));
if (!file.exists()) {
throw new AbortException(file + " not found");
}
content = file.readToString();
}
}
return content;
} finally {
lease.release();
}
if (trustCheck && !untrustedFile.equals(content)) {
throw new AbortException(Messages.ReadTrustedStep__has_been_modified_in_an_untrusted_revis(step.path));
}
return content;
}

private FilePath getFilePathWithSuffix(FilePath baseWorkspace) {
Expand Down
Expand Up @@ -25,19 +25,23 @@
package org.jenkinsci.plugins.workflow.multibranch;

import hudson.Extension;
import hudson.Functions;
import hudson.model.Action;
import hudson.model.Descriptor;
import hudson.model.DescriptorVisibilityFilter;
import hudson.model.ItemGroup;
import hudson.model.Queue;
import hudson.model.TaskListener;
import hudson.scm.SCM;
import java.io.IOException;
import java.util.List;
import jenkins.branch.Branch;
import jenkins.scm.api.SCMFileSystem;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMRevisionAction;
import jenkins.scm.api.SCMSource;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition;
import org.jenkinsci.plugins.workflow.flow.FlowDefinition;
import org.jenkinsci.plugins.workflow.flow.FlowDefinitionDescriptor;
Expand Down Expand Up @@ -75,8 +79,23 @@ class SCMBinder extends FlowDefinition {
SCMRevision tip = scmSource.fetch(head, listener);
SCM scm;
if (tip != null) {
scm = scmSource.build(head, scmSource.getTrustedRevision(tip, listener));
build.addAction(new SCMRevisionAction(tip));
SCMRevision rev = scmSource.getTrustedRevision(tip, listener);
try (SCMFileSystem fs = SCMFileSystem.of(scmSource, head, rev)) {
if (fs != null) { // JENKINS-33273
String script = null;
try {
script = fs.child(WorkflowBranchProjectFactory.SCRIPT).contentAsString();
listener.getLogger().println("Obtained " + WorkflowBranchProjectFactory.SCRIPT + " from " + rev);
} catch (IOException | InterruptedException x) {
listener.error("Could not do lightweight checkout, falling back to heavyweight").println(Functions.printThrowable(x).trim());
}
if (script != null) {
return new CpsFlowDefinition(script, true).create(handle, listener, actions);
}
}
}
scm = scmSource.build(head, rev);
} else {
listener.error("Could not determine exact tip revision of " + branch.getName() + "; falling back to nondeterministic checkout");
// Build might fail later anyway, but reason should become clear: for example, branch was deleted before indexing could run.
Expand All @@ -96,6 +115,7 @@ class SCMBinder extends FlowDefinition {
/** Want to display this in the r/o configuration for a branch project, but not offer it on standalone jobs or in any other context. */
@Extension public static class HideMeElsewhere extends DescriptorVisibilityFilter {

@SuppressWarnings("rawtypes")
@Override public boolean filter(Object context, Descriptor descriptor) {
if (descriptor instanceof DescriptorImpl) {
return context instanceof WorkflowJob && ((WorkflowJob) context).getParent() instanceof WorkflowMultiBranchProject;
Expand Down
Expand Up @@ -25,9 +25,7 @@
package org.jenkinsci.plugins.workflow.multibranch;

import hudson.model.Result;
import jenkins.branch.BranchProperty;
import jenkins.branch.BranchSource;
import jenkins.branch.DefaultBranchPropertyStrategy;
import jenkins.plugins.git.GitSampleRepoRule;
import jenkins.plugins.git.GitStep;
import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition;
Expand Down Expand Up @@ -55,7 +53,7 @@ public class ReadTrustedStepTest {
sampleRepo.git("add", "Jenkinsfile", "message");
sampleRepo.git("commit", "--all", "--message=defined");
WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p");
mp.getSourcesList().add(new BranchSource(new SCMBinderTest.WarySource(null, sampleRepo.toString(), "", "*", "", false), new DefaultBranchPropertyStrategy(new BranchProperty[0])));
mp.getSourcesList().add(new BranchSource(new SCMBinderTest.WarySource(null, sampleRepo.toString(), "", "*", "", false)));
WorkflowJob p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, "master");
r.waitUntilNoActivity();
WorkflowRun b = p.getLastBuild();
Expand All @@ -64,6 +62,7 @@ public class ReadTrustedStepTest {
SCMBinderTest.assertRevisionAction(b);
r.assertBuildStatusSuccess(b);
r.assertLogContains("said how do you do", b);
r.assertLogContains("Obtained message from ", b);
String branch = "evil";
sampleRepo.git("checkout", "-b", branch);
sampleRepo.write("message", "your father smelt of elderberries");
Expand All @@ -76,6 +75,7 @@ public class ReadTrustedStepTest {
SCMBinderTest.assertRevisionAction(b);
r.assertBuildStatus(Result.FAILURE, b);
r.assertLogContains(Messages.ReadTrustedStep__has_been_modified_in_an_untrusted_revis("message"), b);
r.assertLogContains("Obtained message from ", b);
sampleRepo.write("message", "how do you do");
sampleRepo.write("ignored-message", "I fart in your general direction");
sampleRepo.git("add", "ignored-message");
Expand All @@ -86,6 +86,7 @@ public class ReadTrustedStepTest {
SCMBinderTest.assertRevisionAction(b);
r.assertBuildStatusSuccess(b);
r.assertLogContains("said how do you do", b);
r.assertLogContains("Obtained message from ", b);
}

@Test public void exactRevision() throws Exception {
Expand All @@ -96,7 +97,7 @@ public class ReadTrustedStepTest {
sampleRepo.git("add", "Jenkinsfile", "alpha", "beta");
sampleRepo.git("commit", "--all", "--message=defined");
WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p");
mp.getSourcesList().add(new BranchSource(new SCMBinderTest.WarySource(null, sampleRepo.toString(), "", "*", "", false), new DefaultBranchPropertyStrategy(new BranchProperty[0])));
mp.getSourcesList().add(new BranchSource(new SCMBinderTest.WarySource(null, sampleRepo.toString(), "", "*", "", false)));
WorkflowJob p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, "master");
SemaphoreStep.waitForStart("wait1/1", null);
WorkflowRun b = p.getLastBuild();
Expand Down Expand Up @@ -132,7 +133,7 @@ public class ReadTrustedStepTest {
sampleRepo.git("add", "Jenkinsfile", "lib.groovy");
sampleRepo.git("commit", "--all", "--message=defined");
WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p");
mp.getSourcesList().add(new BranchSource(new SCMBinderTest.WarySource(null, sampleRepo.toString(), "", "*", "", false), new DefaultBranchPropertyStrategy(new BranchProperty[0])));
mp.getSourcesList().add(new BranchSource(new SCMBinderTest.WarySource(null, sampleRepo.toString(), "", "*", "", false)));
WorkflowJob p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, "master");
r.waitUntilNoActivity();
WorkflowRun b = p.getLastBuild();
Expand Down Expand Up @@ -164,9 +165,13 @@ public class ReadTrustedStepTest {
sampleRepo.git("add", "Jenkinsfile", "message");
sampleRepo.git("commit", "--all", "--message=defined");
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsScmFlowDefinition(new GitStep(sampleRepo.toString()).createSCM(), "Jenkinsfile"));
GitStep step = new GitStep(sampleRepo.toString());
step.setCredentialsId("nonexistent"); // TODO work around NPE pending https://github.com/jenkinsci/git-plugin/pull/467
p.setDefinition(new CpsScmFlowDefinition(step.createSCM(), "Jenkinsfile"));
// TODO after https://github.com/jenkinsci/workflow-cps-plugin/pull/97 could setLightweight(true)
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("said how do you do", b);
r.assertLogContains("Obtained message from git ", b);
}

}

0 comments on commit 8aa119c

Please sign in to comment.