/* * The MIT License * * Copyright 2015 Jesse Glick. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jenkinsci.plugins.workflow.cps; import hudson.AbortException; import hudson.Extension; import hudson.FilePath; import hudson.model.Action; import hudson.model.Computer; import hudson.model.Job; import hudson.model.Node; import hudson.model.Queue; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.TopLevelItem; import hudson.scm.SCM; import hudson.scm.SCMDescriptor; import hudson.slaves.WorkspaceList; import java.io.IOException; 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; import org.jenkinsci.plugins.workflow.flow.FlowDefinitionDescriptor; import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; import org.jenkinsci.plugins.workflow.steps.scm.GenericSCMStep; 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; @PersistIn(JOB) 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; this.scriptPath = scriptPath; } public SCM getScm() { return scm; } 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); } } 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()); if (baseWorkspace == null) { throw new IOException(node.getDisplayName() + " may be offline"); } dir = getFilePathWithSuffix(baseWorkspace); } else { // should not happen, but just in case: dir = new FilePath(owner.getRootDir()); } String script; Computer computer = node.toComputer(); if (computer == null) { throw new IOException(node.getDisplayName() + " may be offline"); } SCMStep delegate = new GenericSCMStep(scm); delegate.setPoll(true); delegate.setChangelog(true); 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 throw new IOException(scriptFile + " is not inside " + dir); } if (!scriptFile.exists()) { throw new AbortException(scriptFile + " not found"); } script = scriptFile.readToString(); } CpsFlowExecution exec = new CpsFlowExecution(script, true, owner); exec.flowStartNodeActions.add(new WorkspaceActionImpl(dir, null)); return exec; } private FilePath getFilePathWithSuffix(FilePath baseWorkspace) { return baseWorkspace.withSuffix(getFilePathSuffix() + "script"); } private String getFilePathSuffix() { return System.getProperty(WorkspaceList.class.getName(), "@"); } @Extension public static class DescriptorImpl extends FlowDefinitionDescriptor { @Override public String getDisplayName() { return "Pipeline script from SCM"; } public Collection<? extends SCMDescriptor<?>> getApplicableDescriptors() { StaplerRequest req = Stapler.getCurrentRequest(); 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 } }