-
Notifications
You must be signed in to change notification settings - Fork 395
/
GitHubHookRegisterProblemMonitor.java
257 lines (229 loc) · 7.8 KB
/
GitHubHookRegisterProblemMonitor.java
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package org.jenkinsci.plugins.github.admin;
import com.cloudbees.jenkins.GitHubRepositoryName;
import com.google.common.collect.ImmutableMap;
import hudson.BulkChange;
import hudson.Extension;
import hudson.XmlFile;
import hudson.model.AdministrativeMonitor;
import hudson.model.ManagementLink;
import hudson.model.Saveable;
import hudson.model.listeners.SaveableListener;
import hudson.util.PersistedList;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.github.Messages;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
/**
* Administrative monitor to track problems of registering/removing hooks for GH.
* Holds non-savable map of repo->message and persisted list of ignored projects.
* Anyone can register new problem with {@link #registerProblem(GitHubRepositoryName, Throwable)} and check
* repo for problems with {@link #isProblemWith(GitHubRepositoryName)}
*
* Has own page with table with problems and ignoring list in global management section. Link to this page
* is visible if any problem or ignored repo is registered
*
* @author lanwen (Merkushev Kirill)
* @since 1.17.0
*/
@Extension
public class GitHubHookRegisterProblemMonitor extends AdministrativeMonitor implements Saveable {
private static final Logger LOGGER = LoggerFactory.getLogger(GitHubHookRegisterProblemMonitor.class);
/**
* Problems map. Cleared on Jenkins restarts
*/
private transient Map<GitHubRepositoryName, String> problems = new ConcurrentHashMap<>();
/**
* Ignored list. Saved to file on any change. Reloaded after restart
*/
private PersistedList<GitHubRepositoryName> ignored;
public GitHubHookRegisterProblemMonitor() {
super(GitHubHookRegisterProblemMonitor.class.getSimpleName());
load();
ignored = ignored == null ? new PersistedList<GitHubRepositoryName>(this) : ignored;
ignored.setOwner(this);
}
/**
* @return Immutable copy of map with repo->problem message content
*/
public Map<GitHubRepositoryName, String> getProblems() {
return ImmutableMap.copyOf(problems);
}
/**
* Registers problems. For message {@link Throwable#getMessage()} will be used
*
* @param repo full named GitHub repo, if null nothing will be done
* @param throwable exception with message about problem, if null nothing will be done
*
* @see #registerProblem(GitHubRepositoryName, String)
*/
public void registerProblem(GitHubRepositoryName repo, Throwable throwable) {
if (throwable == null) {
return;
}
registerProblem(repo, throwable.getMessage());
}
/**
* Used by {@link #registerProblem(GitHubRepositoryName, Throwable)}
*
* @param repo full named GitHub repo, if null nothing will be done
* @param message message to show in the interface. Will be used default if blank
*/
private void registerProblem(GitHubRepositoryName repo, String message) {
if (repo == null) {
return;
}
if (!ignored.contains(repo)) {
problems.put(repo, defaultIfBlank(message, Messages.unknown_error()));
} else {
LOGGER.debug("Repo {} is ignored by monitor, skip this problem...", repo);
}
}
/**
* Removes repo from known problems map
*
* @param repo full named GitHub repo, if null nothing will be done
*/
public void resolveProblem(GitHubRepositoryName repo) {
if (repo == null) {
return;
}
problems.remove(repo);
}
/**
* Checks that repo is registered in this monitor
*
* @param repo full named GitHub repo
*
* @return true if repo is in the map
*/
public boolean isProblemWith(GitHubRepositoryName repo) {
return problems.containsKey(repo);
}
/**
* @return immutable copy of list with ignored repos
*/
public List<GitHubRepositoryName> getIgnored() {
return ignored.toList();
}
@Override
public String getDisplayName() {
return Messages.hooks_problem_administrative_monitor_displayname();
}
@Override
public boolean isActivated() {
return !problems.isEmpty();
}
/**
* Depending on whether the user said "yes" or "no", send him to the right place.
*/
@RequirePOST
@RequireAdminRights
public HttpResponse doAct(StaplerRequest req) throws IOException {
if (req.hasParameter("no")) {
disable(true);
return HttpResponses.redirectViaContextPath("/manage");
} else {
return new HttpRedirect(".");
}
}
/**
* This web method requires POST, admin rights and nonnull repo.
* Responds with redirect to monitor page
*
* @param repo to be ignored. Never null
*/
@RequirePOST
@ValidateRepoName
@RequireAdminRights
@RespondWithRedirect
public void doIgnore(@Nonnull @GHRepoName GitHubRepositoryName repo) {
if (!ignored.contains(repo)) {
ignored.add(repo);
}
resolveProblem(repo);
}
/**
* This web method requires POST, admin rights and nonnull repo.
* Responds with redirect to monitor page
*
* @param repo to be disignored. Never null
*/
@RequirePOST
@ValidateRepoName
@RequireAdminRights
@RespondWithRedirect
public void doDisignore(@Nonnull @GHRepoName GitHubRepositoryName repo) {
ignored.remove(repo);
}
/**
* Save the settings to a file. Called on each change of {@code ignored} list
*/
@Override
public synchronized void save() {
if (BulkChange.contains(this)) {
return;
}
try {
getConfigFile().write(this);
SaveableListener.fireOnChange(this, getConfigFile());
} catch (IOException e) {
LOGGER.error("{}", e);
}
}
private synchronized void load() {
XmlFile file = getConfigFile();
if (!file.exists()) {
return;
}
try {
file.unmarshal(this);
} catch (IOException e) {
LOGGER.warn("Failed to load {}", file, e);
}
}
private XmlFile getConfigFile() {
return new XmlFile(new File(Jenkins.getInstance().getRootDir(), getClass().getName() + ".xml"));
}
/**
* @return instance of administrative monitor to register/resolve/ignore/check hook problems
*/
public static GitHubHookRegisterProblemMonitor get() {
return AdministrativeMonitor.all().get(GitHubHookRegisterProblemMonitor.class);
}
@Extension
public static class GitHubHookRegisterProblemManagementLink extends ManagementLink {
@Inject
private GitHubHookRegisterProblemMonitor monitor;
@Override
public String getIconFileName() {
return monitor.getProblems().isEmpty() && monitor.ignored.isEmpty()
? null
: "/plugin/github/img/logo.svg";
}
@Override
public String getUrlName() {
return monitor.getUrl();
}
@Override
public String getDescription() {
return Messages.hooks_problem_administrative_monitor_description();
}
@Override
public String getDisplayName() {
return Messages.hooks_problem_administrative_monitor_displayname();
}
}
}