Commit 230896f0 by fernandosure Committed by Johannes Edmeier

Add notifier for opsgenie

parent 69f85687
......@@ -165,6 +165,60 @@ To enable pagerduty notifications you just have to add a generic service to your
|
|===
[[opsgenie-notifications]]
==== OpsGenie notifications ====
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.
.OpsGenie notifications configuration options
|===
| Property name |Description |Default value
| spring.boot.admin.notify.opsgenie.enabled
| Enable OpsGenie notifications
| `true`
| spring.boot.admin.notify.opsgenie.ignore-changes
| Comma-delimited list of status changes to be ignored. Format: "<from-status>:<to-status>". Wildcards allowed.
| `"UNKNOWN:UP"`
| spring.boot.admin.notify.opsgenie.api-key
| apiKey you received when creating the integration
|
| spring.boot.admin.notify.opsgenie.url
| OpsGenie Alert API url
| `+++"https://api.opsgenie.com/v1/json/alert"+++`
| spring.boot.admin.notify.opsgenie.description
| Description to use in the event. SpEL-expressions are supported
| `+++"#{application.name}/#{application.id} is #{to.status}"+++`
| spring.boot.admin.notify.opsgenie.recipients
| User, group, schedule or escalation names to calculate which users will receive the notifications of the alert.
|
| spring.boot.admin.notify.opsgenie.actions
| Comma separated list of actions that can be executed.
|
| spring.boot.admin.notify.opsgenie.source
| Field to specify source of alert. By default, it will be assigned to IP address of incoming request.
|
| spring.boot.admin.notify.opsgenie.tags
| Comma separated list of labels attached to the alert.
|
| spring.boot.admin.notify.opsgenie.entity
| The entity the alert is related to.
|
| spring.boot.admin.notify.opsgenie.user
| Default owner of the execution. If user is not specified, the system becomes owner of the execution.
|
|===
[hipchat-notifications]
==== Hipchat notifications ====
To enable Hipchat notifications you need to create an API token from you Hipchat account and set the appropriate configuration properties.
......
......@@ -17,6 +17,15 @@ package de.codecentric.boot.admin.config;
import java.util.List;
import de.codecentric.boot.admin.notify.CompositeNotifier;
import de.codecentric.boot.admin.notify.MailNotifier;
import de.codecentric.boot.admin.notify.Notifier;
import de.codecentric.boot.admin.notify.NotifierListener;
import de.codecentric.boot.admin.notify.PagerdutyNotifier;
import de.codecentric.boot.admin.notify.OpsGenieNotifier;
import de.codecentric.boot.admin.notify.HipchatNotifier;
import de.codecentric.boot.admin.notify.SlackNotifier;
import de.codecentric.boot.admin.notify.LetsChatNotifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
......@@ -33,14 +42,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.mail.MailSender;
import de.codecentric.boot.admin.notify.CompositeNotifier;
import de.codecentric.boot.admin.notify.HipchatNotifier;
import de.codecentric.boot.admin.notify.MailNotifier;
import de.codecentric.boot.admin.notify.Notifier;
import de.codecentric.boot.admin.notify.NotifierListener;
import de.codecentric.boot.admin.notify.PagerdutyNotifier;
import de.codecentric.boot.admin.notify.SlackNotifier;
import de.codecentric.boot.admin.notify.LetsChatNotifier;
import de.codecentric.boot.admin.notify.filter.FilteringNotifier;
import de.codecentric.boot.admin.notify.filter.web.NotificationFilterController;
import de.codecentric.boot.admin.web.PrefixHandlerMapping;
......@@ -136,6 +137,22 @@ public class NotifierConfiguration {
}
}
@Configuration
@ConditionalOnProperty(prefix = "spring.boot.admin.notify.opsgenie", name = "api-key")
@AutoConfigureBefore({ NotifierListenerConfiguration.class,
CompositeNotifierConfiguration.class })
public static class OpsGenieNotifierConfiguration {
@Bean
@ConditionalOnMissingBean
@ConfigurationProperties("spring.boot.admin.notify.opsgenie")
public OpsGenieNotifier opsgenieNotifier() {
return new OpsGenieNotifier();
}
}
@Configuration
@ConditionalOnProperty(prefix = "spring.boot.admin.notify.hipchat", name = "url")
@AutoConfigureBefore({ NotifierListenerConfiguration.class,
......
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.codecentric.boot.admin.notify;
import de.codecentric.boot.admin.event.ClientApplicationEvent;
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
/**
* Notifier submitting events to opsgenie.com.
*
* @author Fernando Sure
*/
public class OpsGenieNotifier extends AbstractStatusChangeNotifier {
private static final URI DEFAULT_URI = URI.create("https://api.opsgenie.com/v1/json/alert");
private static final String DEFAULT_MESSAGE = "#{application.name}/#{application.id} is #{to.status}";
private final SpelExpressionParser parser = new SpelExpressionParser();
private RestTemplate restTemplate = new RestTemplate();
/**
* BASE URL for OpsGenie API
*/
private URI url = DEFAULT_URI;
/**
* Integration ApiKey
*/
private String apiKey;
/**
* Comma separated list of users, groups, schedules or escalation names
* to calculate which users will receive the notifications of the alert.
*/
private String recipients;
/**
* Comma separated list of actions that can be executed.
*/
private String actions;
/**
* Field to specify source of alert. By default, it will be assigned to IP address of incoming request
*/
private String source;
/**
* Comma separated list of labels attached to the alert
*/
private String tags;
/**
* The entity the alert is related to.
*/
private String entity;
/**
* Default owner of the execution. If user is not specified, the system becomes owner of the execution.
*/
private String user;
/**
* Trigger description. SpEL template using event as root;
*/
private Expression description;
public OpsGenieNotifier() {
this.description = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);
}
@Override
protected void doNotify(ClientApplicationEvent event) throws Exception {
restTemplate.exchange(buildUrl(event), HttpMethod.POST, createRequest(event), Void.class);
}
protected String buildUrl(ClientApplicationEvent event) {
if ((event instanceof ClientApplicationStatusChangedEvent) &&
("UP".equals(((ClientApplicationStatusChangedEvent) event).getTo().getStatus()))) {
return String.format("%s/close", url.toString());
}
return url.toString();
}
protected HttpEntity createRequest(ClientApplicationEvent event) {
Map<String, Object> body = new HashMap<>();
body.put("apiKey", apiKey);
body.put("message", getMessage(event));
body.put("alias", event.getApplication().getName() + "/" + event.getApplication().getId());
body.put("description", getDescription(event));
if (event instanceof ClientApplicationStatusChangedEvent &&
!"UP".equals(((ClientApplicationStatusChangedEvent) event).getTo().getStatus())) {
if (recipients != null) {
body.put("recipients", recipients);
}
if (actions != null) {
body.put("actions", actions);
}
if (source != null) {
body.put("source", source);
}
if (tags != null) {
body.put("tags", tags);
}
if (entity != null) {
body.put("entity", entity);
}
if (user != null) {
body.put("user", user);
}
Map<String, Object> details = new HashMap<>();
details.put("type", "link");
details.put("href", event.getApplication().getHealthUrl());
details.put("text", "Application health-endpoint");
body.put("details", details);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<>(body, headers);
}
protected String getMessage(ClientApplicationEvent event) {
return description.getValue(event, String.class);
}
protected String getDescription(ClientApplicationEvent event) {
return String.format("Application %s (%s) went from %s to %s", event.getApplication().getName(),
event.getApplication().getId(), ((ClientApplicationStatusChangedEvent) event).getFrom().getStatus(),
((ClientApplicationStatusChangedEvent) event).getTo().getStatus());
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getApiKey() {
return apiKey;
}
public void setDescription(String description) {
this.description = parser.parseExpression(description, ParserContext.TEMPLATE_EXPRESSION);
}
public String getMessage() {
return description.getExpressionString();
}
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String getRecipients() {
return recipients;
}
public void setRecipients(String recipients) {
this.recipients = recipients;
}
public String getActions() {
return actions;
}
public void setActions(String actions) {
this.actions = actions;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getTags() {
return tags;
}
public void setTags(String tags) {
this.tags = tags;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
}
......@@ -25,6 +25,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import de.codecentric.boot.admin.notify.CompositeNotifier;
import de.codecentric.boot.admin.notify.HipchatNotifier;
import de.codecentric.boot.admin.notify.MailNotifier;
import de.codecentric.boot.admin.notify.Notifier;
import de.codecentric.boot.admin.notify.NotifierListener;
import de.codecentric.boot.admin.notify.OpsGenieNotifier;
import de.codecentric.boot.admin.notify.PagerdutyNotifier;
import de.codecentric.boot.admin.notify.SlackNotifier;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration;
......@@ -37,13 +45,6 @@ import de.codecentric.boot.admin.event.ClientApplicationEvent;
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.model.StatusInfo;
import de.codecentric.boot.admin.notify.CompositeNotifier;
import de.codecentric.boot.admin.notify.HipchatNotifier;
import de.codecentric.boot.admin.notify.MailNotifier;
import de.codecentric.boot.admin.notify.Notifier;
import de.codecentric.boot.admin.notify.NotifierListener;
import de.codecentric.boot.admin.notify.PagerdutyNotifier;
import de.codecentric.boot.admin.notify.SlackNotifier;
public class NotifierConfigurationTest {
private static final ClientApplicationEvent APP_DOWN = new ClientApplicationStatusChangedEvent(
......@@ -87,6 +88,13 @@ public class NotifierConfigurationTest {
}
@Test
public void test_opsgenie() {
load(null, "spring.boot.admin.notify.opsgenie.api-key:foo");
assertThat(context.getBean(OpsGenieNotifier.class),
is(instanceOf(OpsGenieNotifier.class)));
}
@Test
public void test_hipchat() {
load(null, "spring.boot.admin.notify.hipchat.url:http://example.com");
assertThat(context.getBean(HipchatNotifier.class), is(instanceOf(HipchatNotifier.class)));
......
package de.codecentric.boot.admin.notify;
import de.codecentric.boot.admin.event.ClientApplicationEvent;
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
import de.codecentric.boot.admin.model.Application;
import de.codecentric.boot.admin.model.StatusInfo;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class OpsGenieNotifierTest {
private OpsGenieNotifier notifier;
private RestTemplate restTemplate;
@Before
public void setUp() {
restTemplate = mock(RestTemplate.class);
notifier = new OpsGenieNotifier();
notifier.setApiKey("--service--");
notifier.setRecipients("--recipients--");
notifier.setRestTemplate(restTemplate);
}
@Test
public void test_onApplicationEvent_resolve() {
StatusInfo infoDown = StatusInfo.ofDown();
StatusInfo infoUp = StatusInfo.ofUp();
ClientApplicationStatusChangedEvent event = getEvent(infoDown, infoUp);
notifier.notify(event);
verify(restTemplate).exchange(eq("https://api.opsgenie.com/v1/json/alert/close"), eq(HttpMethod.POST),
eq(expectedRequest(event)), eq(Void.class));
}
@Test
public void test_onApplicationEvent_trigger() {
StatusInfo infoDown = StatusInfo.ofDown();
StatusInfo infoUp = StatusInfo.ofUp();
ClientApplicationStatusChangedEvent event = getEvent(infoUp, infoDown);
notifier.notify(event);
verify(restTemplate).exchange(eq("https://api.opsgenie.com/v1/json/alert"), eq(HttpMethod.POST),
eq(expectedRequest(event)), eq(Void.class));
}
private ClientApplicationStatusChangedEvent getEvent(StatusInfo from, StatusInfo to) {
return new ClientApplicationStatusChangedEvent(
Application.create("App").withId("-id-").withHealthUrl("http://health").build(), from, to);
}
private String getMessage(ClientApplicationEvent event) {
return String.format("%s/%s is %s", event.getApplication().getName(), event.getApplication().getId(),
((ClientApplicationStatusChangedEvent) event).getTo().getStatus());
}
private String getDescription(ClientApplicationEvent event) {
return String.format("Application %s (%s) went from %s to %s", event.getApplication().getName(),
event.getApplication().getId(), ((ClientApplicationStatusChangedEvent) event).getFrom().getStatus(),
((ClientApplicationStatusChangedEvent) event).getTo().getStatus());
}
private HttpEntity expectedRequest(ClientApplicationEvent event) {
Map<String, Object> expected = new HashMap<>();
expected.put("apiKey", "--service--");
expected.put("message", getMessage(event));
expected.put("alias", event.getApplication().getName() + "/" + event.getApplication().getId());
expected.put("description", getDescription(event));
if (event instanceof ClientApplicationStatusChangedEvent &&
!"UP".equals(((ClientApplicationStatusChangedEvent) event).getTo().getStatus())) {
expected.put("recipients", "--recipients--");
Map<String, Object> details = new HashMap<>();
details.put("type", "link");
details.put("href", event.getApplication().getHealthUrl());
details.put("text", "Application health-endpoint");
expected.put("details", details);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<>(expected, headers);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment