Add Notifier for adding a vote to a GitLab MR (Fixes #168)
This commit is contained in:
parent
adc2c63b27
commit
b78c69d1ca
|
@ -8,6 +8,7 @@ import com.dabsquared.gitlabjenkins.gitlab.hook.model.NoteHook;
|
|||
import com.dabsquared.gitlabjenkins.gitlab.hook.model.PushHook;
|
||||
import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher;
|
||||
import com.dabsquared.gitlabjenkins.publisher.GitLabMessagePublisher;
|
||||
import com.dabsquared.gitlabjenkins.publisher.GitLabVotePublisher;
|
||||
import com.dabsquared.gitlabjenkins.trigger.TriggerOpenMergeRequest;
|
||||
import com.dabsquared.gitlabjenkins.trigger.branch.ProjectBranchesProvider;
|
||||
import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilter;
|
||||
|
@ -69,7 +70,7 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
|
|||
private boolean setBuildDescription = true;
|
||||
private transient boolean addNoteOnMergeRequest;
|
||||
private transient boolean addCiMessage;
|
||||
private boolean addVoteOnMergeRequest = true;
|
||||
private transient boolean addVoteOnMergeRequest;
|
||||
private transient boolean allowAllBranches = false;
|
||||
private transient String branchFilterName;
|
||||
private BranchFilterType branchFilterType;
|
||||
|
@ -145,6 +146,9 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
|
|||
if (trigger.addNoteOnMergeRequest) {
|
||||
project.getPublishersList().add(new GitLabMessagePublisher());
|
||||
}
|
||||
if (trigger.addVoteOnMergeRequest) {
|
||||
project.getPublishersList().add(new GitLabVotePublisher());
|
||||
}
|
||||
project.save();
|
||||
}
|
||||
}
|
||||
|
@ -177,10 +181,6 @@ public class GitLabPushTrigger extends Trigger<Job<?, ?>> {
|
|||
return setBuildDescription;
|
||||
}
|
||||
|
||||
public boolean getAddVoteOnMergeRequest() {
|
||||
return addVoteOnMergeRequest;
|
||||
}
|
||||
|
||||
public boolean getAcceptMergeRequestOnSuccess() {
|
||||
return acceptMergeRequestOnSuccess;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package com.dabsquared.gitlabjenkins.publisher;
|
||||
|
||||
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
|
||||
import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty;
|
||||
import com.dabsquared.gitlabjenkins.gitlab.api.GitLabApi;
|
||||
import hudson.Extension;
|
||||
import hudson.Launcher;
|
||||
import hudson.model.AbstractBuild;
|
||||
import hudson.model.AbstractProject;
|
||||
import hudson.model.BuildListener;
|
||||
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 org.kohsuke.stapler.DataBoundConstructor;
|
||||
|
||||
import javax.ws.rs.ProcessingException;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Robin Müller
|
||||
*/
|
||||
public class GitLabVotePublisher extends Notifier {
|
||||
private static final Logger LOGGER = Logger.getLogger(GitLabVotePublisher.class.getName());
|
||||
|
||||
@DataBoundConstructor
|
||||
public GitLabVotePublisher() { }
|
||||
|
||||
public BuildStepMonitor getRequiredMonitorService() {
|
||||
return BuildStepMonitor.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
|
||||
addVoteOnMergeRequest(build, listener);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Extension
|
||||
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return Messages.GitLabVotePublisher_DisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
private void addVoteOnMergeRequest(Run<?, ?> build, TaskListener listener) {
|
||||
String projectId = getProjectId(build);
|
||||
Integer mergeRequestId = getMergeRequestId(build);
|
||||
if (projectId != null && mergeRequestId != null) {
|
||||
try {
|
||||
GitLabApi client = getClient(build);
|
||||
if (client == null) {
|
||||
listener.getLogger().println("No GitLab connection configured");
|
||||
} else {
|
||||
client.createMergeRequestNote(projectId, mergeRequestId, getResultIcon(build.getResult()));
|
||||
}
|
||||
} catch (WebApplicationException | ProcessingException e) {
|
||||
listener.getLogger().printf("Failed to add vote on Merge Request for project '%s': %s%n", projectId, e.getMessage());
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to add vote on Merge Request for project '%s'", projectId), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getProjectId(Run<?, ?> build) {
|
||||
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
|
||||
return cause == null ? null : cause.getData().getTargetProjectId().toString();
|
||||
}
|
||||
|
||||
Integer getMergeRequestId(Run<?, ?> build) {
|
||||
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
|
||||
return cause == null ? null : cause.getData().getMergeRequestId();
|
||||
}
|
||||
|
||||
private String getResultIcon(Result result) {
|
||||
if (result == Result.SUCCESS) {
|
||||
return ":+1:";
|
||||
} else {
|
||||
return ":-1:";
|
||||
}
|
||||
}
|
||||
|
||||
private static GitLabApi getClient(Run<?, ?> build) {
|
||||
GitLabConnectionProperty connectionProperty = build.getParent().getProperty(GitLabConnectionProperty.class);
|
||||
if (connectionProperty != null) {
|
||||
return connectionProperty.getClient();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -30,9 +30,6 @@
|
|||
<f:entry title="Set build description to build cause (eg. Merge request or Git Push )" field="setBuildDescription">
|
||||
<f:checkbox default="true"/>
|
||||
</f:entry>
|
||||
<f:entry title="Vote added to note with build status on merge requests" field="addVoteOnMergeRequest">
|
||||
<f:checkbox default="true"/>
|
||||
</f:entry>
|
||||
<f:entry title="Accept merge request on success" field="acceptMergeRequestOnSuccess">
|
||||
<f:checkbox default="false"/>
|
||||
</f:entry>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
GitLabCommitStatusPublisher.DisplayName=Publish build status to GitLab commit (GitLab 8.1+ required)
|
||||
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
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
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.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.MessageFormat;
|
||||
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 GitLabVotePublisherTest {
|
||||
|
||||
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;
|
||||
String projectId = "3";
|
||||
Integer mergeRequestId = 1;
|
||||
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.SUCCESS, buildNumber, projectId);
|
||||
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
|
||||
String defaultNote = MessageFormat.format(":+1:",
|
||||
Result.SUCCESS, build.getParent().getDisplayName(), buildNumber, buildUrl);
|
||||
|
||||
HttpRequest[] requests = new HttpRequest[] {
|
||||
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId.toString(), defaultNote)
|
||||
};
|
||||
|
||||
GitLabVotePublisher publisher = spy(new GitLabVotePublisher());
|
||||
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;
|
||||
String projectId = "3";
|
||||
Integer mergeRequestId = 1;
|
||||
AbstractBuild build = mockBuild("/build/123", GIT_LAB_CONNECTION, Result.FAILURE, buildNumber, projectId);
|
||||
String buildUrl = Jenkins.getInstance().getRootUrl() + build.getUrl();
|
||||
String defaultNote = MessageFormat.format(":-1:",
|
||||
Result.FAILURE, build.getParent().getDisplayName(), buildNumber, buildUrl);
|
||||
|
||||
HttpRequest[] requests = new HttpRequest[] {
|
||||
prepareSendMessageWithSuccessResponse(projectId, mergeRequestId.toString(), defaultNote)
|
||||
};
|
||||
|
||||
GitLabVotePublisher publisher = spy(new GitLabVotePublisher());
|
||||
doReturn(projectId).when(publisher).getProjectId(build);
|
||||
doReturn(mergeRequestId).when(publisher).getMergeRequestId(build);
|
||||
publisher.perform(build, null, listener);
|
||||
|
||||
mockServerClient.verify(requests);
|
||||
}
|
||||
|
||||
private HttpRequest prepareSendMessageWithSuccessResponse(String projectId, String mergeRequestId, String body) throws UnsupportedEncodingException {
|
||||
HttpRequest updateCommitStatus = prepareSendMessageStatus(projectId, mergeRequestId, body);
|
||||
mockServerClient.when(updateCommitStatus).respond(response().withStatusCode(200));
|
||||
return updateCommitStatus;
|
||||
}
|
||||
|
||||
private HttpRequest prepareSendMessageStatus(String projectId, String mergeRequestId, String body) throws UnsupportedEncodingException {
|
||||
return request()
|
||||
.withPath("/gitlab/api/v3/projects/" + URLEncoder.encode(projectId, "UTF-8") + "/merge_requests/" + URLEncoder.encode(mergeRequestId, "UTF-8") + "/notes")
|
||||
.withMethod("POST")
|
||||
.withHeader("PRIVATE-TOKEN", "secret")
|
||||
.withQueryStringParameter("body", body);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue