Skip to content

Commit 230896f

Browse files
fernandosurejoshiste
authored andcommitted
Add notifier for opsgenie
1 parent 69f8568 commit 230896f

File tree

5 files changed

+418
-15
lines changed

5 files changed

+418
-15
lines changed

spring-boot-admin-docs/src/main/asciidoc/server-notifications.adoc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,60 @@ To enable pagerduty notifications you just have to add a generic service to your
165165
|
166166
|===
167167

168+
169+
[[opsgenie-notifications]]
170+
==== OpsGenie notifications ====
171+
To enable OpsGenie notifications you just have to add a new JSON Rest API integration to your OpsGenie account and set `spring.boot.admin.notify.opsgenie.api-key` to the apiKey you received.
172+
173+
.OpsGenie notifications configuration options
174+
|===
175+
| Property name |Description |Default value
176+
177+
| spring.boot.admin.notify.opsgenie.enabled
178+
| Enable OpsGenie notifications
179+
| `true`
180+
181+
| spring.boot.admin.notify.opsgenie.ignore-changes
182+
| Comma-delimited list of status changes to be ignored. Format: "<from-status>:<to-status>". Wildcards allowed.
183+
| `"UNKNOWN:UP"`
184+
185+
| spring.boot.admin.notify.opsgenie.api-key
186+
| apiKey you received when creating the integration
187+
|
188+
189+
| spring.boot.admin.notify.opsgenie.url
190+
| OpsGenie Alert API url
191+
| `+++"https://api.opsgenie.com/v1/json/alert"+++`
192+
193+
| spring.boot.admin.notify.opsgenie.description
194+
| Description to use in the event. SpEL-expressions are supported
195+
| `+++"#{application.name}/#{application.id} is #{to.status}"+++`
196+
197+
| spring.boot.admin.notify.opsgenie.recipients
198+
| User, group, schedule or escalation names to calculate which users will receive the notifications of the alert.
199+
|
200+
201+
| spring.boot.admin.notify.opsgenie.actions
202+
| Comma separated list of actions that can be executed.
203+
|
204+
205+
| spring.boot.admin.notify.opsgenie.source
206+
| Field to specify source of alert. By default, it will be assigned to IP address of incoming request.
207+
|
208+
209+
| spring.boot.admin.notify.opsgenie.tags
210+
| Comma separated list of labels attached to the alert.
211+
|
212+
213+
| spring.boot.admin.notify.opsgenie.entity
214+
| The entity the alert is related to.
215+
|
216+
217+
| spring.boot.admin.notify.opsgenie.user
218+
| Default owner of the execution. If user is not specified, the system becomes owner of the execution.
219+
|
220+
|===
221+
168222
[hipchat-notifications]
169223
==== Hipchat notifications ====
170224
To enable Hipchat notifications you need to create an API token from you Hipchat account and set the appropriate configuration properties.

spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/config/NotifierConfiguration.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717

1818
import java.util.List;
1919

20+
import de.codecentric.boot.admin.notify.CompositeNotifier;
21+
import de.codecentric.boot.admin.notify.MailNotifier;
22+
import de.codecentric.boot.admin.notify.Notifier;
23+
import de.codecentric.boot.admin.notify.NotifierListener;
24+
import de.codecentric.boot.admin.notify.PagerdutyNotifier;
25+
import de.codecentric.boot.admin.notify.OpsGenieNotifier;
26+
import de.codecentric.boot.admin.notify.HipchatNotifier;
27+
import de.codecentric.boot.admin.notify.SlackNotifier;
28+
import de.codecentric.boot.admin.notify.LetsChatNotifier;
2029
import org.springframework.beans.factory.annotation.Autowired;
2130
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2231
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
@@ -33,14 +42,6 @@
3342
import org.springframework.context.annotation.Primary;
3443
import org.springframework.mail.MailSender;
3544

36-
import de.codecentric.boot.admin.notify.CompositeNotifier;
37-
import de.codecentric.boot.admin.notify.HipchatNotifier;
38-
import de.codecentric.boot.admin.notify.MailNotifier;
39-
import de.codecentric.boot.admin.notify.Notifier;
40-
import de.codecentric.boot.admin.notify.NotifierListener;
41-
import de.codecentric.boot.admin.notify.PagerdutyNotifier;
42-
import de.codecentric.boot.admin.notify.SlackNotifier;
43-
import de.codecentric.boot.admin.notify.LetsChatNotifier;
4445
import de.codecentric.boot.admin.notify.filter.FilteringNotifier;
4546
import de.codecentric.boot.admin.notify.filter.web.NotificationFilterController;
4647
import de.codecentric.boot.admin.web.PrefixHandlerMapping;
@@ -136,6 +137,22 @@ public PagerdutyNotifier pagerdutyNotifier() {
136137
}
137138
}
138139

140+
141+
@Configuration
142+
@ConditionalOnProperty(prefix = "spring.boot.admin.notify.opsgenie", name = "api-key")
143+
@AutoConfigureBefore({ NotifierListenerConfiguration.class,
144+
CompositeNotifierConfiguration.class })
145+
public static class OpsGenieNotifierConfiguration {
146+
@Bean
147+
@ConditionalOnMissingBean
148+
@ConfigurationProperties("spring.boot.admin.notify.opsgenie")
149+
public OpsGenieNotifier opsgenieNotifier() {
150+
return new OpsGenieNotifier();
151+
}
152+
}
153+
154+
155+
139156
@Configuration
140157
@ConditionalOnProperty(prefix = "spring.boot.admin.notify.hipchat", name = "url")
141158
@AutoConfigureBefore({ NotifierListenerConfiguration.class,
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Copyright 2014-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package de.codecentric.boot.admin.notify;
18+
19+
import de.codecentric.boot.admin.event.ClientApplicationEvent;
20+
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
21+
22+
import java.net.URI;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
import org.springframework.expression.Expression;
26+
import org.springframework.expression.ParserContext;
27+
import org.springframework.expression.spel.standard.SpelExpressionParser;
28+
import org.springframework.http.HttpEntity;
29+
import org.springframework.http.HttpHeaders;
30+
import org.springframework.http.HttpMethod;
31+
import org.springframework.http.MediaType;
32+
import org.springframework.web.client.RestTemplate;
33+
34+
/**
35+
* Notifier submitting events to opsgenie.com.
36+
*
37+
* @author Fernando Sure
38+
*/
39+
public class OpsGenieNotifier extends AbstractStatusChangeNotifier {
40+
private static final URI DEFAULT_URI = URI.create("https://api.opsgenie.com/v1/json/alert");
41+
private static final String DEFAULT_MESSAGE = "#{application.name}/#{application.id} is #{to.status}";
42+
private final SpelExpressionParser parser = new SpelExpressionParser();
43+
private RestTemplate restTemplate = new RestTemplate();
44+
45+
/**
46+
* BASE URL for OpsGenie API
47+
*/
48+
private URI url = DEFAULT_URI;
49+
50+
/**
51+
* Integration ApiKey
52+
*/
53+
private String apiKey;
54+
55+
/**
56+
* Comma separated list of users, groups, schedules or escalation names
57+
* to calculate which users will receive the notifications of the alert.
58+
*/
59+
private String recipients;
60+
61+
/**
62+
* Comma separated list of actions that can be executed.
63+
*/
64+
private String actions;
65+
66+
/**
67+
* Field to specify source of alert. By default, it will be assigned to IP address of incoming request
68+
*/
69+
private String source;
70+
71+
/**
72+
* Comma separated list of labels attached to the alert
73+
*/
74+
private String tags;
75+
76+
/**
77+
* The entity the alert is related to.
78+
*/
79+
private String entity;
80+
81+
/**
82+
* Default owner of the execution. If user is not specified, the system becomes owner of the execution.
83+
*/
84+
private String user;
85+
86+
/**
87+
* Trigger description. SpEL template using event as root;
88+
*/
89+
private Expression description;
90+
91+
public OpsGenieNotifier() {
92+
this.description = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);
93+
}
94+
95+
@Override
96+
protected void doNotify(ClientApplicationEvent event) throws Exception {
97+
restTemplate.exchange(buildUrl(event), HttpMethod.POST, createRequest(event), Void.class);
98+
}
99+
100+
protected String buildUrl(ClientApplicationEvent event) {
101+
if ((event instanceof ClientApplicationStatusChangedEvent) &&
102+
("UP".equals(((ClientApplicationStatusChangedEvent) event).getTo().getStatus()))) {
103+
return String.format("%s/close", url.toString());
104+
}
105+
return url.toString();
106+
}
107+
108+
protected HttpEntity createRequest(ClientApplicationEvent event) {
109+
Map<String, Object> body = new HashMap<>();
110+
body.put("apiKey", apiKey);
111+
body.put("message", getMessage(event));
112+
body.put("alias", event.getApplication().getName() + "/" + event.getApplication().getId());
113+
body.put("description", getDescription(event));
114+
115+
if (event instanceof ClientApplicationStatusChangedEvent &&
116+
!"UP".equals(((ClientApplicationStatusChangedEvent) event).getTo().getStatus())) {
117+
118+
if (recipients != null) {
119+
body.put("recipients", recipients);
120+
}
121+
if (actions != null) {
122+
body.put("actions", actions);
123+
}
124+
if (source != null) {
125+
body.put("source", source);
126+
}
127+
if (tags != null) {
128+
body.put("tags", tags);
129+
}
130+
if (entity != null) {
131+
body.put("entity", entity);
132+
}
133+
if (user != null) {
134+
body.put("user", user);
135+
}
136+
137+
Map<String, Object> details = new HashMap<>();
138+
details.put("type", "link");
139+
details.put("href", event.getApplication().getHealthUrl());
140+
details.put("text", "Application health-endpoint");
141+
body.put("details", details);
142+
}
143+
144+
HttpHeaders headers = new HttpHeaders();
145+
headers.setContentType(MediaType.APPLICATION_JSON);
146+
return new HttpEntity<>(body, headers);
147+
}
148+
149+
protected String getMessage(ClientApplicationEvent event) {
150+
return description.getValue(event, String.class);
151+
}
152+
153+
protected String getDescription(ClientApplicationEvent event) {
154+
return String.format("Application %s (%s) went from %s to %s", event.getApplication().getName(),
155+
event.getApplication().getId(), ((ClientApplicationStatusChangedEvent) event).getFrom().getStatus(),
156+
((ClientApplicationStatusChangedEvent) event).getTo().getStatus());
157+
}
158+
159+
public void setApiKey(String apiKey) {
160+
this.apiKey = apiKey;
161+
}
162+
163+
public String getApiKey() {
164+
return apiKey;
165+
}
166+
167+
public void setDescription(String description) {
168+
this.description = parser.parseExpression(description, ParserContext.TEMPLATE_EXPRESSION);
169+
}
170+
171+
public String getMessage() {
172+
return description.getExpressionString();
173+
}
174+
175+
public void setRestTemplate(RestTemplate restTemplate) {
176+
this.restTemplate = restTemplate;
177+
}
178+
179+
public String getRecipients() {
180+
return recipients;
181+
}
182+
183+
public void setRecipients(String recipients) {
184+
this.recipients = recipients;
185+
}
186+
187+
public String getActions() {
188+
return actions;
189+
}
190+
191+
public void setActions(String actions) {
192+
this.actions = actions;
193+
}
194+
195+
public String getSource() {
196+
return source;
197+
}
198+
199+
public void setSource(String source) {
200+
this.source = source;
201+
}
202+
203+
public String getTags() {
204+
return tags;
205+
}
206+
207+
public void setTags(String tags) {
208+
this.tags = tags;
209+
}
210+
211+
public String getEntity() {
212+
return entity;
213+
}
214+
215+
public void setEntity(String entity) {
216+
this.entity = entity;
217+
}
218+
219+
public String getUser() {
220+
return user;
221+
}
222+
223+
public void setUser(String user) {
224+
this.user = user;
225+
}
226+
}

spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/config/NotifierConfigurationTest.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@
2525
import java.util.Arrays;
2626
import java.util.List;
2727

28+
import de.codecentric.boot.admin.notify.CompositeNotifier;
29+
import de.codecentric.boot.admin.notify.HipchatNotifier;
30+
import de.codecentric.boot.admin.notify.MailNotifier;
31+
import de.codecentric.boot.admin.notify.Notifier;
32+
import de.codecentric.boot.admin.notify.NotifierListener;
33+
import de.codecentric.boot.admin.notify.OpsGenieNotifier;
34+
import de.codecentric.boot.admin.notify.PagerdutyNotifier;
35+
import de.codecentric.boot.admin.notify.SlackNotifier;
2836
import org.junit.After;
2937
import org.junit.Test;
3038
import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration;
@@ -37,13 +45,6 @@
3745
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
3846
import de.codecentric.boot.admin.model.Application;
3947
import de.codecentric.boot.admin.model.StatusInfo;
40-
import de.codecentric.boot.admin.notify.CompositeNotifier;
41-
import de.codecentric.boot.admin.notify.HipchatNotifier;
42-
import de.codecentric.boot.admin.notify.MailNotifier;
43-
import de.codecentric.boot.admin.notify.Notifier;
44-
import de.codecentric.boot.admin.notify.NotifierListener;
45-
import de.codecentric.boot.admin.notify.PagerdutyNotifier;
46-
import de.codecentric.boot.admin.notify.SlackNotifier;
4748

4849
public class NotifierConfigurationTest {
4950
private static final ClientApplicationEvent APP_DOWN = new ClientApplicationStatusChangedEvent(
@@ -86,6 +87,13 @@ public void test_pagerduty() {
8687
is(instanceOf(PagerdutyNotifier.class)));
8788
}
8889

90+
@Test
91+
public void test_opsgenie() {
92+
load(null, "spring.boot.admin.notify.opsgenie.api-key:foo");
93+
assertThat(context.getBean(OpsGenieNotifier.class),
94+
is(instanceOf(OpsGenieNotifier.class)));
95+
}
96+
8997
@Test
9098
public void test_hipchat() {
9199
load(null, "spring.boot.admin.notify.hipchat.url:http://example.com");

0 commit comments

Comments
 (0)