diff --git a/pom.xml b/pom.xml index 41addfa..95a0a5e 100644 --- a/pom.xml +++ b/pom.xml @@ -162,6 +162,11 @@ workflow-step-api 1.15 + + org.jenkins-ci.plugins.workflow + workflow-job + 1.15 + org.jenkins-ci.plugins credentials diff --git a/src/main/java/com/dabsquared/gitlabjenkins/GitLabPushTrigger.java b/src/main/java/com/dabsquared/gitlabjenkins/GitLabPushTrigger.java index 4a77d19..f77e2b7 100644 --- a/src/main/java/com/dabsquared/gitlabjenkins/GitLabPushTrigger.java +++ b/src/main/java/com/dabsquared/gitlabjenkins/GitLabPushTrigger.java @@ -100,6 +100,8 @@ public class GitLabPushTrigger extends Trigger> { private String targetBranchRegex; private MergeRequestLabelFilterConfig mergeRequestLabelFilterConfig; private volatile Secret secretToken; + private String pendingBuildName; + private boolean cancelPendingBuildsOnUpdate; private transient BranchFilter branchFilter; private transient PushHookTriggerHandler pushHookTriggerHandler; @@ -120,8 +122,8 @@ public class GitLabPushTrigger extends Trigger> { boolean setBuildDescription, boolean addNoteOnMergeRequest, boolean addCiMessage, boolean addVoteOnMergeRequest, boolean acceptMergeRequestOnSuccess, BranchFilterType branchFilterType, String includeBranchesSpec, String excludeBranchesSpec, String targetBranchRegex, - MergeRequestLabelFilterConfig mergeRequestLabelFilterConfig, String secretToken, boolean triggerOnPipelineEvent, - boolean triggerOnApprovedMergeRequest) { + MergeRequestLabelFilterConfig mergeRequestLabelFilterConfig, String secretToken, boolean triggerOnPipelineEvent, + boolean triggerOnApprovedMergeRequest, String pendingBuildName, boolean cancelPendingBuildsOnUpdate) { this.triggerOnPush = triggerOnPush; this.triggerOnMergeRequest = triggerOnMergeRequest; this.triggerOnAcceptedMergeRequest = triggerOnAcceptedMergeRequest; @@ -144,6 +146,8 @@ public class GitLabPushTrigger extends Trigger> { this.mergeRequestLabelFilterConfig = mergeRequestLabelFilterConfig; this.secretToken = Secret.fromString(secretToken); this.triggerOnApprovedMergeRequest = triggerOnApprovedMergeRequest; + this.pendingBuildName = pendingBuildName; + this.cancelPendingBuildsOnUpdate = cancelPendingBuildsOnUpdate; initializeTriggerHandler(); initializeBranchFilter(); @@ -273,6 +277,14 @@ public class GitLabPushTrigger extends Trigger> { return secretToken == null ? null : secretToken.getPlainText(); } + public String getPendingBuildName() { + return pendingBuildName; + } + + public boolean getCancelPendingBuildsOnUpdate() { + return this.cancelPendingBuildsOnUpdate; + } + @DataBoundSetter public void setTriggerOnPush(boolean triggerOnPush) { this.triggerOnPush = triggerOnPush; @@ -388,6 +400,16 @@ public class GitLabPushTrigger extends Trigger> { this.triggerOnPipelineEvent = triggerOnPipelineEvent; } + @DataBoundSetter + public void setPendingBuildName(String pendingBuildName) { + this.pendingBuildName = pendingBuildName; + } + + @DataBoundSetter + public void setCancelPendingBuildsOnUpdate(boolean cancelPendingBuildsOnUpdate) { + this.cancelPendingBuildsOnUpdate = cancelPendingBuildsOnUpdate; + } + // executes when the Trigger receives a push request public void onPost(final PushHook hook) { if (branchFilter == null) { @@ -438,7 +460,7 @@ public class GitLabPushTrigger extends Trigger> { private void initializeTriggerHandler() { mergeRequestHookTriggerHandler = newMergeRequestHookTriggerHandler(triggerOnMergeRequest, triggerOnAcceptedMergeRequest, triggerOnClosedMergeRequest, triggerOpenMergeRequestOnPush, - skipWorkInProgressMergeRequest, triggerOnApprovedMergeRequest); + skipWorkInProgressMergeRequest, triggerOnApprovedMergeRequest, cancelPendingBuildsOnUpdate); noteHookTriggerHandler = newNoteHookTriggerHandler(triggerOnNoteRequest, noteRegex); pushHookTriggerHandler = newPushHookTriggerHandler(triggerOnPush, triggerOpenMergeRequestOnPush, skipWorkInProgressMergeRequest); pipelineTriggerHandler = newPipelineHookTriggerHandler(triggerOnPipelineEvent); diff --git a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/AbstractWebHookTriggerHandler.java b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/AbstractWebHookTriggerHandler.java index f4aae4c..e0e6136 100644 --- a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/AbstractWebHookTriggerHandler.java +++ b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/AbstractWebHookTriggerHandler.java @@ -6,23 +6,22 @@ import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty; import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient; import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState; import com.dabsquared.gitlabjenkins.gitlab.hook.model.WebHook; -import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher; import com.dabsquared.gitlabjenkins.trigger.exception.NoRevisionToBuildException; import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilter; import com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilter; import com.dabsquared.gitlabjenkins.util.LoggerUtil; -import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.CauseAction; import hudson.model.Job; import hudson.plugins.git.GitSCM; import hudson.plugins.git.RevisionParameterAction; import hudson.scm.SCM; -import jenkins.model.Jenkins; import jenkins.model.ParameterizedJobMixIn; import jenkins.triggers.SCMTriggerItem; import net.karneim.pojobuilder.GeneratePojoBuilder; +import org.apache.commons.lang.StringUtils; import org.eclipse.jgit.transport.URIish; +import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider; import javax.ws.rs.ProcessingException; import javax.ws.rs.WebApplicationException; @@ -37,6 +36,7 @@ import java.util.logging.Logger; public abstract class AbstractWebHookTriggerHandler implements WebHookTriggerHandler { private static final Logger LOGGER = Logger.getLogger(AbstractWebHookTriggerHandler.class.getName()); + protected PendingBuildsHandler pendingBuildsHandler = new PendingBuildsHandler(); @Override public void handle(Job job, H hook, boolean ciSkip, BranchFilter branchFilter, MergeRequestLabelFilter mergeRequestLabelFilter) { @@ -48,6 +48,7 @@ public abstract class AbstractWebHookTriggerHandler implement String targetBranch = getTargetBranch(hook); if (branchFilter.isBranchAllowed(targetBranch)) { LOGGER.log(Level.INFO, "{0} triggered for {1}.", LoggerUtil.toArray(job.getFullName(), getTriggerType())); + cancelPendingBuildsIfNecessary(job, hook); setCommitStatusPendingIfNecessary(job, hook); scheduleBuild(job, createActions(job, hook)); } else { @@ -60,19 +61,17 @@ public abstract class AbstractWebHookTriggerHandler implement protected abstract boolean isCiSkip(H hook); private void setCommitStatusPendingIfNecessary(Job job, H hook) { - if (job instanceof AbstractProject && ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class) != null) { - GitLabCommitStatusPublisher publisher = - (GitLabCommitStatusPublisher) ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class); + String buildName = PendingBuildsHandler.resolvePendingBuildName(job); + if (StringUtils.isNotBlank(buildName)) { GitLabClient client = job.getProperty(GitLabConnectionProperty.class).getClient(); BuildStatusUpdate buildStatusUpdate = retrieveBuildStatusUpdate(hook); try { if (client == null) { LOGGER.log(Level.SEVERE, "No GitLab connection configured"); } else { - String targetUrl = - Jenkins.getInstance().getRootUrl() + job.getUrl() + job.getNextBuildNumber() + "/"; + String targetUrl = DisplayURLProvider.get().getJobURL(job); client.changeBuildStatus(buildStatusUpdate.getProjectId(), buildStatusUpdate.getSha(), - BuildState.pending, buildStatusUpdate.getRef(), publisher.getName(), targetUrl, BuildState.pending.name()); + BuildState.pending, buildStatusUpdate.getRef(), buildName, targetUrl, BuildState.pending.name()); } } catch (WebApplicationException | ProcessingException e) { LOGGER.log(Level.SEVERE, "Failed to set build state to pending", e); @@ -94,6 +93,8 @@ public abstract class AbstractWebHookTriggerHandler implement return actions.toArray(new Action[actions.size()]); } + protected void cancelPendingBuildsIfNecessary(Job job, H hook) {} + protected abstract CauseData retrieveCauseData(H hook); protected abstract String getTargetBranch(H hook); diff --git a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/PendingBuildsHandler.java b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/PendingBuildsHandler.java new file mode 100644 index 0000000..368d5b2 --- /dev/null +++ b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/PendingBuildsHandler.java @@ -0,0 +1,96 @@ +package com.dabsquared.gitlabjenkins.trigger.handler; + +import com.dabsquared.gitlabjenkins.GitLabPushTrigger; +import com.dabsquared.gitlabjenkins.cause.CauseData; +import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause; +import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty; +import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient; +import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState; +import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher; +import com.dabsquared.gitlabjenkins.util.LoggerUtil; +import hudson.model.AbstractProject; +import hudson.model.Cause; +import hudson.model.Job; +import hudson.model.Queue; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; +import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PendingBuildsHandler { + + private static final Logger LOGGER = Logger.getLogger(PendingBuildsHandler.class.getName()); + + public void cancelPendingBuilds(Job job, Integer projectId, String branch) { + Queue queue = Jenkins.getInstance().getQueue(); + for (Queue.Item item : queue.getItems()) { + if (!job.getName().equals(item.task.getName())) { + continue; + } + GitLabWebHookCause queueItemGitLabWebHookCause = getGitLabWebHookCauseData(item); + if (queueItemGitLabWebHookCause == null) { + continue; + } + CauseData queueItemCauseData = queueItemGitLabWebHookCause.getData(); + if (!projectId.equals(queueItemCauseData.getSourceProjectId())) { + continue; + } + if (branch.equals(queueItemCauseData.getBranch())) { + cancel(item, queue, branch); + setCommitStatusCancelledIfNecessary(queueItemCauseData, job); + } + } + } + + private GitLabWebHookCause getGitLabWebHookCauseData(Queue.Item item) { + for (Cause cause : item.getCauses()) { + if (cause instanceof GitLabWebHookCause) { + return (GitLabWebHookCause) cause; + } + } + return null; + } + + private void cancel(Queue.Item item, Queue queue, String branch) { + try { + LOGGER.log(Level.INFO, "Cancelling job {0} for branch {1}", LoggerUtil.toArray(item.task.getName(), branch)); + queue.cancel(item); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error cancelling queued build", e); + } + } + + private void setCommitStatusCancelledIfNecessary(CauseData causeData, Job job) { + String buildName = resolvePendingBuildName(job); + if (StringUtils.isBlank(buildName)) { + return; + } + String targetUrl = DisplayURLProvider.get().getJobURL(job); + GitLabClient client = job.getProperty(GitLabConnectionProperty.class).getClient(); + try { + client.changeBuildStatus(causeData.getSourceProjectId(), causeData.getLastCommit(), BuildState.canceled, + causeData.getSourceBranch(), buildName, targetUrl, BuildState.canceled.name()); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Failed to set build state to pending", e); + } + } + + public static String resolvePendingBuildName(Job job) { + if (job instanceof AbstractProject) { + GitLabCommitStatusPublisher publisher = + (GitLabCommitStatusPublisher) ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class); + if (publisher != null) { + return publisher.getName(); + } + } else if (job instanceof WorkflowJob) { + GitLabPushTrigger trigger = GitLabPushTrigger.getFromJob(job); + if (trigger != null) { + return trigger.getPendingBuildName(); + } + } + return null; + } +} diff --git a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerFactory.java b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerFactory.java index 8370640..2c4f28e 100644 --- a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerFactory.java +++ b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerFactory.java @@ -21,11 +21,12 @@ public final class MergeRequestHookTriggerHandlerFactory { boolean triggerOnClosedMergeRequest, TriggerOpenMergeRequest triggerOpenMergeRequest, boolean skipWorkInProgressMergeRequest, - boolean triggerOnApprovedMergeRequest) { + boolean triggerOnApprovedMergeRequest, + boolean cancelPendingBuildsOnUpdate) { if (triggerOnMergeRequest || triggerOnAcceptedMergeRequest || triggerOnClosedMergeRequest || triggerOpenMergeRequest != TriggerOpenMergeRequest.never || triggerOnApprovedMergeRequest) { return new MergeRequestHookTriggerHandlerImpl(retrieveAllowedStates(triggerOnMergeRequest, triggerOnAcceptedMergeRequest, triggerOnClosedMergeRequest, triggerOpenMergeRequest), retrieveAllowedActions(triggerOnApprovedMergeRequest), - skipWorkInProgressMergeRequest); + skipWorkInProgressMergeRequest, cancelPendingBuildsOnUpdate); } else { return new NopMergeRequestHookTriggerHandler(); } diff --git a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImpl.java b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImpl.java index 1c8865c..89e4478 100644 --- a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImpl.java +++ b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImpl.java @@ -12,6 +12,7 @@ import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilter; import com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilter; import com.dabsquared.gitlabjenkins.trigger.handler.AbstractWebHookTriggerHandler; import com.dabsquared.gitlabjenkins.util.BuildUtil; +import com.dabsquared.gitlabjenkins.trigger.handler.PendingBuildsHandler; import hudson.model.Job; import hudson.model.Run; import hudson.plugins.git.GitSCM; @@ -39,15 +40,17 @@ class MergeRequestHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler allowedStates; private final boolean skipWorkInProgressMergeRequest; private final Collection allowedActions; + private final boolean cancelPendingBuildsOnUpdate; - MergeRequestHookTriggerHandlerImpl(Collection allowedStates, boolean skipWorkInProgressMergeRequest) { - this(allowedStates, EnumSet.allOf(Action.class),skipWorkInProgressMergeRequest); + MergeRequestHookTriggerHandlerImpl(Collection allowedStates, boolean skipWorkInProgressMergeRequest, boolean cancelPendingBuildsOnUpdate) { + this(allowedStates, EnumSet.allOf(Action.class), skipWorkInProgressMergeRequest, cancelPendingBuildsOnUpdate); } - MergeRequestHookTriggerHandlerImpl(Collection allowedStates, Collection allowedActions, boolean skipWorkInProgressMergeRequest) { + MergeRequestHookTriggerHandlerImpl(Collection allowedStates, Collection allowedActions, boolean skipWorkInProgressMergeRequest, boolean cancelPendingBuildsOnUpdate) { this.allowedStates = allowedStates; this.allowedActions = allowedActions; this.skipWorkInProgressMergeRequest = skipWorkInProgressMergeRequest; + this.cancelPendingBuildsOnUpdate = cancelPendingBuildsOnUpdate; } @Override @@ -77,6 +80,17 @@ class MergeRequestHookTriggerHandlerImpl extends AbstractWebHookTriggerHandler job, MergeRequestHook hook) { + if (!this.cancelPendingBuildsOnUpdate) { + return; + } + if (!hook.getObjectAttributes().getAction().equals(Action.update)) { + return; + } + this.pendingBuildsHandler.cancelPendingBuilds(job, hook.getObjectAttributes().getSourceProjectId(), hook.getObjectAttributes().getSourceBranch()); + } + @Override protected String getTargetBranch(MergeRequestHook hook) { return hook.getObjectAttributes() == null ? null : hook.getObjectAttributes().getTargetBranch(); diff --git a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/push/OpenMergeRequestPushHookTriggerHandler.java b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/push/OpenMergeRequestPushHookTriggerHandler.java index 75cf572..e8c778e 100644 --- a/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/push/OpenMergeRequestPushHookTriggerHandler.java +++ b/src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/push/OpenMergeRequestPushHookTriggerHandler.java @@ -12,20 +12,20 @@ import com.dabsquared.gitlabjenkins.gitlab.api.model.MergeRequest; import com.dabsquared.gitlabjenkins.gitlab.api.model.Project; import com.dabsquared.gitlabjenkins.gitlab.hook.model.PushHook; import com.dabsquared.gitlabjenkins.gitlab.hook.model.State; -import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher; import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilter; import com.dabsquared.gitlabjenkins.trigger.filter.MergeRequestLabelFilter; import com.dabsquared.gitlabjenkins.util.LoggerUtil; -import hudson.model.AbstractProject; +import com.dabsquared.gitlabjenkins.trigger.handler.PendingBuildsHandler; import hudson.model.Action; import hudson.model.CauseAction; import hudson.model.Job; import hudson.plugins.git.RevisionParameterAction; import hudson.triggers.Trigger; -import jenkins.model.Jenkins; import jenkins.model.ParameterizedJobMixIn; import jenkins.model.ParameterizedJobMixIn.ParameterizedJob; +import org.apache.commons.lang.StringUtils; import org.eclipse.jgit.transport.URIish; +import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider; import javax.ws.rs.ProcessingException; import javax.ws.rs.WebApplicationException; @@ -72,7 +72,7 @@ class OpenMergeRequestPushHookTriggerHandler implements PushHookTriggerHandler { } } } - + } else { LOGGER.log(Level.FINE, "Not a ParameterizedJob: {0}",LoggerUtil.toArray(job.getClass().getName())); } @@ -114,7 +114,6 @@ class OpenMergeRequestPushHookTriggerHandler implements PushHookTriggerHandler { Project project = client.getProject(mergeRequest.getSourceProjectId().toString()); String commit = branch.getCommit().getId(); setCommitStatusPendingIfNecessary(job, mergeRequest.getSourceProjectId(), commit, branch.getName()); - List actions = Arrays.asList(new CauseAction(new GitLabWebHookCause(retrieveCauseData(hook, project, mergeRequest, branch))), new RevisionParameterAction(commit, retrieveUrIish(hook))); scheduleBuild(job, actions.toArray(new Action[actions.size()])); @@ -153,13 +152,12 @@ class OpenMergeRequestPushHookTriggerHandler implements PushHookTriggerHandler { } private void setCommitStatusPendingIfNecessary(Job job, Integer projectId, String commit, String ref) { - if (job instanceof AbstractProject && ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class) != null) { - GitLabCommitStatusPublisher publisher = - (GitLabCommitStatusPublisher) ((AbstractProject) job).getPublishersList().get(GitLabCommitStatusPublisher.class); + String buildName = PendingBuildsHandler.resolvePendingBuildName(job); + if (StringUtils.isNotBlank(buildName)) { GitLabClient client = job.getProperty(GitLabConnectionProperty.class).getClient(); try { - String targetUrl = Jenkins.getInstance().getRootUrl() + job.getUrl() + job.getNextBuildNumber() + "/"; - client.changeBuildStatus(projectId, commit, BuildState.pending, ref, publisher.getName(), targetUrl, BuildState.pending.name()); + String targetUrl = DisplayURLProvider.get().getJobURL(job); + client.changeBuildStatus(projectId, commit, BuildState.pending, ref, buildName, targetUrl, BuildState.pending.name()); } catch (WebApplicationException | ProcessingException e) { LOGGER.log(Level.SEVERE, "Failed to set build state to pending", e); } diff --git a/src/main/resources/com/dabsquared/gitlabjenkins/GitLabPushTrigger/config.jelly b/src/main/resources/com/dabsquared/gitlabjenkins/GitLabPushTrigger/config.jelly index 2c0aae2..09d139b 100644 --- a/src/main/resources/com/dabsquared/gitlabjenkins/GitLabPushTrigger/config.jelly +++ b/src/main/resources/com/dabsquared/gitlabjenkins/GitLabPushTrigger/config.jelly @@ -42,6 +42,12 @@ + + + + + + diff --git a/src/main/webapp/help/help-pendingBuildName.html b/src/main/webapp/help/help-pendingBuildName.html new file mode 100644 index 0000000..ee6a87a --- /dev/null +++ b/src/main/webapp/help/help-pendingBuildName.html @@ -0,0 +1,8 @@ +
+
+

Applicable only for pipelines.

+

When filled, a 'pending' build status with the given build name is published to Gitlab when the pipeline is + triggered. Further status updates should be defined in pipeline steps.

+

For other types of jobs the build name is taken from 'Publish build status to Gitlab commit' post-build action.

+
+
diff --git a/src/test/java/com/dabsquared/gitlabjenkins/trigger/handler/PendingBuildsHandlerTest.java b/src/test/java/com/dabsquared/gitlabjenkins/trigger/handler/PendingBuildsHandlerTest.java new file mode 100644 index 0000000..b583fec --- /dev/null +++ b/src/test/java/com/dabsquared/gitlabjenkins/trigger/handler/PendingBuildsHandlerTest.java @@ -0,0 +1,219 @@ +package com.dabsquared.gitlabjenkins.trigger.handler; + +import com.dabsquared.gitlabjenkins.GitLabPushTrigger; +import com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty; +import com.dabsquared.gitlabjenkins.gitlab.api.GitLabClient; +import com.dabsquared.gitlabjenkins.gitlab.api.model.BuildState; +import com.dabsquared.gitlabjenkins.gitlab.hook.model.*; +import com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.*; +import com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher; +import com.dabsquared.gitlabjenkins.trigger.filter.BranchFilterType; +import hudson.model.FreeStyleProject; +import hudson.model.ItemGroup; +import hudson.model.Project; +import hudson.model.Queue; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.jvnet.hudson.test.JenkinsRule; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +import static com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.CommitBuilder.commit; +import static com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.MergeRequestObjectAttributesBuilder.mergeRequestObjectAttributes; +import static com.dabsquared.gitlabjenkins.gitlab.hook.model.builder.generated.UserBuilder.user; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class PendingBuildsHandlerTest { + + private static final String GITLAB_BUILD_NAME = "Jenkins"; + + @ClassRule + public static JenkinsRule jenkins = new JenkinsRule(); + + @Mock + private GitLabClient gitLabClient; + + @Mock + private GitLabConnectionProperty gitLabConnectionProperty; + + @Before + public void init() { + when(gitLabConnectionProperty.getClient()).thenReturn(gitLabClient); + } + + @After + public void clearQueue() { + Queue queue = jenkins.getInstance().getQueue(); + for (Queue.Item item : queue.getItems()) { + queue.cancel(item); + } + } + + @Test + public void projectCanBeConfiguredToSendPendingBuildStatusWhenTriggered() throws IOException { + Project project = freestyleProject("freestyleProject1", new GitLabCommitStatusPublisher(GITLAB_BUILD_NAME, false)); + + GitLabPushTrigger gitLabPushTrigger = gitLabPushTrigger(project); + + gitLabPushTrigger.onPost(pushHook(1, "branch1", "commit1")); + + verify(gitLabClient).changeBuildStatus(eq(1), eq("commit1"), eq(BuildState.pending), eq("branch1"), eq(GITLAB_BUILD_NAME), + contains("/freestyleProject1/"), eq(BuildState.pending.name())); + verifyNoMoreInteractions(gitLabClient); + } + + @Test + public void workflowJobCanConfiguredToSendToPendingBuildStatusWhenTriggered() throws IOException { + WorkflowJob workflowJob = workflowJob(); + + GitLabPushTrigger gitLabPushTrigger = gitLabPushTrigger(workflowJob); + gitLabPushTrigger.setPendingBuildName(GITLAB_BUILD_NAME); + + gitLabPushTrigger.onPost(mergeRequestHook(1, "branch1", "commit1")); + + verify(gitLabClient).changeBuildStatus(eq(1), eq("commit1"), eq(BuildState.pending), eq("branch1"), eq(GITLAB_BUILD_NAME), + contains("/workflowJob/"), eq(BuildState.pending.name())); + verifyNoMoreInteractions(gitLabClient); + } + + @Test + public void queuedMergeRequestBuildsCanBeCancelledOnMergeRequestUpdate() throws IOException { + Project project = freestyleProject("project1", new GitLabCommitStatusPublisher(GITLAB_BUILD_NAME, false)); + + GitLabPushTrigger gitLabPushTrigger = gitLabPushTrigger(project); + gitLabPushTrigger.setCancelPendingBuildsOnUpdate(true); + + assertThat(jenkins.getInstance().getQueue().getItems().length, is(0)); + + gitLabPushTrigger.onPost(mergeRequestHook(1, "sourceBranch", "commit1")); // Will be cancelled + gitLabPushTrigger.onPost(mergeRequestHook(1, "sourceBranch", "commit2")); // Will be cancelled + gitLabPushTrigger.onPost(mergeRequestHook(1, "sourceBranch", "commit3")); + gitLabPushTrigger.onPost(mergeRequestHook(1, "anotherBranch", "commit4")); + gitLabPushTrigger.onPost(mergeRequestHook(2, "sourceBranch", "commit5")); + + verify(gitLabClient).changeBuildStatus(eq(1), eq("commit1"), eq(BuildState.canceled), eq("sourceBranch"), + eq("Jenkins"), contains("project1"), eq(BuildState.canceled.name())); + verify(gitLabClient).changeBuildStatus(eq(1), eq("commit2"), eq(BuildState.canceled), eq("sourceBranch"), + eq("Jenkins"), contains("project1"), eq(BuildState.canceled.name())); + + assertThat(jenkins.getInstance().getQueue().getItems().length, is(3)); + } + + private GitLabPushTrigger gitLabPushTrigger(Project project) throws IOException { + GitLabPushTrigger gitLabPushTrigger = gitLabPushTrigger(); + project.addTrigger(gitLabPushTrigger); + gitLabPushTrigger.start(project,true); + return gitLabPushTrigger; + } + + private GitLabPushTrigger gitLabPushTrigger(WorkflowJob workflowJob) { + GitLabPushTrigger gitLabPushTrigger = gitLabPushTrigger(); + workflowJob.addTrigger(gitLabPushTrigger); + gitLabPushTrigger.start(workflowJob,true); + return gitLabPushTrigger; + } + + private GitLabPushTrigger gitLabPushTrigger() { + GitLabPushTrigger gitLabPushTrigger = new GitLabPushTrigger(); + gitLabPushTrigger.setTriggerOnPush(true); + gitLabPushTrigger.setTriggerOnMergeRequest(true); + gitLabPushTrigger.setPendingBuildName(GITLAB_BUILD_NAME); + gitLabPushTrigger.setBranchFilterType(BranchFilterType.NameBasedFilter); + gitLabPushTrigger.setBranchFilterName(""); + return gitLabPushTrigger; + } + + private MergeRequestHook mergeRequestHook(int projectId, String branch, String commitId) { + return MergeRequestHookBuilder.mergeRequestHook() + .withObjectAttributes(mergeRequestObjectAttributes() + .withAction(Action.update) + .withState(State.updated) + .withIid(1) + .withTitle("test") + .withTargetProjectId(1) + .withTargetBranch("targetBranch") + .withSourceBranch(branch) + .withSourceProjectId(projectId) + .withLastCommit(commit().withAuthor(user().withName("author").build()).withId(commitId).build()) + .withSource(ProjectBuilder.project() + .withName("test") + .withNamespace("test-namespace") + .withHomepage("https://gitlab.org/test") + .withUrl("git@gitlab.org:test.git") + .withSshUrl("git@gitlab.org:test.git") + .withHttpUrl("https://gitlab.org/test.git") + .build()) + .withTarget(ProjectBuilder.project() + .withName("test") + .withNamespace("test-namespace") + .withHomepage("https://gitlab.org/test") + .withUrl("git@gitlab.org:test.git") + .withSshUrl("git@gitlab.org:test.git") + .withHttpUrl("https://gitlab.org/test.git") + .build()) + .build()) + .withProject(ProjectBuilder.project() + .withWebUrl("https://gitlab.org/test.git") + .build() + ) + .build(); + } + + private PushHook pushHook(int projectId, String branch, String commitId) { + User user = new UserBuilder() + .withName("username") + .build(); + + Repository repository = new RepositoryBuilder() + .withName("repository") + .withGitSshUrl("sshUrl") + .withGitHttpUrl("httpUrl") + .build(); + + return new PushHookBuilder() + .withProjectId(projectId) + .withRef(branch) + .withAfter(commitId) + .withRepository(new Repository()) + .withProject(ProjectBuilder.project().withNamespace("namespace").build()) + .withCommits(Arrays.asList(CommitBuilder.commit().withId(commitId).withAuthor(user).build())) + .withRepository(repository) + .withObjectKind("push") + .withUserName("username") + .build(); + } + + private Project freestyleProject(String name, GitLabCommitStatusPublisher gitLabCommitStatusPublisher) throws IOException { + FreeStyleProject project = jenkins.createFreeStyleProject(name); + project.setQuietPeriod(5000); + project.getPublishersList().add(gitLabCommitStatusPublisher); + project.addProperty(gitLabConnectionProperty); + return project; + } + + private WorkflowJob workflowJob() throws IOException { + ItemGroup itemGroup = mock(ItemGroup.class); + when(itemGroup.getFullName()).thenReturn("parent"); + when(itemGroup.getUrlChildPrefix()).thenReturn("prefix"); + + WorkflowJob workflowJob = new WorkflowJob(itemGroup, "workflowJob"); + when(itemGroup.getRootDirFor(workflowJob)).thenReturn(new File("work")); + + workflowJob.addProperty(gitLabConnectionProperty); + workflowJob.setQuietPeriod(5000); + workflowJob.onCreatedFromScratch(); + return workflowJob; + } +} diff --git a/src/test/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImplTest.java b/src/test/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImplTest.java index db19c73..b41dd2a 100644 --- a/src/test/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImplTest.java +++ b/src/test/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImplTest.java @@ -69,7 +69,7 @@ public class MergeRequestHookTriggerHandlerImplTest { } }); project.setQuietPeriod(0); - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.reopened), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.reopened), false, false); mergeRequestHookTriggerHandler.handle(project, mergeRequestHook() .withObjectAttributes(mergeRequestObjectAttributes().withDescription("[ci-skip]").build()) .build(), true, BranchFilterFactory.newBranchFilter(branchFilterConfig().build(BranchFilterType.All)), @@ -81,7 +81,7 @@ public class MergeRequestHookTriggerHandlerImplTest { @Test public void mergeRequest_build() throws IOException, InterruptedException, GitAPIException, ExecutionException { - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.reopened), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.reopened), false, false); OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.opened); assertThat(buildTriggered.isSignaled(), is(true)); @@ -89,7 +89,7 @@ public class MergeRequestHookTriggerHandlerImplTest { @Test public void mergeRequest_build_when_accepted() throws IOException, InterruptedException, GitAPIException, ExecutionException { - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.merged), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.merged), false, false); OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.merged); assertThat(buildTriggered.isSignaled(), is(true)); @@ -97,7 +97,7 @@ public class MergeRequestHookTriggerHandlerImplTest { @Test public void mergeRequest_build_when_closed() throws IOException, InterruptedException, GitAPIException, ExecutionException { - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.closed), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.closed), false, false); OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.closed); assertThat(buildTriggered.isSignaled(), is(true)); @@ -105,7 +105,7 @@ public class MergeRequestHookTriggerHandlerImplTest { @Test public void mergeRequest_do_not_build_when_accepted() throws IOException, InterruptedException, GitAPIException, ExecutionException { - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.updated), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.updated), false, false); OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.merged); assertThat(buildTriggered.isSignaled(), is(false)); @@ -113,7 +113,7 @@ public class MergeRequestHookTriggerHandlerImplTest { @Test public void mergeRequest_do_not_build_when_closed() throws IOException, InterruptedException, GitAPIException, ExecutionException { - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.updated), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(Arrays.asList(State.opened, State.updated), false, false); OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, State.closed); assertThat(buildTriggered.isSignaled(), is(false)); @@ -121,7 +121,8 @@ public class MergeRequestHookTriggerHandlerImplTest { @Test public void mergeRequest_build_when_approved() throws IOException, InterruptedException, GitAPIException, ExecutionException { - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(EnumSet.allOf(State.class), EnumSet.of(Action.approved), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(EnumSet.allOf(State.class), EnumSet.of(Action.approved), false, false); + OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, Action.approved); assertThat(buildTriggered.isSignaled(), is(true)); @@ -129,7 +130,7 @@ public class MergeRequestHookTriggerHandlerImplTest { @Test public void mergeRequest_do_not_build_when_when_approved() throws Exception { - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(EnumSet.allOf(State.class), EnumSet.of(Action.update), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(EnumSet.allOf(State.class), EnumSet.of(Action.update), false, false); OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, defaultMergeRequestObjectAttributes().withState(State.opened).withAction(Action.approved)); assertThat(buildTriggered.isSignaled(), is (false)); @@ -148,11 +149,11 @@ public class MergeRequestHookTriggerHandlerImplTest { private void mergeRequest_build_only_when_approved(Action action) throws GitAPIException, IOException, InterruptedException { - MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(EnumSet.allOf(State.class), EnumSet.of(Action.approved), false); + MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler = new MergeRequestHookTriggerHandlerImpl(EnumSet.allOf(State.class), EnumSet.of(Action.approved), false, false); OneShotEvent buildTriggered = doHandle(mergeRequestHookTriggerHandler, action); assertThat(buildTriggered.isSignaled(), is(false)); - } + } private OneShotEvent doHandle(MergeRequestHookTriggerHandler mergeRequestHookTriggerHandler, Action action) throws GitAPIException, IOException, InterruptedException { return doHandle(mergeRequestHookTriggerHandler, defaultMergeRequestObjectAttributes().withAction(action));