Skip to content

Commit

Permalink
Merge pull request #122 from yannack/delayedApprove
Browse files Browse the repository at this point in the history
Delayed approval mechanism [FIXED JENKINS-11409]
  • Loading branch information
rsandell committed Feb 10, 2014
2 parents 3246d8e + 3cf05c8 commit c512069
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 24 deletions.
Expand Up @@ -93,9 +93,10 @@ public synchronized void onCompleted(AbstractBuild r, TaskListener listener) {
GerritCause cause = getCause(r);
logger.info("Completed. Build: {} Cause: {}", r, cause);
if (cause != null) {
GerritTrigger trigger = GerritTrigger.getTrigger(r.getProject());
cleanUpGerritCauses(cause, r);
GerritTriggeredEvent event = cause.getEvent();
if (GerritTrigger.getTrigger(r.getProject()) != null) {
if (trigger != null) {
// There won't be a trigger if this job was run through a unit test
GerritTrigger.getTrigger(r.getProject()).notifyBuildEnded(event);
}
Expand Down Expand Up @@ -123,24 +124,56 @@ public synchronized void onCompleted(AbstractBuild r, TaskListener listener) {
}

updateTriggerContexts(r);
if (memory.isAllBuildsCompleted(event)) {
try {
logger.info("All Builds are completed for cause: {}", cause);
if (event instanceof GerritEventLifecycle) {
((GerritEventLifecycle)event).fireAllBuildsCompleted();
}
NotificationFactory.getInstance().queueBuildCompleted(memory.getMemoryImprint(event), listener);
} finally {
memory.forget(event);
}
} else {
logger.info("Waiting for more builds to complete for cause [{}]. Status: \n{}",
if (hasDelayedApproval(trigger)) {
logger.info("Delayed approval set. Waiting for delayed approval for cause [{}]. Status: \n{}",
cause, memory.getStatusReport(event));
} else {
allBuildsCompleted(event, cause, listener);
}
}
}
}

/**
* Whether the trigger has a delayed approval.
*
* @param trigger the trigger to look in.
* @return True only if the trigger is non null and has a delayed approval.
*/
private boolean hasDelayedApproval(GerritTrigger trigger) {
if (trigger != null) {
if (trigger.isDelayedApproval()) {
return true;
}
}
return false;
}

/**
* Manages the end of a Gerrit Event. Should be called after each build related to an event completes if that build
* should report back to Gerrit.
*
* @param event the Gerrit Event which may need to be completed.
* @param cause the Gerrit Cause which triggered the build initially.
* @param listener the Jenkins listener.
*/
public synchronized void allBuildsCompleted(GerritTriggeredEvent event, GerritCause cause, TaskListener listener) {
if (memory.isAllBuildsCompleted(event)) {
try {
logger.info("All Builds are completed for cause: {}", cause);
if (event instanceof GerritEventLifecycle) {
((GerritEventLifecycle)event).fireAllBuildsCompleted();
}
NotificationFactory.getInstance().queueBuildCompleted(memory.getMemoryImprint(event), listener);
} finally {
memory.forget(event);
}
} else {
logger.info("Waiting for more builds to complete for cause [{}]. Status: \n{}",
cause, memory.getStatusReport(event));
}
}

@Override
public synchronized void onStarted(AbstractBuild r, TaskListener listener) {
GerritCause cause = getCause(r);
Expand Down
@@ -0,0 +1,252 @@
/*
* The MIT License
*
* Copyright 2014 Smartmatic International Corporation. All rights reserved.
*
* 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.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger;


import hudson.Extension;
import hudson.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.kohsuke.stapler.DataBoundConstructor;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.model.AbstractProject;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Hudson;
import java.util.Map;
import java.io.PrintStream;
import hudson.Launcher;
import com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.ToGerritRunListener;
import com.sonyericsson.hudson.plugins.gerrit.trigger.Messages;


/**
* Triggers a build previously run to send its report to Gerrit, if it hadn't yet.
*
* @author Yannick Bréhon <yannick.brehon@smartmatic.com>
*/

public class GerritDelayedApprover extends Notifier {

private static final Logger logger = LoggerFactory.getLogger(GerritDelayedApprover.class);
private String delayedJob;
private String delayedBuildNumber;


/**
* Default DataBound Constructor.
*
* @param delayedJob the name of the job which may need a delayed approval. buildVariables are expanded.
* @param delayedBuildNumber the number of the build which may need a delayed approval. buildVariables are expanded.
*/
@DataBoundConstructor
public GerritDelayedApprover(
String delayedJob,
String delayedBuildNumber) {
this.delayedJob = delayedJob;
this.delayedBuildNumber = delayedBuildNumber;
}
/**
* The delayedBuildNumber, ie number of the build which may need a delayed approval.
*
* @return the delayedBuildNumber
*/
public String getDelayedBuildNumber() {
return delayedBuildNumber;
}

/**
* Sets the delayedBuildNumber.
*
* @param delayedBuildNumber
* the delayedBuildNumber
*/
public void setDelayedBuildNumber(String delayedBuildNumber) {
this.delayedBuildNumber = delayedBuildNumber;
}

/**
* The delayedJob, ie the name of the job which may need a delayed approval.
*
* @return the delayedJob
*/
public String getDelayedJob() {
return delayedJob;
}

/**
* Sets the delayedJob.
*
* @param delayedJob
* the delayedJob
*/
public void setDelayedJob(String delayedJob) {
this.delayedJob = delayedJob;
}

/**
* No concurrency management is required, new style extension.
* @return BuildStepMonitor.NONE
*
*/
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}

/**
* This extension needs to run after the build is completed.
* @return true
*
*/
@Override
public boolean needsToRunAfterFinalized() {
return true;
}

/**
* The actual performer method which will run in a post-build step and close a Gerrit Triggered event.
* @param build current build
* @param launcher launcher
* @param listener the build listener
* @throws InterruptedException if interrupted
* @return boolean indicating whether this perform step succeeded
*/
public boolean perform(final AbstractBuild<?, ?> build, final Launcher launcher, final BuildListener listener)
throws InterruptedException {
PrintStream consoleLogger = null;
if (listener != null) {
consoleLogger = listener.getLogger();
}

performLogger(consoleLogger, "Starting");
Map<String, String> buildVars = build.getBuildVariables();
String jobName = Util.replaceMacro(delayedJob, buildVars);
String numberStr = Util.replaceMacro(delayedBuildNumber, buildVars);
int number;
try {
number = Integer.parseInt(numberStr);
} catch (NumberFormatException e) {
performLogger(consoleLogger, "Could not find a job with number " + numberStr);
return true;
}
AbstractBuild initiatingBuild = locateBuild(jobName, number);
if (initiatingBuild == null) {
performLogger(consoleLogger, "Could not find a build for job " + jobName + " with number " + numberStr);
return true;
}
GerritCause cause = (GerritCause)initiatingBuild.getCause(GerritCause.class);
if (cause == null) {
performLogger(consoleLogger, "Job " + jobName + " with number " + numberStr
+ " was not started by Gerrit, not sending a Gerrit notification.");
return true;
}

ToGerritRunListener thelistener = ToGerritRunListener.getInstance();
thelistener.allBuildsCompleted(cause.getEvent(), cause, null);
return true;
}

/**
* Logging method which will log to the build's output if possible (not possible during unit-tests).
* @param consoleLogger the logger to use for logging
* @param message the message to log
*/
public void performLogger(PrintStream consoleLogger, String message) {
if (consoleLogger != null) {
consoleLogger.println("Gerrit Delayed Approver: " + message);
} else {
logger.info("Gerrit Delayed Approver: " + message);
}
}


/**
* Finds a build based on the project name and build number.
* @param jobName the name of the project inspected
* @param buildNumber the number of the build searched
* @return the build found (may be null)
*/
public AbstractBuild locateBuild(String jobName, int buildNumber) {
AbstractProject project = Hudson.getInstance().getItemByFullName(jobName, AbstractProject.class);
if (project == null) {
return null;
}
AbstractBuild build = (AbstractBuild)project.getBuildByNumber(buildNumber);
return build;
}

/*---------------------------------*/
/* ----- Descriptor stuff -------- */
/*---------------------------------*/

/**
* getter for the Descriptor.
* @return the associated descriptor
*/
@Override
public BuildStepDescriptor<Publisher> getDescriptor() {
return DESCRIPTOR;
}

/**
* The associated descriptor instance.
*/
public static final GerritDelayedApproverDescriptor DESCRIPTOR = new GerritDelayedApproverDescriptor();

/**
* Descriptor class.
*/
@Extension
public static final class GerritDelayedApproverDescriptor extends
BuildStepDescriptor<Publisher> {

//private static final Logger logger = LoggerFactory.getLogger(GerritDelayedApproverDescriptor.class);

/**
* Overridden constructor, needed to reload saved data.
*/
public GerritDelayedApproverDescriptor() {
super(GerritDelayedApprover.class);
}

@Override
public String getDisplayName() {
return Messages.SendDelayedGerritApproval();
}

@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}

/*@Override
public String getHelpFile() {
return "/plugin/build-publisher/help/config/publish.html";
}*/

}
}
Expand Up @@ -123,6 +123,7 @@ public class GerritTrigger extends Trigger<AbstractProject> implements GerritEve
private Integer gerritBuildNotBuiltVerifiedValue;
private Integer gerritBuildNotBuiltCodeReviewValue;
private boolean silentMode;
private boolean delayedApproval;
private boolean escapeQuotes;
private boolean noNameAndEmailParameters;
private String buildStartMessage;
Expand Down Expand Up @@ -178,6 +179,7 @@ public class GerritTrigger extends Trigger<AbstractProject> implements GerritEve
* Job specific Gerrit code review vote when a build is not built, null means
* that the global value should be used.
* @param silentMode Silent Mode on or off.
* @param delayedApproval Delayed Approval on or off.
* @param escapeQuotes EscapeQuotes on or off.
* @param noNameAndEmailParameters Whether to create parameters containing name and email
* @param buildStartMessage Message to write to Gerrit when a build begins
Expand Down Expand Up @@ -210,6 +212,7 @@ public GerritTrigger(
Integer gerritBuildNotBuiltVerifiedValue,
Integer gerritBuildNotBuiltCodeReviewValue,
boolean silentMode,
boolean delayedApproval,
boolean escapeQuotes,
boolean noNameAndEmailParameters,
String buildStartMessage,
Expand Down Expand Up @@ -237,6 +240,7 @@ public GerritTrigger(
this.gerritBuildNotBuiltVerifiedValue = gerritBuildNotBuiltVerifiedValue;
this.gerritBuildNotBuiltCodeReviewValue = gerritBuildNotBuiltCodeReviewValue;
this.silentMode = silentMode;
this.delayedApproval = delayedApproval;
this.escapeQuotes = escapeQuotes;
this.noNameAndEmailParameters = noNameAndEmailParameters;
this.buildStartMessage = buildStartMessage;
Expand Down Expand Up @@ -1233,6 +1237,16 @@ public boolean isSilentMode() {
return silentMode;
}

/**
* If delayed approval is on or off. When delayed approval is on there will be no automatic result of the build
* sent back to Gerrit. This will have to be sent using a different mechanism. Default is false.
*
* @return true if delayed approval is on.
*/
public boolean isDelayedApproval() {
return delayedApproval;
}

/**
* if escapeQuotes is on or off. When escapeQuotes is on this plugin will escape quotes in Gerrit event parameter
* string Default is true
Expand Down Expand Up @@ -1339,6 +1353,16 @@ public void setSilentMode(boolean silentMode) {
this.silentMode = silentMode;
}

/**
* Sets delayed approval to on or off. When delayed approval is on there will be no automatic result of the
* build sent back to Gerrit. This will have to be sent using a different mechanism. Default is false.
*
* @param delayedApproval true if delayed approval should be on.
*/
public void setDelayedApproval(boolean delayedApproval) {
this.delayedApproval = delayedApproval;
}

/**
* URL to send in comment to Gerrit.
*
Expand Down

0 comments on commit c512069

Please sign in to comment.