package com.chikli.hudson.plugin.naginator; import hudson.Extension; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Result; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Notifier; import hudson.tasks.Publisher; import net.sf.json.JSONObject; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.DataBoundConstructor; import java.io.IOException; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.ObjectStreamException; import java.net.MalformedURLException; import java.net.URL; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.logging.Level; import java.util.logging.Logger; /** * Reschedules a build if the current one fails. * * @author Nayan Hajratwala <nayan@chikli.com> */ public class NaginatorPublisher extends Notifier { private final String regexpForRerun; private final boolean rerunIfUnstable; private final boolean checkRegexp; private boolean debug = false; @DataBoundConstructor public NaginatorPublisher(String regexpForRerun, boolean rerunIfUnstable, boolean checkRegexp) { this.regexpForRerun = regexpForRerun; this.checkRegexp = checkRegexp; this.rerunIfUnstable = rerunIfUnstable; } public boolean isRerunIfUnstable() { return rerunIfUnstable; } public boolean isCheckRegexp() { return checkRegexp; } public String getRegexpForRerun() { return regexpForRerun; } public void setDebug(boolean debug) { this.debug = debug; } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { // If the build was successful, we don't need to Nag, so just return true if (build.getResult() == Result.SUCCESS) { return true; } // If we're not set to rerun if unstable, and the build's unstable, return true. if ((!rerunIfUnstable) && (build.getResult() == Result.UNSTABLE)) { return true; } // If we're supposed to check for a regular expression in the build output before // scheduling a new build, do so. if (checkRegexp) { if (debug) LOGGER.log(Level.WARNING, "Got checkRegexp == true"); if ((regexpForRerun!=null) && (!regexpForRerun.equals(""))) { if (debug) LOGGER.log(Level.WARNING, "regexpForRerun - " + regexpForRerun); try { // If parseLog returns false, we didn't find the regular expression, // so return true. if (!parseLog(build.getLogFile(), regexpForRerun)) { if (debug) LOGGER.log(Level.WARNING, "regexp not in logfile"); return true; } } catch (IOException e) { e.printStackTrace(listener .error("error while parsing logs for naginator - forcing rebuild.")); } } } // if a build fails for a reason that cannot be immediately fixed, // immediate rescheduling may cause a very tight loop. // combined with publishers like e-mail, IM, this could flood the users. // // so to avoid this problem, progressively introduce delay until the next build // delay = the number of consective build problems * 5 mins // back off at most 3 hours int n=0; for(AbstractBuild<?,?> b=build; b!=null && b.getResult()!=Result.SUCCESS && n<60; b=b.getPreviousBuild()) n+=5; if (debug) LOGGER.log(Level.WARNING, "about to try to schedule a build"); return scheduleBuild(build, n); } /** * Wrapper method for mocking purposes. */ public boolean scheduleBuild(AbstractBuild<?, ?> build, int n) throws InterruptedException, IOException { // Schedule a new build with the back off if (debug) { try { build.setDescription("rebuild"); } catch (IOException e) { LOGGER.log(Level.WARNING, "couldn't set description: " + e.getStackTrace()); } return true; } else { return build.getProject().scheduleBuild(n*60, new NaginatorCause()); } } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.BUILD; } private boolean parseLog(File logFile, String regexp) throws IOException, InterruptedException { if (regexp == null) { return false; } // Assume default encoding and text files String line; Pattern pattern = Pattern.compile(regexp); BufferedReader reader = new BufferedReader(new FileReader(logFile)); while ((line = reader.readLine()) != null) { Matcher matcher = pattern.matcher(line); if (matcher.find()) { return true; } } return false; } @Override public DescriptorImpl getDescriptor() { // see Descriptor javadoc for more about what a descriptor is. return (DescriptorImpl) super.getDescriptor(); } /** * Descriptor for {@link NaginatorPublisher}. Used as a singleton. * The class is marked as public so that it can be accessed from views. * * <p> * See <tt>views/hudson/plugins/naginator/NaginatorBuilder/*.jelly</tt> * for the actual HTML fragment for the configuration screen. */ @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> { public DescriptorImpl() { super(NaginatorPublisher.class); } /** * This human readable name is used in the configuration screen. */ public String getDisplayName() { return "Retry build after failure (Naginator)"; } @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } /** * Creates a new instance of {@link NaginatorPublisher} from a submitted form. */ @Override public Notifier newInstance(StaplerRequest req, JSONObject formData) throws FormException { return req.bindJSON(NaginatorPublisher.class, formData); } } private static final Logger LOGGER = Logger.getLogger(NaginatorPublisher.class.getName()); }