Add Notifier for accepting a MR on success

This commit is contained in:
Robin Müller 2016-09-04 18:33:35 +02:00
parent 1cae833f60
commit f7bea1ae87
6 changed files with 216 additions and 69 deletions

View File

@ -6,6 +6,7 @@ import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.MergeRequestHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.NoteHook;
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PushHook;
import com.dabsquared.gitlabjenkins.publisher.GitLabAcceptMergeRequestPublisher;
import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher;
import com.dabsquared.gitlabjenkins.publisher.GitLabMessagePublisher;
import com.dabsquared.gitlabjenkins.publisher.GitLabVotePublisher;
@ -81,7 +82,7 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
private transient PushHookTriggerHandler pushHookTriggerHandler;
private transient MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler;
private transient NoteHookTriggerHandler noteHookTriggerHandler;
private boolean acceptMergeRequestOnSuccess = false;
private transient boolean acceptMergeRequestOnSuccess;
@DataBoundConstructor
@ -149,6 +150,9 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
if (trigger.addVoteOnMergeRequest) {
project.getPublishersList().add(new GitLabVotePublisher());
}
if (trigger.acceptMergeRequestOnSuccess) {
project.getPublishersList().add(new GitLabAcceptMergeRequestPublisher());
}
project.save();
}
}

View File

@ -94,8 +94,8 @@ public interface GitLabApi {
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/merge_request/{mergeRequestId}/merge")
void acceptMergeRequest(@PathParam("projectId") String projectId,
@Path("/projects/{projectId}/merge_requests/{mergeRequestId}/merge")
void acceptMergeRequest(@PathParam("projectId") Integer projectId,
@PathParam("mergeRequestId") Integer mergeRequestId,
@QueryParam("merge_commit_message") String mergeCommitMessage,
@QueryParam("should_remove_source_branch") boolean shouldRemoveSourceBranch);
@ -107,13 +107,6 @@ public interface GitLabApi {
@PathParam("mergeRequestId") Integer mergeRequestId,
@QueryParam("body") String body);
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/merge_requests/{mergeRequestId}/notes")
void createMergeRequestNote(@PathParam("projectId") String projectId,
@PathParam("mergeRequestId") Integer mergeRequestId,
@QueryParam("body") String body);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/projects/{projectId}/merge_requests")

View File

@ -1,59 +0,0 @@
package com.dabsquared.gitlabjenkins.listener;
import com.dabsquared.gitlabjenkins.GitLabPushTrigger;
import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import hudson.Extension;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import jenkins.model.Jenkins;
import javax.annotation.Nonnull;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.WebApplicationException;
import static com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty.getClient;
/**
* @author Robin Müller
*/
@Extension
public class GitLabMergeRequestRunListener extends RunListener<Run<?, ?>> {
@Override
public void onCompleted(Run<?, ?> build, @Nonnull TaskListener listener) {
GitLabPushTrigger trigger = GitLabPushTrigger.getFromJob(build.getParent());
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
if (trigger != null && cause != null && (cause.getData().getActionType() == CauseData.ActionType.MERGE || cause.getData().getActionType() == CauseData.ActionType.NOTE)) {
String buildUrl = getBuildUrl(build);
Result buildResult = build.getResult();
Integer projectId = cause.getData().getTargetProjectId();
Integer mergeRequestId = cause.getData().getMergeRequestId();
if (buildResult == Result.SUCCESS) {
acceptMergeRequestIfNecessary(build, trigger, listener, projectId.toString(), mergeRequestId);
}
}
}
private String getBuildUrl(Run<?, ?> build) {
return Jenkins.getInstance().getRootUrl() + build.getUrl();
}
private void acceptMergeRequestIfNecessary(Run<?, ?> build, GitLabPushTrigger trigger, TaskListener listener, String projectId, Integer mergeRequestId) {
if (trigger.getAcceptMergeRequestOnSuccess()) {
try {
GitLabApi client = getClient(build);
if (client == null) {
listener.getLogger().println("No GitLab connection configured");
} else {
client.acceptMergeRequest(projectId, mergeRequestId, "Merge Request accepted by jenkins build success", false);
}
} catch (WebApplicationException | ProcessingException e) {
listener.getLogger().printf("Failed to accept merge request: %s", e.getMessage());
}
}
}
}

View File

@ -0,0 +1,57 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
import hudson.Extension;
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.Publisher;
import org.kohsuke.stapler.DataBoundConstructor;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.WebApplicationException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author Robin Müller
*/
public class GitLabAcceptMergeRequestPublisher extends MergeRequestNotifier {
private static final Logger LOGGER = Logger.getLogger(GitLabAcceptMergeRequestPublisher.class.getName());
@DataBoundConstructor
public GitLabAcceptMergeRequestPublisher() { }
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
@Override
public String getDisplayName() {
return Messages.GitLabAcceptMergeRequestPublisher_DisplayName();
}
}
@Override
protected void perform(Run<?, ?> build, TaskListener listener, GitLabApi client, Integer projectId, Integer mergeRequestId) {
try {
if (build.getResult() == Result.SUCCESS) {
client.acceptMergeRequest(projectId, mergeRequestId, "Merge Request accepted by jenkins build success", false);
}
} catch (WebApplicationException | ProcessingException e) {
listener.getLogger().printf("Failed to accept merge request for project '%s': %s%n", projectId, e.getMessage());
LOGGER.log(Level.SEVERE, String.format("Failed to accept merge request for project '%s'", projectId), e);
}
}
}

View File

@ -2,3 +2,4 @@ GitLabCommitStatusPublisher.DisplayName=Publish build status to GitLab commit (G
name.required=Build name required.
GitLabMessagePublisher.DisplayName=Add note with build status on GitLab merge requests
GitLabVotePublisher.DisplayName=Add vote for build status on GitLab merge requests
GitLabAcceptMergeRequestPublisher.DisplayName=Accept GitLab merge request on success

View File

@ -0,0 +1,151 @@
package com.dabsquared.gitlabjenkins.publisher;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.dabsquared.gitlabjenkins.connection.GitLabConnection;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig;
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.model.StreamBuildListener;
import hudson.plugins.git.util.BuildData;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockserver.client.server.MockServerClient;
import org.mockserver.junit.MockServerRule;
import org.mockserver.model.HttpRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
/**
* @author Nikolay Ustinov
*/
public class GitLabAcceptMergeRequestPublisherTest {
private static final String GIT_LAB_CONNECTION = "GitLab";
private static final String API_TOKEN = "secret";
@ClassRule
public static MockServerRule mockServer = new MockServerRule(new Object());
@ClassRule
public static JenkinsRule jenkins = new JenkinsRule();
private MockServerClient mockServerClient;
private BuildListener listener;
@BeforeClass
public static void setupConnection() throws IOException {
GitLabConnectionConfig connectionConfig = jenkins.get(GitLabConnectionConfig.class);
String apiTokenId = "apiTokenId";
for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) {
List<Domain> domains = credentialsStore.getDomains();
credentialsStore.addCredentials(domains.get(0),
new StringCredentialsImpl(CredentialsScope.SYSTEM, apiTokenId, "GitLab API Token", Secret.fromString(API_TOKEN)));
}
}
connectionConfig.addConnection(new GitLabConnection(GIT_LAB_CONNECTION, "http://localhost:" + mockServer.getPort() + "/gitlab", apiTokenId, false, 10, 10));
}
@Before
public void setup() {
listener = new StreamBuildListener(jenkins.createTaskListener().getLogger(), Charset.defaultCharset());
mockServerClient = new MockServerClient("localhost", mockServer.getPort());
}
@After
public void cleanup() {
mockServerClient.reset();
}
@Test
public void success() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.SUCCESS, buildNumber);
HttpRequest[] requests = new HttpRequest[] {
prepareAcceptMergeRequestWithSuccessResponse(projectId, mergeRequestId)
};
GitLabAcceptMergeRequestPublisher publisher = spy(new GitLabAcceptMergeRequestPublisher());
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verify(requests);
}
@Test
public void failed() throws IOException, InterruptedException {
Integer buildNumber = 1;
Integer projectId = 3;
Integer mergeRequestId = 1;
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.FAILURE, buildNumber);
GitLabAcceptMergeRequestPublisher publisher = spy(new GitLabAcceptMergeRequestPublisher());
doReturn(projectId).when(publisher).getProjectId(build);
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
publisher.perform(build, null, listener);
mockServerClient.verifyZeroInteractions();
}
private HttpRequest prepareAcceptMergeRequestWithSuccessResponse(Integer projectId, Integer mergeRequestId) throws UnsupportedEncodingException {
HttpRequest updateCommitStatus = prepareAcceptMergeRequest(projectId, mergeRequestId);
mockServerClient.when(updateCommitStatus).respond(response().withStatusCode(200));
return updateCommitStatus;
}
private HttpRequest prepareAcceptMergeRequest(Integer projectId, Integer mergeRequestId) throws UnsupportedEncodingException {
return request()
.withPath("/gitlab/api/v3/projects/" + projectId + "/merge_requests/" + mergeRequestId + "/merge")
.withMethod("PUT")
.withHeader("PRIVATE-TOKEN", "secret")
.withQueryStringParameter("merge_commit_message", "Merge Request accepted by jenkins build success")
.withQueryStringParameter("should_remove_source_branch", "false");
}
private AbstractBuild mockBuild(String buildUrl, String gitLabConnection, Result result, Integer buildNumber, String... remoteUrls) {
AbstractBuild build = mock(AbstractBuild.class);
BuildData buildData = mock(BuildData.class);
when(buildData.getRemoteUrls()).thenReturn(new HashSet<>(Arrays.asList(remoteUrls)));
when(build.getAction(BuildData.class)).thenReturn(buildData);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getResult()).thenReturn(result);
when(build.getUrl()).thenReturn(buildUrl);
when(build.getNumber()).thenReturn(buildNumber);
AbstractProject<?, ?> project = mock(AbstractProject.class);
when(project.getProperty(GitLabConnectionProperty.class)).thenReturn(new GitLabConnectionProperty(gitLabConnection));
when(build.getProject()).thenReturn(project);
return build;
}
}