Skip to content

Commit

Permalink
Merge pull request #97 from jglick/SCMFileSystem-JENKINS-33273
Browse files Browse the repository at this point in the history
[JENKINS-33273] Allow lightweight checkouts to be used from CpsScmFlowDefinition
  • Loading branch information
jglick committed Mar 3, 2017
2 parents 6adb71c + 4e3fb85 commit 89b4e8d
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 12 deletions.
Expand Up @@ -42,6 +42,7 @@
import java.util.Collection;
import java.util.List;
import jenkins.model.Jenkins;
import jenkins.scm.api.SCMFileSystem;
import org.jenkinsci.plugins.workflow.cps.persistence.PersistIn;
import static org.jenkinsci.plugins.workflow.cps.persistence.PersistenceContext.JOB;
import org.jenkinsci.plugins.workflow.flow.FlowDefinition;
Expand All @@ -51,6 +52,7 @@
import org.jenkinsci.plugins.workflow.steps.scm.SCMStep;
import org.jenkinsci.plugins.workflow.support.actions.WorkspaceActionImpl;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;

Expand All @@ -59,6 +61,7 @@ public class CpsScmFlowDefinition extends FlowDefinition {

private final SCM scm;
private final String scriptPath;
private boolean lightweight;

@DataBoundConstructor public CpsScmFlowDefinition(SCM scm, String scriptPath) {
this.scm = scm;
Expand All @@ -73,18 +76,38 @@ public String getScriptPath() {
return scriptPath;
}

public boolean isLightweight() {
return lightweight;
}

@DataBoundSetter public void setLightweight(boolean lightweight) {
this.lightweight = lightweight;
}

@Override public CpsFlowExecution create(FlowExecutionOwner owner, TaskListener listener, List<? extends Action> actions) throws Exception {
for (Action a : actions) {
if (a instanceof CpsFlowFactoryAction2) {
return ((CpsFlowFactoryAction2) a).create(this, owner, actions);
}
}
FilePath dir;
Queue.Executable _build = owner.getExecutable();
if (!(_build instanceof Run)) {
throw new IOException("can only check out SCM into a Run");
}
Run<?,?> build = (Run<?,?>) _build;
if (isLightweight()) {
try (SCMFileSystem fs = SCMFileSystem.of(build.getParent(), scm)) {
if (fs != null) {
String script = fs.child(scriptPath).contentAsString();
listener.getLogger().println("Obtained " + scriptPath + " from " + scm.getKey());
return new CpsFlowExecution(script, true, owner);
} else {
listener.getLogger().println("Lightweight checkout support not available, falling back to full checkout.");
}
}
}
listener.getLogger().println("Checking out " + scm.getKey() + " to read " + scriptPath);
FilePath dir;
Node node = Jenkins.getActiveInstance();
if (build.getParent() instanceof TopLevelItem) {
FilePath baseWorkspace = node.getWorkspaceFor((TopLevelItem) build.getParent());
Expand All @@ -103,8 +126,7 @@ public String getScriptPath() {
SCMStep delegate = new GenericSCMStep(scm);
delegate.setPoll(true);
delegate.setChangelog(true);
WorkspaceList.Lease lease = computer.getWorkspaceList().acquire(dir);
try {
try (WorkspaceList.Lease lease = computer.getWorkspaceList().acquire(dir)) {
delegate.checkout(build, dir, listener, node.createLauncher(listener));
FilePath scriptFile = dir.child(scriptPath);
if (!scriptFile.absolutize().getRemote().replace('\\', '/').startsWith(dir.absolutize().getRemote().replace('\\', '/') + '/')) { // TODO JENKINS-26838
Expand All @@ -114,8 +136,6 @@ public String getScriptPath() {
throw new AbortException(scriptFile + " not found");
}
script = scriptFile.readToString();
} finally {
lease.release();
}
CpsFlowExecution exec = new CpsFlowExecution(script, true, owner);
exec.flowStartNodeActions.add(new WorkspaceActionImpl(dir, null));
Expand All @@ -138,10 +158,12 @@ private String getFilePathSuffix() {

public Collection<? extends SCMDescriptor<?>> getApplicableDescriptors() {
StaplerRequest req = Stapler.getCurrentRequest();
Job job = req != null ? req.findAncestorObject(Job.class) : null;
return job != null ? SCM._for(job) : /* TODO 1.599+ does this for job == null */ SCM.all();
Job<?,?> job = req != null ? req.findAncestorObject(Job.class) : null;
return SCM._for(job);
}

// TODO doCheckLightweight impossible to write even though we have SCMFileSystem.supports(SCM), because form validation cannot pass the SCM object

}

}
Expand Up @@ -29,6 +29,9 @@ THE SOFTWARE.
<f:entry field="scriptPath" title="${%Script Path}">
<f:textbox default="Jenkinsfile"/>
</f:entry>
<f:entry field="lightweight" title="${%Lightweight checkout}">
<f:checkbox default="true"/>
</f:entry>
<f:block>
<a href="pipeline-syntax" target="_blank">${%Pipeline Syntax}</a>
</f:block>
Expand Down
@@ -0,0 +1,7 @@
<div>
If selected, try to obtain the Pipeline script contents directly from the SCM without performing a full checkout.
The advantage of this mode is its efficiency; however, you will not get any changelogs or polling based on the SCM.
(If you use <code>checkout scm</code> during the build, this will populate the changelog and initialize polling.)
Also build parameters will not be substituted into SCM configuration in this mode.
Only selected SCM plugins support this mode.
</div>
Expand Up @@ -60,9 +60,24 @@ public class CpsScmFlowDefinitionTest {
@Rule public JenkinsRule r = new JenkinsRule();
@Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule();

@Test public void configRoundtrip() throws Exception {
sampleRepo.init();
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
CpsScmFlowDefinition def = new CpsScmFlowDefinition(new GitStep(sampleRepo.toString()).createSCM(), "Jenkinsfile");
def.setLightweight(true);
p.setDefinition(def);
r.configRoundtrip(p);
def = (CpsScmFlowDefinition) p.getDefinition();
assertEquals("Jenkinsfile", def.getScriptPath());
assertTrue(def.isLightweight());
assertEquals(GitSCM.class, def.getScm().getClass());
}

@Test public void basics() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsScmFlowDefinition(new SingleFileSCM("flow.groovy", "echo 'hello from SCM'"), "flow.groovy"));
CpsScmFlowDefinition def = new CpsScmFlowDefinition(new SingleFileSCM("flow.groovy", "echo 'hello from SCM'"), "flow.groovy");
def.setLightweight(false); // currently the default, but just to be clear that we do rely on that in this test
p.setDefinition(def);
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
// TODO currently the log text is in Run.log, but not on FlowStartNode/LogAction, so not visible from Workflow Steps etc.
r.assertLogContains("hello from SCM", b);
Expand All @@ -84,7 +99,9 @@ public class CpsScmFlowDefinitionTest {
sampleRepo.git("commit", "--message=init");
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.addTrigger(new SCMTrigger("")); // no schedule, use notifyCommit only
p.setDefinition(new CpsScmFlowDefinition(new GitStep(sampleRepo.toString()).createSCM(), "flow.groovy"));
CpsScmFlowDefinition def = new CpsScmFlowDefinition(new GitStep(sampleRepo.toString()).createSCM(), "flow.groovy");
def.setLightweight(false);
p.setDefinition(def);
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Cloning the remote Git repository", b);
r.assertLogContains("version one", b);
Expand Down Expand Up @@ -115,7 +132,9 @@ public class CpsScmFlowDefinitionTest {
sampleRepo.git("add", "flow.groovy");
sampleRepo.git("commit", "--message=init");
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsScmFlowDefinition(new GitStep(sampleRepo.toString()).createSCM(), "flow.groovy"));
CpsScmFlowDefinition def = new CpsScmFlowDefinition(new GitStep(sampleRepo.toString()).createSCM(), "flow.groovy");
def.setLightweight(false);
p.setDefinition(def);
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogContains("Cloning the remote Git repository", b);
r.assertLogContains("version one", b);
Expand All @@ -125,6 +144,23 @@ public class CpsScmFlowDefinitionTest {
assertEquals(Collections.emptyList(), changeSets);
}

@Issue("JENKINS-33273")
@Test public void lightweight() throws Exception {
sampleRepo.init();
sampleRepo.write("flow.groovy", "echo 'version one'");
sampleRepo.git("add", "flow.groovy");
sampleRepo.git("commit", "--message=init");
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
GitStep step = new GitStep(sampleRepo.toString());
CpsScmFlowDefinition def = new CpsScmFlowDefinition(step.createSCM(), "flow.groovy");
def.setLightweight(true);
p.setDefinition(def);
WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
r.assertLogNotContains("Cloning the remote Git repository", b);
r.assertLogContains("Obtained flow.groovy from git " + sampleRepo, b);
r.assertLogContains("version one", b);
}

@Issue("JENKINS-28447")
@Test public void usingParameter() throws Exception {
sampleRepo.init();
Expand All @@ -135,9 +171,11 @@ public class CpsScmFlowDefinitionTest {
sampleRepo.write("flow.groovy", "echo 'version two'");
sampleRepo.git("commit", "--all", "--message=two");
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsScmFlowDefinition(new GitSCM(Collections.singletonList(new UserRemoteConfig(sampleRepo.fileUrl(), null, null, null)),
CpsScmFlowDefinition def = new CpsScmFlowDefinition(new GitSCM(Collections.singletonList(new UserRemoteConfig(sampleRepo.fileUrl(), null, null, null)),
Collections.singletonList(new BranchSpec("${VERSION}")),
false, Collections.<SubmoduleConfig>emptyList(), null, null, Collections.<GitSCMExtension>emptyList()), "flow.groovy"));
false, Collections.<SubmoduleConfig>emptyList(), null, null, Collections.<GitSCMExtension>emptyList()), "flow.groovy");
def.setLightweight(false); // TODO SCMFileSystem.of cannot pick up build parameters
p.setDefinition(def);
p.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("VERSION", "master")));
r.assertLogContains("version two", r.assertBuildStatusSuccess(p.scheduleBuild2(0)));
r.assertLogContains("version one", r.assertBuildStatusSuccess(p.scheduleBuild2(0, new ParametersAction(new StringParameterValue("VERSION", "one")))));
Expand Down

0 comments on commit 89b4e8d

Please sign in to comment.