package com.cloudbees.jenkins;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractProject;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.util.ListBoxModel;
import jenkins.tasks.SimpleBuildStep;
import org.jenkinsci.plugins.github.common.ExpandableMessage;
import org.jenkinsci.plugins.github.extension.status.StatusErrorHandler;
import org.jenkinsci.plugins.github.status.GitHubCommitStatusSetter;
import org.jenkinsci.plugins.github.status.err.ChangingBuildStatusErrorHandler;
import org.jenkinsci.plugins.github.status.err.ShallowAnyErrorHandler;
import org.jenkinsci.plugins.github.status.sources.AnyDefinedRepositorySource;
import org.jenkinsci.plugins.github.status.sources.BuildDataRevisionShaSource;
import org.jenkinsci.plugins.github.status.sources.ConditionalStatusResultSource;
import org.jenkinsci.plugins.github.status.sources.DefaultCommitContextSource;
import org.jenkinsci.plugins.github.status.sources.DefaultStatusResultSource;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.github.GHCommitState;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.Collections;
import static com.cloudbees.jenkins.Messages.GitHubCommitNotifier_DisplayName;
import static com.google.common.base.Objects.firstNonNull;
import static hudson.model.Result.FAILURE;
import static hudson.model.Result.SUCCESS;
import static hudson.model.Result.UNSTABLE;
import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.jenkinsci.plugins.github.status.sources.misc.AnyBuildResult.onAnyResult;
import static org.jenkinsci.plugins.github.status.sources.misc.BetterThanOrEqualBuildResult.betterThanOrEqualTo;
/**
* Create commit status notifications on the commits based on the outcome of the build.
*
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
*/
public class GitHubCommitNotifier extends Notifier implements SimpleBuildStep {
private static final ExpandableMessage DEFAULT_MESSAGE = new ExpandableMessage("");
private ExpandableMessage statusMessage = DEFAULT_MESSAGE;
private final String resultOnFailure;
private static final Result[] SUPPORTED_RESULTS = {FAILURE, UNSTABLE, SUCCESS};
private static final Logger LOGGER = LoggerFactory.getLogger(GitHubCommitNotifier.class);
@Restricted(NoExternalUse.class)
public GitHubCommitNotifier() {
this(getDefaultResultOnFailure().toString());
}
/**
* @since 1.10
*/
@DataBoundConstructor
public GitHubCommitNotifier(String resultOnFailure) {
this.resultOnFailure = resultOnFailure;
}
/**
* @since 1.14.1
*/
public ExpandableMessage getStatusMessage() {
return statusMessage;
}
/**
* @since 1.14.1
*/
@DataBoundSetter
public void setStatusMessage(ExpandableMessage statusMessage) {
this.statusMessage = statusMessage;
}
/**
* @since 1.10
*/
@Nonnull
public String getResultOnFailure() {
return resultOnFailure != null ? resultOnFailure : getDefaultResultOnFailure().toString();
}
@Nonnull
public static Result getDefaultResultOnFailure() {
return FAILURE;
}
@Nonnull
/*package*/ Result getEffectiveResultOnFailure() {
return Result.fromString(trimToEmpty(resultOnFailure));
}
@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
@Override
public void perform(@NonNull Run<?, ?> build,
@NonNull FilePath ws,
@NonNull Launcher launcher,
@NonNull TaskListener listener) throws InterruptedException, IOException {
GitHubCommitStatusSetter setter = new GitHubCommitStatusSetter();
setter.setReposSource(new AnyDefinedRepositorySource());
setter.setCommitShaSource(new BuildDataRevisionShaSource());
setter.setContextSource(new DefaultCommitContextSource());
String content = firstNonNull(statusMessage, DEFAULT_MESSAGE).getContent();
if (isNotBlank(content)) {
setter.setStatusResultSource(new ConditionalStatusResultSource(
asList(
betterThanOrEqualTo(SUCCESS, GHCommitState.SUCCESS, content),
betterThanOrEqualTo(UNSTABLE, GHCommitState.FAILURE, content),
betterThanOrEqualTo(FAILURE, GHCommitState.ERROR, content),
onAnyResult(GHCommitState.PENDING, content)
)));
} else {
setter.setStatusResultSource(new DefaultStatusResultSource());
}
if (getEffectiveResultOnFailure().equals(SUCCESS)) {
setter.setErrorHandlers(Collections.<StatusErrorHandler>singletonList(new ShallowAnyErrorHandler()));
} else if (resultOnFailure == null) {
setter.setErrorHandlers(null);
} else {
setter.setErrorHandlers(Collections.<StatusErrorHandler>singletonList(
new ChangingBuildStatusErrorHandler(getEffectiveResultOnFailure().toString())));
}
setter.perform(build, ws, launcher, listener);
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
@Override
public String getDisplayName() {
return GitHubCommitNotifier_DisplayName();
}
public ListBoxModel doFillResultOnFailureItems() {
ListBoxModel items = new ListBoxModel();
for (Result result : SUPPORTED_RESULTS) {
items.add(result.toString());
}
return items;
}
}
}