/* * The MIT License * * Copyright (c) 2012, Cisco Systems, Inc., Max Spring * * 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 com.cisco.step.jenkins.plugins.jenkow; import hudson.Extension; import hudson.Launcher; import hudson.model.Action; import hudson.model.BuildListener; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Result; import hudson.model.Run; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import hudson.util.FormValidation; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import jenkins.model.Jenkins; import net.sf.json.JSONObject; import org.activiti.engine.HistoryService; import org.activiti.engine.ProcessEngine; import org.activiti.engine.RuntimeService; import org.activiti.engine.history.HistoricDetail; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.impl.persistence.entity.HistoricDetailVariableInstanceUpdateEntity; import org.jenkinsci.plugins.database.Database; import org.jenkinsci.plugins.database.DatabaseDescriptor; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; public class JenkowBuilder extends Builder{ private String workflowName; // TODO 8: make this a singleton which can be persisted private Map<String,JenkowAction> deferredActions; // Fields in config.jelly must match the parameter names in the "DataBoundConstructor" @DataBoundConstructor public JenkowBuilder(String workflowName) { this.workflowName = workflowName; } public String getWorkflowName() { return workflowName; } public void addDeferredAction(JenkowAction ja){ if (deferredActions == null) deferredActions = new HashMap<String,JenkowAction>(); deferredActions.put(ja.getCalleeJobName(),ja); } public JenkowAction getDeferredAction(Run r){ return (deferredActions == null)? null : deferredActions.get(r.getParent().getDisplayName()); } @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, FileNotFoundException { PrintStream log = listener.getLogger(); BuildLoggerMap.put(build,log); ProcessEngine eng = JenkowEngine.getEngine(); RuntimeService rtSvc = eng.getRuntimeService(); String procId = null; boolean result = true; try { procId = WfUtil.launchWf(log,workflowName,build.getParent().getName(),build.getNumber()); if (procId != null){ // TODO 5: is there a better way than polling to detect the termination of a process? HistoryService hstSvc = eng.getHistoryService(); while (true){ // TODO 8: can we get a hold of any exception the engine is throwing and show it at least in the log? // TODO 8: builder should log each time the process makes a state change HistoricProcessInstance hProcInst = hstSvc.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult(); if (hProcInst.getEndTime() != null){ String wbr = getWorkflowBuildResult(hstSvc,procId); log.println(Consts.UI_PREFIX+": \""+workflowName+"\" ended ("+wbr+")"); if (Result.FAILURE.toString().equals(wbr)) result = false; break; } Thread.sleep(1000); } } } finally { BuildLoggerMap.remove(build); if (procId != null && rtSvc.createExecutionQuery().processInstanceId(procId).singleResult() != null){ // should kick in when the current build is aborted log.println(Consts.UI_PREFIX+": aborting \""+workflowName+"\" (#"+procId+")"); rtSvc.deleteProcessInstance(procId,build.getFullDisplayName()+" finished"); } } return result; } private String getWorkflowBuildResult(HistoryService hstSvc, String procId){ for (HistoricDetail hd : hstSvc.createHistoricDetailQuery().variableUpdates().processInstanceId(procId).list()){ if (hd instanceof HistoricDetailVariableInstanceUpdateEntity){ HistoricDetailVariableInstanceUpdateEntity hdvar = (HistoricDetailVariableInstanceUpdateEntity)hd; if (Consts.BUILD_RESULT_NAME.equals(hdvar.getName())){ Object v = hdvar.getValue(); if (v != null) return v.toString(); } } } return null; } public WorkflowDiagram getWorkflowDiagram() { return new WorkflowDiagram(workflowName); } @Override public DescriptorImpl getDescriptor() { return (DescriptorImpl)super.getDescriptor(); } public static DescriptorImpl descriptor() { return Jenkins.getInstance().getDescriptorByType(JenkowBuilder.DescriptorImpl.class); } @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Builder> { private Database database; // TODO 8: make workflowRepoRoot optional in config UI, similar to "External Database" // private String workflowRepoRoot; @Deprecated private transient JenkowEngineConfig engineConfig; public DescriptorImpl(){ super(JenkowBuilder.class); load(); if (engineConfig!=null) { database = engineConfig.toDatabase(); engineConfig = null; } } public FormValidation doCheckWorkflowName(@QueryParameter String value) throws IOException, ServletException { return WfUtil.checkWorkflowName(value); } public void doCreateWorkflow(@QueryParameter String wfn) throws Exception{ JenkowPlugin.getInstance().getRepo().ensureWorkflowDefinition(wfn); } public boolean isApplicable(Class<? extends AbstractProject> aClass) { return true; } public String getDisplayName() { return Consts.UI_PREFIX; } @Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { req.bindJSON(this,formData); save(); return true; } public Database getDatabase() { if (database==null) return new H2DemoDatabase(); return database; } public List<DatabaseDescriptor> getDatabaseDescriptors() { List<DatabaseDescriptor> r = new ArrayList<DatabaseDescriptor>(); r.add(H2DemoDatabase.DESCRIPTOR); r.addAll(DatabaseDescriptor.all()); return r; } } }