forked from huwang-dematic/gitlab-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMergeRequestBuildHandler.java
More file actions
165 lines (152 loc) · 6.55 KB
/
Copy pathMergeRequestBuildHandler.java
File metadata and controls
165 lines (152 loc) · 6.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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.Executor;
import hudson.model.Job;
import hudson.model.Queue;
import hudson.model.Result;
import hudson.model.Run;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.CauseOfInterruption;
import jenkins.model.Jenkins;
import org.apache.commons.lang3.StringUtils;
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
public class MergeRequestBuildHandler {
private static final Logger LOGGER = Logger.getLogger(MergeRequestBuildHandler.class.getName());
public void cancelPendingBuilds(Job<?, ?> job, Integer projectId, String branch) {
Queue queue = Objects.requireNonNull(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);
}
}
}
/**
* Aborts in-flight builds of {@code job} whose {@link GitLabWebHookCause} matches the given
* source project id and source branch. Intended to be invoked when a merge request is updated
* and the user has opted in via {@code cancelRunningBuildsOnUpdate}.
*/
public void cancelRunningBuilds(Job<?, ?> job, Integer projectId, String sourceBranch) {
for (Run<?, ?> build : job.getBuilds()) {
if (!build.isBuilding()) {
continue;
}
GitLabWebHookCause cause = build.getCause(GitLabWebHookCause.class);
if (cause == null) {
continue;
}
CauseData causeData = cause.getData();
if (!projectId.equals(causeData.getSourceProjectId())) {
continue;
}
if (!sourceBranch.equals(causeData.getSourceBranch())) {
continue;
}
stopBuild(build, job.getName(), sourceBranch);
setCommitStatusCancelledIfNecessary(causeData, job);
}
}
private void stopBuild(Run<?, ?> build, String jobName, String sourceBranch) {
Executor executor = build.getExecutor();
if (executor == null) {
return;
}
try {
LOGGER.log(
Level.INFO,
"Stopping build {0} of job {1} superseded by merge request update on branch {2}",
LoggerUtil.toArray(build.getDisplayName(), jobName, sourceBranch));
executor.interrupt(Result.ABORTED, new SupersededByMergeRequestUpdate(sourceBranch));
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error stopping running build", e);
}
}
/** Surfaced in the build log so users understand why the build was killed. */
public static final class SupersededByMergeRequestUpdate extends CauseOfInterruption {
private static final long serialVersionUID = 1L;
private final String sourceBranch;
public SupersededByMergeRequestUpdate(String sourceBranch) {
this.sourceBranch = sourceBranch;
}
@Override
public String getShortDescription() {
return "Superseded by merge request update on source branch '" + sourceBranch + "'";
}
}
private GitLabWebHookCause getGitLabWebHookCauseData(Queue.Item item) {
for (Cause cause : item.getCauses()) {
if (cause instanceof GitLabWebHookCause hookCause) {
return hookCause;
}
}
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();
String ref = StringUtils.removeStart(causeData.getSourceBranch(), "refs/tags/");
try {
client.changeBuildStatus(
causeData.getSourceProjectId(),
causeData.getLastCommit(),
BuildState.canceled,
ref,
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 project) {
GitLabCommitStatusPublisher publisher =
(GitLabCommitStatusPublisher) project.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;
}
}