Skip to content

Commit

Permalink
Added support for custom labels and annotations
Browse files Browse the repository at this point in the history
Signed-off-by: Decker, Stefan <Stefan.Decker@gdata.de>
  • Loading branch information
StefanHufschmidt authored and jschaefer-pott committed Feb 18, 2019
1 parent 7e5bacb commit 2eca828
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 13 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ The values `startsAt`, `endsAt` and `generatorURL` will be transmitted to the Al
`startsAt` will be set to the point of time when the condition triggered the alert.
`endsAt` will be set to the point of time when the condition triggered the alert plus the set grace time which is configured for the alert.

Additionally you can configure your own custom annotations and labels which should be submitted to the AlertManager (see screenshot below).

## How to deploy on Graylog
You can easily build the plugin by executing `./gradlew build`.
Afterwards there should be a `.jar` file inside the `build/libs/` directory.
Expand All @@ -37,9 +39,6 @@ Follow the instructions mentioned [here](http://docs.graylog.org/en/2.4/pages/pl
![Configuration of Callback](images/New_AlertManager_Callback_Window.png)

## Planned Features
* Add possibility to define custom labels in UI when configuring the callback
* Add possibility to define custom annotations in UI when configuring the callback

You would like to contribute anything? - Take a look at [CONTRIBUTING.md](CONTRIBUTING.md).

## Known Issues
Expand Down
Binary file modified images/New_AlertManager_Callback_Window.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,25 @@
import org.graylog2.plugin.configuration.fields.TextField;
import org.graylog2.plugin.streams.Stream;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;

public class AlertManagerAlarmCallback implements AlarmCallback {

static final String CONFIGURATION_KEY_API_URL = "alertmanager_api_url";
static final String CONFIGURATION_KEY_ALERT_NAME = "alertmanager_alert_name";
static final String CONFIGURATION_KEY_CUSTOM_LABELS = "alertmanager_custom_labels";
static final String CONFIGURATION_KEY_CUSTOM_ANNOTATIONS = "alertmanager_custom_annotations";

private Configuration configuration;
private AlertManagerPostRequestSender alertManagerPostRequestSender;

@Override
public void initialize(Configuration config) throws AlarmCallbackConfigurationException {
configuration = config;
alertManagerPostRequestSender = new AlertManagerPostRequestSender(config.getString("alertmanager_api_url"));
alertManagerPostRequestSender = new AlertManagerPostRequestSender(config.getString(CONFIGURATION_KEY_API_URL));
}

@Override
Expand All @@ -44,7 +50,7 @@ public ConfigurationRequest getRequestedConfiguration() {

// API URL
ConfigurationField alertmanagerApiUrl = new TextField(
"alertmanager_api_url",
CONFIGURATION_KEY_API_URL,
"AlertManager API URL",
"http://localhost:9093/api/v1/alerts",
"This callback sends a POST-Request to an AlertManager API. It converts the information into a format which is readable by the AlertManager.",
Expand All @@ -54,14 +60,34 @@ public ConfigurationRequest getRequestedConfiguration() {

// Alert Name
ConfigurationField alertName = new TextField(
"alertmanager_alert_name",
CONFIGURATION_KEY_ALERT_NAME,
"AlertManager Alert Name",
"TestAlert",
"The name for the specific AlertManager alert (will be transmitted as 'alertname'-label).",
Optional.NOT_OPTIONAL
);
configurationRequest.addField(alertName);

// Custom labels
ConfigurationField customLabels = new TextField(
CONFIGURATION_KEY_CUSTOM_LABELS,
"Custom AlertManager labels",
"",
"The custom AlertManager label key-value-pairs separated by '" + CustomPropertiesTextFieldParser.KEY_VALUE_PAIR_SEPARATOR + "' to set for each alert. Please use the following notation: 'label1=value1" + CustomPropertiesTextFieldParser.KEY_VALUE_PAIR_SEPARATOR + "label2=value2'",
Optional.OPTIONAL
);
configurationRequest.addField(customLabels);

// Custom annotations
ConfigurationField customAnnotations = new TextField(
CONFIGURATION_KEY_CUSTOM_ANNOTATIONS,
"Custom AlertManager annotations",
"",
"The custom AlertManager annotation key-value-pairs separated by '" + CustomPropertiesTextFieldParser.KEY_VALUE_PAIR_SEPARATOR + "' to set for each alert. Please use the following notation: 'annotation1=value1" + CustomPropertiesTextFieldParser.KEY_VALUE_PAIR_SEPARATOR + "annotation2=value2'",
Optional.OPTIONAL
);
configurationRequest.addField(customAnnotations);

return configurationRequest;
}

Expand All @@ -77,14 +103,34 @@ public Map<String, Object> getAttributes() {

@Override
public void checkConfiguration() throws ConfigurationException {
String apiUrl = configuration.getString("alertmanager_api_url");
final String apiUrl = configuration.getString(CONFIGURATION_KEY_API_URL);
if (apiUrl == null) {
throw new ConfigurationException("AlertManager API URL has to be set.");
}

try {
new URI(apiUrl);
} catch (URISyntaxException e) {
throw new ConfigurationException("The URL: '" + apiUrl + "' is not a valid URL. " + e.getMessage());
}


CustomPropertiesTextFieldParser customPropertiesTextFieldParser = new CustomPropertiesTextFieldParser();

final String customLabels = configuration.getString(CONFIGURATION_KEY_CUSTOM_LABELS);
try {
customPropertiesTextFieldParser.extractKeyValuePairsFromCustomField(customLabels);
} catch (IOException e) {
// Not a valid configuration for custom labels
throw new ConfigurationException("The format for given custom labels is invalid. " + e.getMessage());
}

final String customAnnotations = configuration.getString(CONFIGURATION_KEY_CUSTOM_ANNOTATIONS);
try {
customPropertiesTextFieldParser.extractKeyValuePairsFromCustomField(customAnnotations);
} catch (IOException e) {
// Not a valid configuration for custom labels
throw new ConfigurationException("The format for given custom annotations is invalid. " + e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@
import org.graylog2.plugin.streams.Stream;
import org.joda.time.DateTime;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

class AlertManagerPayloadBuilder {

private static final String STREAM_TITLE_KEY = "stream_title";
private static final String ALERTMANAGER_ALERT_NAME_KEY = "alertmanager_alert_name";
private static final String ALERTNAME_KEY = "alertname";

private Stream stream;
private AlertCondition.CheckResult checkResult;
private Configuration configuration;
private CustomPropertiesTextFieldParser customPropertiesTextFieldParser;

private AlertManagerPayloadBuilder() {
// Private constructor to hide the implicit one
customPropertiesTextFieldParser = new CustomPropertiesTextFieldParser();
}

static AlertManagerPayloadBuilder newInstance() {
Expand Down Expand Up @@ -56,20 +58,28 @@ AlertManagerPayload build() {

private Map<String, Object> extractLabels() {
Map<String, Object> labels = new HashMap<>();
if(configuration != null && configuration.getString(ALERTMANAGER_ALERT_NAME_KEY) != null) {
labels.put(ALERTNAME_KEY, configuration.getString(ALERTMANAGER_ALERT_NAME_KEY));
if(configuration != null && configuration.getString(AlertManagerAlarmCallback.CONFIGURATION_KEY_ALERT_NAME) != null) {
labels.put(ALERTNAME_KEY, configuration.getString(AlertManagerAlarmCallback.CONFIGURATION_KEY_ALERT_NAME));
} else {
labels.put(ALERTNAME_KEY, "Please add a valid configuration object to AlertManager plugin.");
}

// custom labels
final String customLabelString = configuration != null ? configuration.getString(AlertManagerAlarmCallback.CONFIGURATION_KEY_CUSTOM_LABELS) : null;
try {
labels.putAll(customPropertiesTextFieldParser.extractKeyValuePairsFromCustomField(customLabelString));
} catch (IOException e) {
// damaged configuration, so we'll not put any additional label into the map
}

return labels;
}

private Map<String, Object> extractAnnotations() {
Map<String, Object> annotations = new HashMap<>();

if(stream != null && stream.getTitle() != null) {
annotations.put(STREAM_TITLE_KEY, stream.getTitle());
annotations.put("stream_title", stream.getTitle());
}

if(checkResult != null) {
Expand All @@ -80,6 +90,14 @@ private Map<String, Object> extractAnnotations() {
}
}

// custom annotations
final String customAnnotationString = configuration != null ? configuration.getString(AlertManagerAlarmCallback.CONFIGURATION_KEY_CUSTOM_ANNOTATIONS) : null;
try {
annotations.putAll(customPropertiesTextFieldParser.extractKeyValuePairsFromCustomField(customAnnotationString));
} catch (IOException e) {
// damaged configuration, so we'll not put any additional annotation into the map
}

return annotations;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package de.gdata.mobilelab.alertmanagercallback;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
* This parser can parse key-value-pairs from a text field string.
* It uses {@link #KEY_VALUE_PAIR_SEPARATOR} for separating each key-value-pair.
* The keys and related values will be parsed using {@link Properties#load(InputStream)}.
*/
class CustomPropertiesTextFieldParser {

/** The separator used in text field to split between key-value-pairs. */
static final String KEY_VALUE_PAIR_SEPARATOR = ";";

/** The separator used by {@link Properties#load(InputStream)} for key-value-pair separation. */
private static final String PROPERTIES_KEY_VALUE_PAIR_SEPARATOR = "\n";

/**
* Parses the text field value with custom key-value-pairs into a map.<br>
* It uses the {@link #KEY_VALUE_PAIR_SEPARATOR} for the line separation. This separation is required
* for loading the key-value-pairs using {@link Properties#load(InputStream)}.
*
* @param textFieldValue the text field value
* @return the map with key-value-pairs from text field
* @throws IOException if parsing the key-value-pairs fails
*/
Map<? extends String, ?> extractKeyValuePairsFromCustomField(String textFieldValue) throws IOException {
Map<String, Object> extractedPairs = new HashMap<>();

if (textFieldValue != null && !"".equals(textFieldValue)) {
final String preparedTextFieldValue = textFieldValue.replaceAll(KEY_VALUE_PAIR_SEPARATOR, PROPERTIES_KEY_VALUE_PAIR_SEPARATOR);
Properties properties = new Properties();
InputStream stringInputStream = new ByteArrayInputStream(preparedTextFieldValue.getBytes(StandardCharsets.UTF_8));
properties.load(stringInputStream);
properties.forEach((key, value) -> extractedPairs.put((String) key, value));
}

return extractedPairs;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void getRequestedConfiguration() {
ConfigurationRequest configurationRequest = alertManagerAlarmCallback.getRequestedConfiguration();

// then: text fields have been set
assertEquals(2, configurationRequest.getFields().size());
assertEquals(4, configurationRequest.getFields().size());

ConfigurationField field = configurationRequest.getField("alertmanager_api_url");
assertNotNull(field);
Expand All @@ -69,6 +69,22 @@ public void getRequestedConfiguration() {
assertEquals("TestAlert", field.getDefaultValue());
assertEquals("The name for the specific AlertManager alert (will be transmitted as 'alertname'-label).", field.getDescription());
assertEquals(ConfigurationField.Optional.NOT_OPTIONAL, field.isOptional());

field = configurationRequest.getField("alertmanager_custom_labels");
assertNotNull(field);
assertEquals(TextField.FIELD_TYPE, field.getFieldType());
assertEquals("Custom AlertManager labels", field.getHumanName());
assertEquals("", field.getDefaultValue());
assertEquals("The custom AlertManager label key-value-pairs separated by '" + CustomPropertiesTextFieldParser.KEY_VALUE_PAIR_SEPARATOR + "' to set for each alert. Please use the following notation: 'label1=value1" + CustomPropertiesTextFieldParser.KEY_VALUE_PAIR_SEPARATOR + "label2=value2'", field.getDescription());
assertEquals(ConfigurationField.Optional.OPTIONAL, field.isOptional());

field = configurationRequest.getField("alertmanager_custom_annotations");
assertNotNull(field);
assertEquals(TextField.FIELD_TYPE, field.getFieldType());
assertEquals("Custom AlertManager annotations", field.getHumanName());
assertEquals("", field.getDefaultValue());
assertEquals("The custom AlertManager annotation key-value-pairs separated by '" + CustomPropertiesTextFieldParser.KEY_VALUE_PAIR_SEPARATOR + "' to set for each alert. Please use the following notation: 'annotation1=value1" + CustomPropertiesTextFieldParser.KEY_VALUE_PAIR_SEPARATOR + "annotation2=value2'", field.getDescription());
assertEquals(ConfigurationField.Optional.OPTIONAL, field.isOptional());
}

@Test
Expand Down
Loading

0 comments on commit 2eca828

Please sign in to comment.