Skip to content

Commit ee3a990

Browse files
committed
Add hipchat notification, test and relevant docs
1 parent c40cd36 commit ee3a990

File tree

4 files changed

+248
-0
lines changed

4 files changed

+248
-0
lines changed

spring-boot-admin-docs/src/main/asciidoc/index.adoc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,44 @@ To enable pagerduty notifications you just have to add a generic service to your
428428
|
429429
|===
430430

431+
[hipchat-notifications]
432+
==== Hipchat notifications ====
433+
To enable Hipchat notifications you need to create an API token from you Hipchat account and set the appropriate configuration properties.
434+
435+
.Hipchat notifications configuration options
436+
|===
437+
| Property name |Description |Default value
438+
439+
| spring.boot.admin.notify.hipchat.enabled
440+
| Enable Hipchat notifications
441+
| `true`
442+
443+
| spring.boot.admin.notify.hipchat.ignore-changes
444+
| Comma-delimited list of status changes to be ignored. Format: "<from-status>:<to-status>". Wildcards allowed.
445+
| `"UNKNOWN:UP"`
446+
447+
| spring.boot.admin.notify.hipchat.url
448+
| The HipChat REST API (V2) URL
449+
|
450+
451+
| spring.boot.admin.notify.hipchat.auth-token
452+
| The API token with access to the notification room
453+
|
454+
455+
| spring.boot.admin.notify.hipchat.room-id
456+
| The ID or url-encoded name of the room to send notifications to
457+
|
458+
459+
| spring.boot.admin.notify.hipchat.notify
460+
| Whether the message should trigger a user notification
461+
| `false`
462+
463+
| spring.boot.admin.notify.hipchat.description
464+
| Description to use in the event. SpEL-expressions are supported
465+
| `+++"<strong>#{application.name}</strong>/#{application.id} is <strong>#{to.status}</strong>"+++`
466+
|
467+
|===
468+
431469
[reminder-notifactaions]
432470
==== Reminder notifications ====
433471
To get reminders for down/offline applications you can add a `RemindingNotifier` to your `ApplicationContext`. The `RemindingNotifier` uses another `Notifier` as delegate to send the reminders.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package de.codecentric.boot.admin.config;
2+
3+
import de.codecentric.boot.admin.notify.HipchatNotifier;
4+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
5+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
6+
import org.springframework.boot.context.properties.ConfigurationProperties;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
/**
11+
* @author Jamie Brown
12+
*/
13+
@Configuration
14+
@ConditionalOnProperty("spring.boot.admin.notify.hipchat.url")
15+
public class HipchatNotifierConfiguration
16+
{
17+
@Bean
18+
@ConditionalOnMissingBean
19+
@ConditionalOnProperty(prefix = "spring.boot.admin.notify.hipchat", name = "enabled", matchIfMissing = true)
20+
@ConfigurationProperties("spring.boot.admin.notify.hipchat")
21+
public HipchatNotifier hipchatNotifier()
22+
{
23+
return new HipchatNotifier();
24+
}
25+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package de.codecentric.boot.admin.notify;
2+
3+
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
4+
import org.springframework.expression.Expression;
5+
import org.springframework.expression.ParserContext;
6+
import org.springframework.expression.spel.standard.SpelExpressionParser;
7+
import org.springframework.web.client.RestTemplate;
8+
9+
import java.net.URI;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
/**
14+
* @author Jamie BRown
15+
*/
16+
public class HipchatNotifier extends AbstractStatusChangeNotifier
17+
{
18+
private final static String DEFAULT_DESCRIPTION = "<strong>#{application.name}</strong>/#{application.id} is <strong>#{to.status}</strong>";
19+
20+
private final SpelExpressionParser parser = new SpelExpressionParser();
21+
private RestTemplate restTemplate = new RestTemplate();
22+
23+
/**
24+
* Base URL for HipChat API (i.e. https://ACCOUNT_NAME.hipchat.com/v2
25+
*/
26+
private URI url;
27+
28+
/**
29+
* API token that has access to notify in the room
30+
*/
31+
private String authToken;
32+
33+
/**
34+
* Id of the room to notify
35+
*/
36+
private String roomId;
37+
38+
/**
39+
* TRUE will cause OS notification, FALSE will only notify to room
40+
*/
41+
private Boolean notify;
42+
43+
/**
44+
* Trigger description. SpEL template using event as root;
45+
*/
46+
private Expression description;
47+
48+
public HipchatNotifier()
49+
{
50+
this.description = parser.parseExpression(DEFAULT_DESCRIPTION,
51+
ParserContext.TEMPLATE_EXPRESSION);
52+
}
53+
54+
@Override
55+
protected void doNotify(ClientApplicationStatusChangedEvent event)
56+
{
57+
restTemplate.postForEntity(buildUrl(), createHipChatNotification(event), Void.class);
58+
}
59+
60+
private String buildUrl()
61+
{
62+
return String.format("%s/room/%s/notification?auth_token=%s", url.toString(), roomId, authToken);
63+
}
64+
65+
private Map<String, Object> createHipChatNotification(ClientApplicationStatusChangedEvent event)
66+
{
67+
Map<String, Object> result = new HashMap<String, Object>();
68+
69+
String color = "UP".equals(event.getTo().getStatus()) ? "green" : "red";
70+
String message = description.getValue(event, String.class);
71+
72+
result.put("color", color);
73+
result.put("message", message);
74+
result.put("notify", Boolean.TRUE.equals(notify));
75+
result.put("message_format", "html");
76+
77+
return result;
78+
}
79+
80+
public void setUrl(URI url)
81+
{
82+
this.url = url;
83+
}
84+
85+
public void setAuthToken(String authToken)
86+
{
87+
this.authToken = authToken;
88+
}
89+
90+
public void setRoomId(String roomId)
91+
{
92+
this.roomId = roomId;
93+
}
94+
95+
public void setNotify(Boolean notify)
96+
{
97+
this.notify = notify;
98+
}
99+
100+
public void setDescription(String description)
101+
{
102+
this.description = parser.parseExpression(description, ParserContext.TEMPLATE_EXPRESSION);
103+
}
104+
105+
public void setRestTemplate(RestTemplate restTemplate)
106+
{
107+
this.restTemplate = restTemplate;
108+
}
109+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package de.codecentric.boot.admin.notify;
2+
3+
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
4+
import de.codecentric.boot.admin.model.Application;
5+
import de.codecentric.boot.admin.model.StatusInfo;
6+
import org.junit.Before;
7+
import org.junit.Test;
8+
import org.springframework.web.client.RestTemplate;
9+
10+
import java.net.URI;
11+
import java.util.HashMap;
12+
import java.util.Map;
13+
14+
import static org.mockito.Matchers.any;
15+
import static org.mockito.Matchers.eq;
16+
import static org.mockito.Mockito.mock;
17+
import static org.mockito.Mockito.verify;
18+
19+
/**
20+
* @author Jamie Brown
21+
*/
22+
public class HipchatNotifierTest
23+
{
24+
private HipchatNotifier notifier;
25+
private RestTemplate restTemplate;
26+
27+
@Before
28+
public void setUp()
29+
{
30+
restTemplate = mock(RestTemplate.class);
31+
notifier = new HipchatNotifier();
32+
notifier.setNotify(true);
33+
notifier.setAuthToken("--token-");
34+
notifier.setRoomId("-room-");
35+
notifier.setUrl(URI.create("http://localhost/v2"));
36+
notifier.setRestTemplate(restTemplate);
37+
}
38+
39+
@Test
40+
public void test_onApplicationEvent_resolve()
41+
{
42+
StatusInfo infoDown = StatusInfo.ofDown();
43+
StatusInfo infoUp = StatusInfo.ofUp();
44+
45+
notifier.notify(new ClientApplicationStatusChangedEvent(
46+
Application.create("App").withId("-id-").withHealthUrl("http://health").build(),
47+
infoDown, infoUp));
48+
49+
Map<String, Object> expected = new HashMap<String, Object>();
50+
expected.put("color", "green");
51+
expected.put("message", "<strong>App</strong>/-id- is <strong>UP</strong>");
52+
expected.put("notify", Boolean.TRUE);
53+
expected.put("message_format", "html");
54+
55+
verify(restTemplate).postForEntity(any(String.class), eq(expected), eq(Void.class));
56+
}
57+
58+
@Test
59+
public void test_onApplicationEvent_trigger()
60+
{
61+
StatusInfo infoDown = StatusInfo.ofDown();
62+
StatusInfo infoUp = StatusInfo.ofUp();
63+
64+
notifier.notify(new ClientApplicationStatusChangedEvent(
65+
Application.create("App").withId("-id-").withHealthUrl("http://health").build(),
66+
infoUp, infoDown));
67+
68+
Map<String, Object> expected = new HashMap<String, Object>();
69+
expected.put("color", "red");
70+
expected.put("message", "<strong>App</strong>/-id- is <strong>DOWN</strong>");
71+
expected.put("notify", Boolean.TRUE);
72+
expected.put("message_format", "html");
73+
74+
verify(restTemplate).postForEntity(any(String.class), eq(expected), eq(Void.class));
75+
}
76+
}

0 commit comments

Comments
 (0)