Watcher: Adding PagerDuty Action

* This action enables sending notifications to pager duty services.
* Utilizes pager duty's REST API
* Similar to the `email`, `hipchat` and `slack` actions, multiple `pagerduty` accounts can be configured, each with its own Service API key
* A `pagerduty` account is roughly mapped to a service in your pagerduty service.
* `pagerduty` actions are associated with an account, or if not, their events will be sent via the default account.
* An incident can be acknowledged, resolved or triggered

Closes elastic/elasticsearch#492

Original commit: elastic/x-pack-elasticsearch@72cc21d119
This commit is contained in:
Alexander Reelsen 2016-01-05 19:39:25 +01:00
parent e8eb0fa312
commit 9709036024
22 changed files with 2170 additions and 8 deletions

View File

@ -27,6 +27,8 @@ import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.InternalEmailService;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatService;
import org.elasticsearch.watcher.actions.hipchat.service.InternalHipChatService;
import org.elasticsearch.watcher.actions.pagerduty.service.InternalPagerDutyService;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyService;
import org.elasticsearch.watcher.actions.slack.service.InternalSlackService;
import org.elasticsearch.watcher.actions.slack.service.SlackService;
import org.elasticsearch.watcher.client.WatcherClientModule;
@ -163,6 +165,7 @@ public class WatcherPlugin extends Plugin {
EmailService.class,
HipChatService.class,
SlackService.class,
PagerDutyService.class,
HttpClient.class,
WatcherSettingsValidation.class);
}
@ -193,6 +196,7 @@ public class WatcherPlugin extends Plugin {
module.registerSetting(InternalSlackService.SLACK_ACCOUNT_SETTING);
module.registerSetting(InternalEmailService.EMAIL_ACCOUNT_SETTING);
module.registerSetting(InternalHipChatService.HIPCHAT_ACCOUNT_SETTING);
module.registerSetting(InternalPagerDutyService.PAGERDUTY_ACCOUNT_SETTING);
}
public void onModule(NetworkModule module) {

View File

@ -10,6 +10,8 @@ import org.elasticsearch.watcher.actions.email.service.EmailTemplate;
import org.elasticsearch.watcher.actions.hipchat.HipChatAction;
import org.elasticsearch.watcher.actions.index.IndexAction;
import org.elasticsearch.watcher.actions.logging.LoggingAction;
import org.elasticsearch.watcher.actions.pagerduty.PagerDutyAction;
import org.elasticsearch.watcher.actions.pagerduty.service.IncidentEvent;
import org.elasticsearch.watcher.actions.slack.SlackAction;
import org.elasticsearch.watcher.actions.slack.service.message.SlackMessage;
import org.elasticsearch.watcher.actions.webhook.WebhookAction;
@ -87,4 +89,16 @@ public final class ActionBuilders {
public static SlackAction.Builder slackAction(String account, SlackMessage.Template message) {
return SlackAction.builder(account, message);
}
public static PagerDutyAction.Builder triggerPagerDutyAction(String account, String description) {
return pagerDutyAction(IncidentEvent.templateBuilder(description).setAccount(account));
}
public static PagerDutyAction.Builder pagerDutyAction(IncidentEvent.Template.Builder event) {
return PagerDutyAction.builder(event.build());
}
public static PagerDutyAction.Builder pagerDutyAction(IncidentEvent.Template event) {
return PagerDutyAction.builder(event);
}
}

View File

@ -20,6 +20,10 @@ import org.elasticsearch.watcher.actions.index.IndexAction;
import org.elasticsearch.watcher.actions.index.IndexActionFactory;
import org.elasticsearch.watcher.actions.logging.LoggingAction;
import org.elasticsearch.watcher.actions.logging.LoggingActionFactory;
import org.elasticsearch.watcher.actions.pagerduty.PagerDutyAction;
import org.elasticsearch.watcher.actions.pagerduty.PagerDutyActionFactory;
import org.elasticsearch.watcher.actions.pagerduty.service.InternalPagerDutyService;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyService;
import org.elasticsearch.watcher.actions.slack.SlackAction;
import org.elasticsearch.watcher.actions.slack.SlackActionFactory;
import org.elasticsearch.watcher.actions.slack.service.InternalSlackService;
@ -43,6 +47,7 @@ public class WatcherActionModule extends AbstractModule {
registerAction(LoggingAction.TYPE, LoggingActionFactory.class);
registerAction(HipChatAction.TYPE, HipChatActionFactory.class);
registerAction(SlackAction.TYPE, SlackActionFactory.class);
registerAction(PagerDutyAction.TYPE, PagerDutyActionFactory.class);
}
public void registerAction(String type, Class<? extends ActionFactory> parserType) {
@ -60,11 +65,21 @@ public class WatcherActionModule extends AbstractModule {
bind(ActionRegistry.class).asEagerSingleton();
// email
bind(HtmlSanitizer.class).asEagerSingleton();
bind(InternalEmailService.class).asEagerSingleton();
bind(EmailService.class).to(InternalEmailService.class).asEagerSingleton();
bind(HipChatService.class).to(InternalHipChatService.class).asEagerSingleton();
bind(SlackService.class).to(InternalSlackService.class).asEagerSingleton();
// hipchat
bind(InternalHipChatService.class).asEagerSingleton();
bind(HipChatService.class).to(InternalHipChatService.class);
// slack
bind(InternalSlackService.class).asEagerSingleton();
bind(SlackService.class).to(InternalSlackService.class);
// pager duty
bind(InternalPagerDutyService.class).asEagerSingleton();
bind(PagerDutyService.class).to(InternalPagerDutyService.class);
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ExecutableAction;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyAccount;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyService;
import org.elasticsearch.watcher.actions.pagerduty.service.SentEvent;
import org.elasticsearch.watcher.actions.pagerduty.service.IncidentEvent;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.elasticsearch.watcher.watch.Payload;
import java.util.Map;
/**
*
*/
public class ExecutablePagerDutyAction extends ExecutableAction<PagerDutyAction> {
private final TextTemplateEngine templateEngine;
private final PagerDutyService pagerDutyService;
public ExecutablePagerDutyAction(PagerDutyAction action, ESLogger logger, PagerDutyService pagerDutyService, TextTemplateEngine templateEngine) {
super(action, logger);
this.pagerDutyService = pagerDutyService;
this.templateEngine = templateEngine;
}
@Override
public Action.Result execute(final String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
PagerDutyAccount account = action.event.account != null ?
pagerDutyService.getAccount(action.event.account) :
pagerDutyService.getDefaultAccount();
if (account == null) {
// the account associated with this action was deleted
throw new IllegalStateException("account [" + action.event.account + "] was not found. perhaps it was deleted");
}
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
IncidentEvent event = action.event.render(ctx.watch().id(), actionId, templateEngine, model, account.getDefaults());
if (ctx.simulateAction(actionId)) {
return new PagerDutyAction.Result.Simulated(event);
}
SentEvent sentEvent = account.send(event, payload);
return new PagerDutyAction.Result.Executed(account.getName(), sentEvent);
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.pagerduty.service.IncidentEvent;
import org.elasticsearch.watcher.actions.pagerduty.service.SentEvent;
import java.io.IOException;
import java.util.Objects;
/**
*
*/
public class PagerDutyAction implements Action {
public static final String TYPE = "pagerduty";
final IncidentEvent.Template event;
public PagerDutyAction(IncidentEvent.Template event) {
this.event = event;
}
@Override
public String type() {
return TYPE;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PagerDutyAction that = (PagerDutyAction) o;
return Objects.equals(event, that.event);
}
@Override
public int hashCode() {
return Objects.hash(event);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
event.toXContent(builder, params);
return builder;
}
public static PagerDutyAction parse(String watchId, String actionId, XContentParser parser) throws IOException {
IncidentEvent.Template eventTemplate = IncidentEvent.Template.parse(watchId, actionId, parser);
return new PagerDutyAction(eventTemplate);
}
public static Builder builder(IncidentEvent.Template event) {
return new Builder(new PagerDutyAction(event));
}
public interface Result {
class Executed extends Action.Result implements Result {
private final String account;
private final SentEvent sentEvent;
public Executed(String account, SentEvent sentEvent) {
super(TYPE, status(sentEvent));
this.account = account;
this.sentEvent = sentEvent;
}
public SentEvent sentEvent() {
return sentEvent;
}
public String account() {
return account;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(type);
builder.field(XField.SENT_EVENT.getPreferredName(), sentEvent, params);
return builder.endObject();
}
static Status status(SentEvent sentEvent) {
return sentEvent.successful() ? Status.SUCCESS : Status.FAILURE;
}
}
class Simulated extends Action.Result implements Result {
private final IncidentEvent event;
protected Simulated(IncidentEvent event) {
super(TYPE, Status.SIMULATED);
this.event = event;
}
public IncidentEvent event() {
return event;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject(type)
.field(XField.EVENT.getPreferredName(), event, params)
.endObject();
}
}
}
public static class Builder implements Action.Builder<PagerDutyAction> {
final PagerDutyAction action;
public Builder(PagerDutyAction action) {
this.action = action;
}
@Override
public PagerDutyAction build() {
return action;
}
}
public interface XField {
ParseField SENT_EVENT = new ParseField("sent_event");
ParseField EVENT = new ParseField("event");
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.ActionFactory;
import org.elasticsearch.watcher.actions.hipchat.ExecutableHipChatAction;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyAccount;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyService;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import java.io.IOException;
/**
*
*/
public class PagerDutyActionFactory extends ActionFactory<PagerDutyAction, ExecutablePagerDutyAction> {
private final TextTemplateEngine templateEngine;
private final PagerDutyService pagerDutyService;
@Inject
public PagerDutyActionFactory(Settings settings, TextTemplateEngine templateEngine, PagerDutyService pagerDutyService) {
super(Loggers.getLogger(ExecutableHipChatAction.class, settings));
this.templateEngine = templateEngine;
this.pagerDutyService = pagerDutyService;
}
@Override
public String type() {
return PagerDutyAction.TYPE;
}
@Override
public PagerDutyAction parseAction(String watchId, String actionId, XContentParser parser) throws IOException {
PagerDutyAction action = PagerDutyAction.parse(watchId, actionId, parser);
PagerDutyAccount account = pagerDutyService.getAccount(action.event.account);
if (account == null) {
throw new ElasticsearchParseException("could not parse [pagerduty] action [{}/{}]. unknown pager duty account [{}]", watchId, account, action.event.account);
}
return action;
}
@Override
public ExecutablePagerDutyAction createExecutable(PagerDutyAction action) {
return new ExecutablePagerDutyAction(action, actionLogger, pagerDutyService, templateEngine);
}
}

View File

@ -0,0 +1,409 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.support.http.HttpMethod;
import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.Scheme;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.elasticsearch.watcher.watch.Payload;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Official documentation for this can be found at
*
* https://developer.pagerduty.com/documentation/howto/manually-trigger-an-incident/
* https://developer.pagerduty.com/documentation/integration/events/trigger
* https://developer.pagerduty.com/documentation/integration/events/acknowledge
* https://developer.pagerduty.com/documentation/integration/events/resolve
*/
public class IncidentEvent implements ToXContent {
static final String HOST = "events.pagerduty.com";
static final String PATH = "/generic/2010-04-15/create_event.json";
final String description;
final @Nullable String incidentKey;
final @Nullable String client;
final @Nullable String clientUrl;
final @Nullable String account;
final String eventType;
final boolean attachPayload;
final @Nullable IncidentEventContext[] contexts;
public IncidentEvent(String description, @Nullable String eventType, @Nullable String incidentKey, @Nullable String client,
@Nullable String clientUrl, @Nullable String account, boolean attachPayload, @Nullable IncidentEventContext[] contexts) {
this.description = description;
if (description == null) {
throw new IllegalStateException("could not create pagerduty event. missing required [" + XField.DESCRIPTION.getPreferredName() + "] setting");
}
this.incidentKey = incidentKey;
this.client = client;
this.clientUrl = clientUrl;
this.account = account;
this.attachPayload = attachPayload;
this.contexts = contexts;
this.eventType = Strings.hasLength(eventType) ? eventType : "trigger";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IncidentEvent template = (IncidentEvent) o;
return Objects.equals(description, template.description) &&
Objects.equals(incidentKey, template.incidentKey) &&
Objects.equals(client, template.client) &&
Objects.equals(clientUrl, template.clientUrl) &&
Objects.equals(attachPayload, template.attachPayload) &&
Objects.equals(eventType, template.eventType) &&
Objects.equals(account, template.account) &&
Arrays.equals(contexts, template.contexts);
}
@Override
public int hashCode() {
int result = Objects.hash(description, incidentKey, client, clientUrl, account, attachPayload, eventType);
result = 31 * result + Arrays.hashCode(contexts);
return result;
}
public HttpRequest createRequest(final String serviceKey, final Payload payload) throws IOException {
return HttpRequest.builder(HOST, -1)
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path(PATH)
.jsonBody(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field(XField.SERVICE_KEY.getPreferredName(), serviceKey);
builder.field(XField.EVENT_TYPE.getPreferredName(), eventType);
builder.field(XField.DESCRIPTION.getPreferredName(), description);
if (incidentKey != null) {
builder.field(XField.INCIDENT_KEY.getPreferredName(), incidentKey);
}
if (client != null) {
builder.field(XField.CLIENT.getPreferredName(), client);
}
if (clientUrl != null) {
builder.field(XField.CLIENT_URL.getPreferredName(), clientUrl);
}
if (attachPayload) {
builder.startObject(XField.DETAILS.getPreferredName());
builder.field(XField.PAYLOAD.getPreferredName());
payload.toXContent(builder, params);
builder.endObject();
}
if (contexts != null && contexts.length > 0) {
builder.startArray(IncidentEvent.XField.CONTEXT.getPreferredName());
for (IncidentEventContext context : contexts) {
context.toXContent(builder, params);
}
builder.endArray();
}
return builder;
}
})
.build();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(XField.DESCRIPTION.getPreferredName(), description);
if (incidentKey != null) {
builder.field(XField.INCIDENT_KEY.getPreferredName(), incidentKey);
}
if (client != null) {
builder.field(XField.CLIENT.getPreferredName(), client);
}
if (clientUrl != null) {
builder.field(XField.CLIENT_URL.getPreferredName(), clientUrl);
}
if (account != null) {
builder.field(XField.ACCOUNT.getPreferredName(), account);
}
builder.field(XField.ATTACH_PAYLOAD.getPreferredName(), attachPayload);
if (contexts != null) {
builder.startArray(XField.CONTEXT.getPreferredName());
for (IncidentEventContext context : contexts) {
context.toXContent(builder, params);
}
builder.endArray();
}
return builder.endObject();
}
public static Template.Builder templateBuilder(String description) {
return templateBuilder(TextTemplate.inline(description).build());
}
public static Template.Builder templateBuilder(TextTemplate description) {
return new Template.Builder(description);
}
public static class Template implements ToXContent {
final TextTemplate description;
final TextTemplate incidentKey;
final TextTemplate client;
final TextTemplate clientUrl;
final TextTemplate eventType;
public final String account;
final Boolean attachPayload;
final IncidentEventContext.Template[] contexts;
public Template(TextTemplate description, TextTemplate eventType, TextTemplate incidentKey, TextTemplate client, TextTemplate clientUrl, String account, Boolean attachPayload, IncidentEventContext.Template[] contexts) {
this.description = description;
this.eventType = eventType;
this.incidentKey = incidentKey;
this.client = client;
this.clientUrl = clientUrl;
this.account = account;
this.attachPayload = attachPayload;
this.contexts = contexts;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Template template = (Template) o;
return Objects.equals(description, template.description) &&
Objects.equals(incidentKey, template.incidentKey) &&
Objects.equals(client, template.client) &&
Objects.equals(clientUrl, template.clientUrl) &&
Objects.equals(eventType, template.eventType) &&
Objects.equals(attachPayload, template.attachPayload) &&
Objects.equals(account, template.account) &&
Arrays.equals(contexts, template.contexts);
}
@Override
public int hashCode() {
int result = Objects.hash(description, eventType, incidentKey, client, clientUrl, attachPayload, account);
result = 31 * result + Arrays.hashCode(contexts);
return result;
}
public IncidentEvent render(String watchId, String actionId, TextTemplateEngine engine, Map<String, Object> model, IncidentEventDefaults defaults) {
String description = this.description != null ? engine.render(this.description, model) : defaults.description;
String incidentKey = this.incidentKey != null ? engine.render(this.incidentKey, model) :
defaults.incidentKey != null ? defaults.incidentKey : watchId;
String client = this.client != null ? engine.render(this.client, model) : defaults.client;
String clientUrl = this.clientUrl != null ? engine.render(this.clientUrl, model) : defaults.clientUrl;
String eventType = this.eventType != null ? engine.render(this.eventType, model) : defaults.eventType;
boolean attachPayload = this.attachPayload != null ? this.attachPayload : defaults.attachPayload;
IncidentEventContext[] contexts = null;
if (this.contexts != null) {
contexts = new IncidentEventContext[this.contexts.length];
for (int i = 0; i < this.contexts.length; i++) {
contexts[i] = this.contexts[i].render(engine, model, defaults);
}
}
return new IncidentEvent(description, eventType, incidentKey, client, clientUrl, account, attachPayload, contexts);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field(XField.DESCRIPTION.getPreferredName(), description, params);
if (incidentKey != null) {
builder.field(XField.INCIDENT_KEY.getPreferredName(), incidentKey, params);
}
if (client != null) {
builder.field(XField.CLIENT.getPreferredName(), client, params);
}
if (clientUrl != null) {
builder.field(XField.CLIENT_URL.getPreferredName(), clientUrl, params);
}
if (eventType != null) {
builder.field(XField.EVENT_TYPE.getPreferredName(), eventType, params);
}
if (attachPayload != null) {
builder.field(XField.ATTACH_PAYLOAD.getPreferredName(), attachPayload);
}
if (account != null) {
builder.field(XField.ACCOUNT.getPreferredName(), account);
}
if (contexts != null) {
builder.startArray(XField.CONTEXT.getPreferredName());
for (IncidentEventContext.Template context : contexts) {
context.toXContent(builder, params);
}
builder.endArray();
}
return builder.endObject();
}
public static Template parse(String watchId, String actionId, XContentParser parser) throws IOException {
TextTemplate incidentKey = null;
TextTemplate description = null;
TextTemplate client = null;
TextTemplate clientUrl = null;
TextTemplate eventType = null;
String account = null;
Boolean attachPayload = null;
IncidentEventContext.Template[] contexts = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.INCIDENT_KEY)) {
try {
incidentKey = TextTemplate.parse(parser);
} catch (ElasticsearchParseException e) {
throw new ElasticsearchParseException("could not parse pager duty event template. failed to parse field [{}]", XField.INCIDENT_KEY.getPreferredName());
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.DESCRIPTION)) {
try {
description = TextTemplate.parse(parser);
} catch (ElasticsearchParseException e) {
throw new ElasticsearchParseException("could not parse pager duty event template. failed to parse field [{}]", XField.DESCRIPTION.getPreferredName());
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.CLIENT)) {
try {
client = TextTemplate.parse(parser);
} catch (ElasticsearchParseException e) {
throw new ElasticsearchParseException("could not parse pager duty event template. failed to parse field [{}]", XField.CLIENT.getPreferredName());
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.CLIENT_URL)) {
try {
clientUrl = TextTemplate.parse(parser);
} catch (ElasticsearchParseException e) {
throw new ElasticsearchParseException("could not parse pager duty event template. failed to parse field [{}]", XField.CLIENT_URL.getPreferredName());
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.ACCOUNT)) {
try {
account = parser.text();
} catch (ElasticsearchParseException e) {
throw new ElasticsearchParseException("could not parse pager duty event template. failed to parse field [{}]", XField.CLIENT_URL.getPreferredName());
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.EVENT_TYPE)) {
try {
eventType = TextTemplate.parse(parser);
} catch (ElasticsearchParseException e) {
throw new ElasticsearchParseException("could not parse pager duty event template. failed to parse field [{}]", XField.EVENT_TYPE.getPreferredName());
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.ATTACH_PAYLOAD)) {
if (token == XContentParser.Token.VALUE_BOOLEAN) {
attachPayload = parser.booleanValue();
} else {
throw new ElasticsearchParseException("could not parse pager duty event template. failed to parse field [{}], expected a boolean value but found [{}] instead", XField.ATTACH_PAYLOAD.getPreferredName(), token);
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.CONTEXT)) {
if (token == XContentParser.Token.START_ARRAY) {
List<IncidentEventContext.Template> list = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
try {
list.add(IncidentEventContext.Template.parse(parser));
} catch (ElasticsearchParseException e) {
throw new ElasticsearchParseException("could not parse pager duty event template. failed to parse field [{}]", XField.CONTEXT.getPreferredName());
}
}
contexts = list.toArray(new IncidentEventContext.Template[list.size()]);
}
} else {
throw new ElasticsearchParseException("could not parse pager duty event template. unexpected field [{}]", currentFieldName);
}
}
return new Template(description, eventType, incidentKey, client, clientUrl, account, attachPayload, contexts);
}
public static class Builder {
final TextTemplate description;
TextTemplate incidentKey;
TextTemplate client;
TextTemplate clientUrl;
TextTemplate eventType;
String account;
Boolean attachPayload;
List<IncidentEventContext.Template> contexts = new ArrayList<>();
public Builder(TextTemplate description) {
this.description = description;
}
public Builder setIncidentKey(TextTemplate incidentKey) {
this.incidentKey = incidentKey;
return this;
}
public Builder setClient(TextTemplate client) {
this.client = client;
return this;
}
public Builder setClientUrl(TextTemplate clientUrl) {
this.clientUrl = clientUrl;
return this;
}
public Builder setEventType(TextTemplate eventType) {
this.eventType = eventType;
return this;
}
public Builder setAccount(String account) {
this.account= account;
return this;
}
public Builder setAttachPayload(Boolean attachPayload) {
this.attachPayload = attachPayload;
return this;
}
public Builder addContext(IncidentEventContext.Template context) {
this.contexts.add(context);
return this;
}
public Template build() {
IncidentEventContext.Template[] contexts = this.contexts.isEmpty() ? null :
this.contexts.toArray(new IncidentEventContext.Template[this.contexts.size()]);
return new Template(description, eventType, incidentKey, client, clientUrl, account, attachPayload, contexts);
}
}
}
interface XField {
ParseField TYPE = new ParseField("type");
ParseField EVENT_TYPE = new ParseField("event_type");
ParseField ACCOUNT = new ParseField("account");
ParseField DESCRIPTION = new ParseField("description");
ParseField INCIDENT_KEY = new ParseField("incident_key");
ParseField CLIENT = new ParseField("client");
ParseField CLIENT_URL = new ParseField("client_url");
ParseField ATTACH_PAYLOAD = new ParseField("attach_payload");
ParseField CONTEXT = new ParseField("context");
ParseField SERVICE_KEY = new ParseField("service_key");
ParseField PAYLOAD = new ParseField("payload");
ParseField DETAILS = new ParseField("details");
}
}

View File

@ -0,0 +1,269 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/**
*
*/
public class IncidentEventContext implements ToXContent {
enum Type {
LINK, IMAGE
}
final Type type;
final String href;
final String text;
final String src;
final String alt;
public static IncidentEventContext link(String href, @Nullable String text) {
assert href != null;
return new IncidentEventContext(Type.LINK, href, text, null, null);
}
public static IncidentEventContext image(String src, @Nullable String href, @Nullable String alt) {
assert src != null;
return new IncidentEventContext(Type.IMAGE, href, null, src, alt);
}
private IncidentEventContext(Type type, String href, String text, String src, String alt) {
this.type = type;
this.href = href;
this.text = text;
this.src = src;
this.alt = alt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IncidentEventContext that = (IncidentEventContext) o;
return Objects.equals(type, that.type) && Objects.equals(href, that.href) && Objects.equals(text, that.text) && Objects.equals(src, that.src)
&& Objects.equals(alt, that.alt);
}
@Override
public int hashCode() {
return Objects.hash(type, href, text, src, alt);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(XField.TYPE.getPreferredName(), type.name().toLowerCase(Locale.ROOT));
switch (type) {
case LINK:
builder.field(XField.HREF.getPreferredName(), href);
if (text != null) {
builder.field(XField.TEXT.getPreferredName(), text);
}
break;
case IMAGE:
builder.field(XField.SRC.getPreferredName(), src);
if (href != null) {
builder.field(XField.HREF.getPreferredName(), href);
}
if (alt != null) {
builder.field(XField.ALT.getPreferredName(), alt);
}
}
return builder.endObject();
}
public static class Template implements ToXContent {
final Type type;
final TextTemplate href;
final TextTemplate text;
final TextTemplate src;
final TextTemplate alt;
public static Template link(TextTemplate href, @Nullable TextTemplate text) {
if (href == null) {
throw new IllegalStateException("could not create link context for pager duty trigger incident event. missing required [href] setting");
}
return new Template(Type.LINK, href, text, null, null);
}
public static Template image(TextTemplate src, @Nullable TextTemplate href, @Nullable TextTemplate alt) {
if (src == null) {
throw new IllegalStateException("could not create link context for pager duty trigger incident event. missing required [src] setting");
}
return new Template(Type.IMAGE, href, null, src, alt);
}
private Template(Type type, TextTemplate href, TextTemplate text, TextTemplate src, TextTemplate alt) {
this.type = type;
this.href = href;
this.text = text;
this.src = src;
this.alt = alt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Template that = (Template) o;
return Objects.equals(type, that.type) && Objects.equals(href, that.href) && Objects.equals(text, that.text) && Objects.equals(src, that.src)
&& Objects.equals(alt, that.alt);
}
@Override
public int hashCode() {
return Objects.hash(type, href, text, src, alt);
}
public IncidentEventContext render(TextTemplateEngine engine, Map<String, Object> model, IncidentEventDefaults defaults) {
switch (type) {
case LINK:
String href = this.href != null ? engine.render(this.href, model) : defaults.link.href;
String text = this.text != null ? engine.render(this.text, model) : defaults.link.text;
return IncidentEventContext.link(href, text);
default:
assert type == Type.IMAGE;
String src = this.src != null ? engine.render(this.src, model) : defaults.image.src;
href = this.href != null ? engine.render(this.href, model) : defaults.image.href;
String alt = this.alt != null ? engine.render(this.alt, model) : defaults.image.alt;
return IncidentEventContext.image(src, href, alt);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(XField.TYPE.getPreferredName(), type.name().toLowerCase(Locale.ROOT));
switch (type) {
case LINK:
builder.field(XField.HREF.getPreferredName(), href, params);
if (text != null) {
builder.field(XField.TEXT.getPreferredName(), text, params);
}
break;
case IMAGE:
builder.field(XField.SRC.getPreferredName(), src, params);
if (href != null) {
builder.field(XField.HREF.getPreferredName(), href, params);
}
if (alt != null) {
builder.field(XField.ALT.getPreferredName(), alt, params);
}
}
return builder.endObject();
}
public static Template parse(XContentParser parser) throws IOException {
Type type = null;
TextTemplate href = null;
TextTemplate text = null;
TextTemplate src = null;
TextTemplate alt = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (Strings.hasLength(currentFieldName)) {
if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.TYPE)) {
try {
type = Type.valueOf(parser.text().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
throw new ElasticsearchParseException("could not parse trigger incident event context. unknown context type [{}]", parser.text());
}
} else {
TextTemplate parsedTemplate;
try {
parsedTemplate = TextTemplate.parse(parser);
} catch (ElasticsearchParseException e) {
throw new ElasticsearchParseException("could not parse trigger incident event context. failed to parse [{}] field", e, parser.text(), currentFieldName);
}
if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.HREF)) {
href = parsedTemplate;
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.TEXT)) {
text = parsedTemplate;
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.SRC)) {
src = parsedTemplate;
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.ALT)) {
alt = parsedTemplate;
} else {
throw new ElasticsearchParseException("could not parse trigger incident event context. unknown field [{}]", currentFieldName);
}
}
}
}
return createAndValidateTemplate(type, href, src, alt, text);
}
private static Template createAndValidateTemplate(Type type, TextTemplate href, TextTemplate src, TextTemplate alt, TextTemplate text) {
if (type == null) {
throw new ElasticsearchParseException("could not parse trigger incident event context. missing required field [{}]", XField.TYPE.getPreferredName());
}
switch (type) {
case LINK:
if (href == null) {
throw new ElasticsearchParseException("could not parse trigger incident event context. missing required field [{}] for [{}] context", XField.HREF.getPreferredName(), Type.LINK.name().toLowerCase(Locale.ROOT));
}
if (src != null) {
throw new ElasticsearchParseException("could not parse trigger incident event context. unexpected field [{}] for [{}] context", XField.SRC.getPreferredName(), Type.LINK.name().toLowerCase(Locale.ROOT));
}
if (alt != null) {
throw new ElasticsearchParseException("could not parse trigger incident event context. unexpected field [{}] for [{}] context", XField.ALT.getPreferredName(), Type.LINK.name().toLowerCase(Locale.ROOT));
}
return link(href, text);
case IMAGE:
if (src == null) {
throw new ElasticsearchParseException("could not parse trigger incident event context. missing required field [{}] for [{}] context", XField.SRC.getPreferredName(), Type.IMAGE.name().toLowerCase(Locale.ROOT));
}
if (text != null) {
throw new ElasticsearchParseException("could not parse trigger incident event context. unexpected field [{}] for [{}] context", XField.TEXT.getPreferredName(), Type.IMAGE.name().toLowerCase(Locale.ROOT));
}
return image(src, href, alt);
default:
throw new ElasticsearchParseException("could not parse trigger incident event context. unknown context type [{}]", type);
}
}
}
interface XField {
ParseField TYPE = new ParseField("type");
ParseField HREF = new ParseField("href");
// "link" context fields
ParseField TEXT = new ParseField("text");
// "image" context fields
ParseField SRC = new ParseField("src");
ParseField ALT = new ParseField("alt");
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.common.settings.Settings;
import java.util.Objects;
/**
* Get trigger default configurations either from global settings or specific account settings and merge them
*/
public class IncidentEventDefaults {
final String description;
final String incidentKey;
final String client;
final String clientUrl;
final String eventType;
final boolean attachPayload;
final Context.LinkDefaults link;
final Context.ImageDefaults image;
public IncidentEventDefaults(Settings accountSettings) {
description = accountSettings.get(IncidentEvent.XField.DESCRIPTION.getPreferredName(), null);
incidentKey = accountSettings.get(IncidentEvent.XField.INCIDENT_KEY.getPreferredName(), null);
client = accountSettings.get(IncidentEvent.XField.CLIENT.getPreferredName(), null);
clientUrl = accountSettings.get(IncidentEvent.XField.CLIENT_URL.getPreferredName(), null);
eventType = accountSettings.get(IncidentEvent.XField.EVENT_TYPE.getPreferredName(), null);
attachPayload = accountSettings.getAsBoolean(IncidentEvent.XField.ATTACH_PAYLOAD.getPreferredName(), false);
link = new Context.LinkDefaults(accountSettings.getAsSettings("link"));
image = new Context.ImageDefaults(accountSettings.getAsSettings("image"));
}
static class Context {
static class LinkDefaults {
final String href;
final String text;
public LinkDefaults(Settings settings) {
href = settings.get(IncidentEventContext.XField.HREF.getPreferredName(), null);
text = settings.get(IncidentEventContext.XField.TEXT.getPreferredName(), null);
}
@Override
public int hashCode() {
return Objects.hash(href, text);
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()){
return false;
}
final LinkDefaults other = (LinkDefaults) obj;
return Objects.equals(href, other.href) && Objects.equals(text, other.text);
}
}
static class ImageDefaults {
final String href;
final String src;
final String alt;
public ImageDefaults(Settings settings) {
href = settings.get(IncidentEventContext.XField.HREF.getPreferredName(), null);
src = settings.get(IncidentEventContext.XField.SRC.getPreferredName(), null);
alt = settings.get(IncidentEventContext.XField.ALT.getPreferredName(), null);
}
@Override
public int hashCode() {
return Objects.hash(href, src, alt);
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()){
return false;
}
final ImageDefaults other = (ImageDefaults) obj;
return Objects.equals(href, other.href) && Objects.equals(src, other.src) && Objects.equals(alt, other.alt);
}
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.watcher.shield.WatcherSettingsFilter;
import org.elasticsearch.watcher.support.http.HttpClient;
/**
*
*/
public class InternalPagerDutyService extends AbstractLifecycleComponent<PagerDutyService> implements PagerDutyService {
public static final Setting<Settings> PAGERDUTY_ACCOUNT_SETTING = Setting.groupSetting("watcher.actions.pagerduty.service.", true, Setting.Scope.CLUSTER);
private final HttpClient httpClient;
private volatile PagerDutyAccounts accounts;
@Inject
public InternalPagerDutyService(Settings settings, HttpClient httpClient, ClusterSettings clusterSettings,
WatcherSettingsFilter settingsFilter) {
super(settings);
this.httpClient = httpClient;
settingsFilter.filterOut(
"watcher.actions.pagerduty.service." + PagerDutyAccount.SERVICE_KEY_SETTING,
"watcher.actions.pagerduty.service.account.*." + PagerDutyAccount.SERVICE_KEY_SETTING
);
clusterSettings.addSettingsUpdateConsumer(PAGERDUTY_ACCOUNT_SETTING, this::setPagerDutyAccountSetting);
}
@Override
protected void doStart() {
setPagerDutyAccountSetting(PAGERDUTY_ACCOUNT_SETTING.get(settings));
}
@Override
protected void doStop() {
}
@Override
protected void doClose() {
}
private void setPagerDutyAccountSetting(Settings settings) {
accounts = new PagerDutyAccounts(settings, httpClient, logger);
}
@Override
public PagerDutyAccount getDefaultAccount() {
return accounts.account(null);
}
@Override
public PagerDutyAccount getAccount(String name) {
return accounts.account(name);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.HttpResponse;
import org.elasticsearch.watcher.watch.Payload;
import java.io.IOException;
/**
*
*/
public class PagerDutyAccount {
public static final String SERVICE_KEY_SETTING = "service_api_key";
public static final String TRIGGER_DEFAULTS_SETTING = "event_defaults";
final String name;
final String serviceKey;
final HttpClient httpClient;
final IncidentEventDefaults eventDefaults;
final ESLogger logger;
public PagerDutyAccount(String name, Settings accountSettings, Settings serviceSettings, HttpClient httpClient, ESLogger logger) {
this.name = name;
this.serviceKey = accountSettings.get(SERVICE_KEY_SETTING, serviceSettings.get(SERVICE_KEY_SETTING, null));
if (this.serviceKey == null) {
throw new SettingsException("invalid pagerduty account [" + name + "]. missing required [" + SERVICE_KEY_SETTING + "] setting");
}
this.httpClient = httpClient;
this.eventDefaults = new IncidentEventDefaults(accountSettings.getAsSettings(TRIGGER_DEFAULTS_SETTING));
this.logger = logger;
}
public String getName() {
return name;
}
public IncidentEventDefaults getDefaults() {
return eventDefaults;
}
public SentEvent send(IncidentEvent event, Payload payload) throws IOException {
HttpRequest request = event.createRequest(serviceKey, payload);
HttpResponse response = httpClient.execute(request);
return SentEvent.responded(event, request, response);
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.watcher.support.http.HttpClient;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class PagerDutyAccounts {
private final Map<String, PagerDutyAccount> accounts;
private final String defaultAccountName;
public PagerDutyAccounts(Settings serviceSettings, HttpClient httpClient, ESLogger logger) {
Settings accountsSettings = serviceSettings.getAsSettings("account");
accounts = new HashMap<>();
for (String name : accountsSettings.names()) {
Settings accountSettings = accountsSettings.getAsSettings(name);
PagerDutyAccount account = new PagerDutyAccount(name, accountSettings, serviceSettings, httpClient, logger);
accounts.put(name, account);
}
String defaultAccountName = serviceSettings.get("default_account");
if (defaultAccountName == null) {
if (accounts.isEmpty()) {
this.defaultAccountName = null;
} else {
PagerDutyAccount account = accounts.values().iterator().next();
logger.info("default pager duty account set to [{}]", account.name);
this.defaultAccountName = account.name;
}
} else if (!accounts.containsKey(defaultAccountName)) {
throw new SettingsException("could not find default pagerduty account [" + defaultAccountName + "]");
} else {
this.defaultAccountName = defaultAccountName;
}
}
/**
* Returns the account associated with the given name. If there is not such account, {@code null} is returned.
* If the given name is {@code null}, the default account will be returned.
*
* @param name The name of the requested account
* @return The account associated with the given name, or {@code null} when requested an unknown account.
* @throws IllegalStateException if the name is null and the default account is null.
*/
public PagerDutyAccount account(String name) throws IllegalStateException {
if (name == null) {
if (defaultAccountName == null) {
throw new IllegalStateException("cannot find default pagerduty account as no accounts have been configured");
}
name = defaultAccountName;
}
return accounts.get(name);
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.common.component.LifecycleComponent;
/**
*
*/
public interface PagerDutyService extends LifecycleComponent<PagerDutyService> {
PagerDutyAccount getDefaultAccount();
PagerDutyAccount getAccount(String accountName);
}

View File

@ -0,0 +1,161 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.watcher.actions.pagerduty.PagerDutyAction;
import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.HttpResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
*
*/
public class SentEvent implements ToXContent {
final IncidentEvent event;
final @Nullable HttpRequest request;
final @Nullable HttpResponse response;
final @Nullable String failureReason;
public static SentEvent responded(IncidentEvent event, HttpRequest request, HttpResponse response) {
String failureReason = resolveFailureReason(response);
return new SentEvent(event, request, response, failureReason);
}
public static SentEvent error(IncidentEvent event, String reason) {
return new SentEvent(event, null, null, reason);
}
private SentEvent(IncidentEvent event, HttpRequest request, HttpResponse response, String failureReason) {
this.event = event;
this.request = request;
this.response = response;
this.failureReason = failureReason;
}
public boolean successful() {
return failureReason == null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SentEvent sentEvent = (SentEvent) o;
return Objects.equals(event, sentEvent.event) && Objects.equals(request, sentEvent.request) && Objects.equals(failureReason, sentEvent.failureReason);
}
@Override
public int hashCode() {
return Objects.hash(event, request, response, failureReason);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(XField.EVENT.getPreferredName(), event, params);
if (!successful()) {
builder.field(XField.REASON.getPreferredName(), failureReason);
if (request != null) {
builder.field(XField.REQUEST.getPreferredName(), request, params);
}
if (response != null) {
builder.field(XField.RESPONSE.getPreferredName(), response, params);
}
}
return builder.endObject();
}
private static String resolveFailureReason(HttpResponse response) {
// if for some reason we failed to parse the body, lets fall back on the http status code.
int status = response.status();
if (status < 300) {
return null;
}
// ok... we have an error
// lets first try to parse the error response in the body
// based on https://developer.pagerduty.com/documentation/rest/errors
try {
XContentParser parser = JsonXContent.jsonXContent.createParser(response.body());
parser.nextToken();
String message = null;
List<String> errors = new ArrayList<>();
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.MESSAGE)) {
message = parser.text();
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.CODE)) {
// we don't use this code.. so just consume the token
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, XField.ERRORS)) {
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
errors.add(parser.text());
}
} else {
throw new ElasticsearchParseException("could not parse pagerduty event response. unexpected field [{}]", currentFieldName);
}
}
StringBuilder sb = new StringBuilder();
if (message != null) {
sb.append(message);
}
if (!errors.isEmpty()) {
sb.append(":");
for (String error : errors) {
sb.append(" ").append(error).append(".");
}
}
return sb.toString();
} catch (Exception e) {
// too bad... we couldn't parse the body... note that we don't log this error as there's no real
// need for it. This whole error parsing is a nice to have, nothing more. On error, the http
// response object is anyway added to the action result in the watch record (though not searchable)
}
switch (status) {
case 400: return "Bad Request";
case 401: return "Unauthorized. The account service api key is invalid.";
case 403: return "Forbidden. The account doesn't have permission to send this trigger.";
case 404: return "The account used invalid HipChat APIs";
case 408: return "Request Timeout. The request took too long to process.";
case 500: return "PagerDuty Server Error. Internal error occurred while processing request.";
default:
return "Unknown Error";
}
}
public interface XField {
ParseField EVENT = PagerDutyAction.XField.EVENT;
ParseField REASON = new ParseField("reason");
ParseField REQUEST = new ParseField("request");
ParseField RESPONSE = new ParseField("response");
ParseField MESSAGE = new ParseField("message");
ParseField CODE = new ParseField("code");
ParseField ERRORS = new ParseField("errors");
}
}

View File

@ -56,6 +56,4 @@ public class InternalSlackService extends AbstractLifecycleComponent<SlackServic
public SlackAccount getAccount(String name) {
return accounts.account(name);
}
}

View File

@ -451,6 +451,87 @@
}
}
}
},
"pagerduty" : {
"type": "object",
"dynamic": true,
"properties": {
"account": {
"type": "string",
"index": "not_analyzed"
},
"sent_event": {
"type": "nested",
"include_in_parent": true,
"dynamic": true,
"properties": {
"reason": {
"type": "string"
},
"request" : {
"type" : "object",
"enabled" : false
},
"response" : {
"type" : "object",
"enabled" : false
},
"event" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"type" : {
"type" : "string",
"index" : "not_analyzed"
},
"client" : {
"type" : "string"
},
"client_url" : {
"index" : "not_analyzed",
"type" : "string"
},
"account" : {
"index" : "not_analyzed",
"type" : "string"
},
"attach_payload" : {
"type" : "boolean"
},
"incident_key" : {
"index" : "not_analyzed",
"type" : "string"
},
"description" : {
"type" : "string"
},
"context" : {
"type" : "nested",
"include_in_parent": true,
"dynamic" : true,
"properties" : {
"type" : {
"type" : "string",
"index" : "not_analyzed"
},
"href" : {
"type" : "string",
"index" : "not_analyzed"
},
"src" : {
"type" : "string",
"index" : "not_analyzed"
},
"alt" : {
"type" : "string"
}
}
}
}
}
}
}
}
}
}
}
@ -463,4 +544,4 @@
}
}
}
}
}

View File

@ -43,12 +43,15 @@ public class WatcherF {
settings.put("watcher.actions.hipchat.service.account.user.profile", "user");
settings.put("watcher.actions.hipchat.service.account.user.auth_token", "FYVx16oDH78ZW9r13wtXbcszyoyA7oX5tiMWg9X0");
// this is for the `test-watcher-v1` notification token
// this is for the `test-watcher-v1` notification token (hipchat)
settings.put("watcher.actions.hipchat.service.account.v1.profile", "v1");
settings.put("watcher.actions.hipchat.service.account.v1.auth_token", "a734baf62df618b96dda55b323fc30");
// this is for our test slack incoming webhook (under elasticsearch team)
System.setProperty("es.watcher.actions.slack.service.account.a1.url", "https://hooks.slack.com/services/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");
System.setProperty("es.watcher.actions.pagerduty.service.account.service1.service_api_key", "fc082467005d4072a914e0bb041882d0");
final CountDownLatch latch = new CountDownLatch(1);
final Node node = new MockNode(settings.build(), Version.CURRENT, Arrays.asList(XPackPlugin.class, XPackPlugin.class));
Runtime.getRuntime().addShutdownHook(new Thread() {

View File

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyAccount;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyService;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.junit.Before;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.watcher.actions.ActionBuilders.triggerPagerDutyAction;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class PagerDutyActionFactoryTests extends ESTestCase {
private PagerDutyActionFactory factory;
private PagerDutyService service;
@Before
public void init() throws Exception {
service = mock(PagerDutyService.class);
factory = new PagerDutyActionFactory(Settings.EMPTY, mock(TextTemplateEngine.class), service);
}
public void testParseAction() throws Exception {
PagerDutyAccount account = mock(PagerDutyAccount.class);
when(service.getAccount("_account1")).thenReturn(account);
PagerDutyAction action = triggerPagerDutyAction("_account1", "_description").build();
XContentBuilder jsonBuilder = jsonBuilder().value(action);
XContentParser parser = JsonXContent.jsonXContent.createParser(jsonBuilder.bytes());
parser.nextToken();
PagerDutyAction parsedAction = factory.parseAction("_w1", "_a1", parser);
assertThat(parsedAction, is(action));
}
public void testParseActionUnknownAccount() throws Exception {
try {
when(service.getAccount("_unknown")).thenReturn(null);
PagerDutyAction action = triggerPagerDutyAction("_unknown", "_body").build();
XContentBuilder jsonBuilder = jsonBuilder().value(action);
XContentParser parser = JsonXContent.jsonXContent.createParser(jsonBuilder.bytes());
parser.nextToken();
factory.parseAction("_w1", "_a1", parser);
fail("Expected ElasticsearchParseException due to unknown account");
} catch (ElasticsearchParseException e) {}
}
}

View File

@ -0,0 +1,243 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.pagerduty.service.IncidentEvent;
import org.elasticsearch.watcher.actions.pagerduty.service.IncidentEventContext;
import org.elasticsearch.watcher.actions.pagerduty.service.IncidentEventDefaults;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyAccount;
import org.elasticsearch.watcher.actions.pagerduty.service.PagerDutyService;
import org.elasticsearch.watcher.actions.pagerduty.service.SentEvent;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.execution.Wid;
import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.HttpResponse;
import org.elasticsearch.watcher.support.text.TextTemplate;
import org.elasticsearch.watcher.support.text.TextTemplateEngine;
import org.elasticsearch.watcher.watch.Payload;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Before;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.watcher.actions.ActionBuilders.pagerDutyAction;
import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContextBuilder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class PagerDutyActionTests extends ESTestCase {
private PagerDutyService service;
@Before
public void init() throws Exception {
service = mock(PagerDutyService.class);
}
public void testExecute() throws Exception {
final String accountName = "account1";
TextTemplateEngine templateEngine = mock(TextTemplateEngine.class);
TextTemplate description = TextTemplate.inline("_description").build();
IncidentEvent.Template.Builder eventBuilder = new IncidentEvent.Template.Builder(description);
boolean attachPayload = randomBoolean();
eventBuilder.setAttachPayload(attachPayload);
eventBuilder.setAccount(accountName);
IncidentEvent.Template eventTemplate = eventBuilder.build();
PagerDutyAction action = new PagerDutyAction(eventTemplate);
ExecutablePagerDutyAction executable = new ExecutablePagerDutyAction(action, logger, service, templateEngine);
Map<String, Object> data = new HashMap<>();
Payload payload = new Payload.Simple(data);
Map<String, Object> metadata = MapBuilder.<String, Object>newMapBuilder().put("_key", "_val").map();
DateTime now = DateTime.now(DateTimeZone.UTC);
Wid wid = new Wid(randomAsciiOfLength(5), randomLong(), now);
WatchExecutionContext ctx = mockExecutionContextBuilder(wid.watchId())
.wid(wid)
.payload(payload)
.time(wid.watchId(), now)
.metadata(metadata)
.buildMock();
Map<String, Object> ctxModel = new HashMap<>();
ctxModel.put("id", ctx.id().value());
ctxModel.put("watch_id", wid.watchId());
ctxModel.put("payload", data);
ctxModel.put("metadata", metadata);
ctxModel.put("execution_time", now);
Map<String, Object> triggerModel = new HashMap<>();
triggerModel.put("triggered_time", now);
triggerModel.put("scheduled_time", now);
ctxModel.put("trigger", triggerModel);
ctxModel.put("vars", Collections.emptyMap());
Map<String, Object> expectedModel = new HashMap<>();
expectedModel.put("ctx", ctxModel);
when(templateEngine.render(description, expectedModel)).thenReturn(description.getTemplate());
IncidentEvent event = new IncidentEvent(description.getTemplate(), null, wid.watchId(), null, null, accountName, attachPayload, null);
PagerDutyAccount account = mock(PagerDutyAccount.class);
when(account.getDefaults()).thenReturn(new IncidentEventDefaults(Settings.EMPTY));
HttpResponse response = mock(HttpResponse.class);
when(response.status()).thenReturn(200);
HttpRequest request = mock(HttpRequest.class);
SentEvent sentEvent = SentEvent.responded(event, request, response);
when(account.send(event, payload)).thenReturn(sentEvent);
when(service.getAccount(accountName)).thenReturn(account);
Action.Result result = executable.execute("_id", ctx, payload);
assertThat(result, notNullValue());
assertThat(result, instanceOf(PagerDutyAction.Result.Executed.class));
assertThat(result.status(), equalTo(Action.Result.Status.SUCCESS));
assertThat(((PagerDutyAction.Result.Executed) result).sentEvent(), sameInstance(sentEvent));
}
public void testParser() throws Exception {
XContentBuilder builder = jsonBuilder().startObject();
String accountName = randomAsciiOfLength(10);
builder.field("account", accountName);
TextTemplate incidentKey = null;
if (randomBoolean()) {
incidentKey = TextTemplate.inline("_incident_key").build();
builder.field("incident_key", incidentKey);
}
TextTemplate description = null;
if (randomBoolean()) {
description = TextTemplate.inline("_description").build();
builder.field("description", description);
}
TextTemplate client = null;
if (randomBoolean()) {
client = TextTemplate.inline("_client").build();
builder.field("client", client);
}
TextTemplate clientUrl = null;
if (randomBoolean()) {
clientUrl = TextTemplate.inline("_client_url").build();
builder.field("client_url", clientUrl);
}
TextTemplate eventType = null;
if (randomBoolean()) {
eventType = TextTemplate.inline(randomFrom("trigger", "resolve", "acknowledge")).build();
builder.field("eventType", eventType);
}
Boolean attachPayload = randomBoolean() ? null : randomBoolean();
if (attachPayload != null) {
builder.field("attach_payload", attachPayload.booleanValue());
}
IncidentEventContext.Template[] contexts = null;
if (randomBoolean()) {
contexts = new IncidentEventContext.Template[] {
IncidentEventContext.Template.link(TextTemplate.inline("_href").build(), TextTemplate.inline("_text").build()),
IncidentEventContext.Template.image(TextTemplate.inline("_src").build(), TextTemplate.inline("_href").build(), TextTemplate.inline("_alt").build())
};
builder.array("context", (Object) contexts);
}
builder.endObject();
BytesReference bytes = builder.bytes();
logger.info("pagerduty action json [{}]", bytes.toUtf8());
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
PagerDutyAction action = PagerDutyAction.parse("_watch", "_action", parser);
assertThat(action, notNullValue());
assertThat(action.event.account, is(accountName));
assertThat(action.event, notNullValue());
assertThat(action.event, instanceOf(IncidentEvent.Template.class));
assertThat(action.event, is(new IncidentEvent.Template(description, eventType, incidentKey, client, clientUrl, accountName, attachPayload, contexts)));
}
public void testParserSelfGenerated() throws Exception {
IncidentEvent.Template.Builder event = IncidentEvent.templateBuilder(randomAsciiOfLength(50));
if (randomBoolean()) {
event.setIncidentKey(TextTemplate.inline(randomAsciiOfLength(50)).build());
}
if (randomBoolean()) {
event.setClient(TextTemplate.inline(randomAsciiOfLength(50)).build());
}
if (randomBoolean()) {
event.setClientUrl(TextTemplate.inline(randomAsciiOfLength(50)).build());
}
if (randomBoolean()) {
event.setAttachPayload(randomBoolean());
}
if (randomBoolean()) {
event.addContext(IncidentEventContext.Template.link(TextTemplate.inline("_href").build(), TextTemplate.inline("_text").build()));
}
if (randomBoolean()) {
event.addContext(IncidentEventContext.Template.image(TextTemplate.inline("_src").build(), TextTemplate.inline("_href").build(), TextTemplate.inline("_alt").build()));
}
if (randomBoolean()) {
event.setEventType(TextTemplate.inline(randomAsciiOfLength(50)).build());
}
if (randomBoolean()) {
event.setAccount(randomAsciiOfLength(50)).build();
}
PagerDutyAction action = pagerDutyAction(event).build();
XContentBuilder jsonBuilder = jsonBuilder();
action.toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS);
XContentParser parser = JsonXContent.jsonXContent.createParser(jsonBuilder.bytes());
parser.nextToken();
PagerDutyAction parsedAction = PagerDutyAction.parse("_w1", "_a1", parser);
assertThat(parsedAction, notNullValue());
assertThat(parsedAction, is(action));
}
public void testParserInvalid() throws Exception {
try {
XContentBuilder builder = jsonBuilder().startObject().field("unknown_field", "value");
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
PagerDutyAction.parse("_watch", "_action", parser);
fail("Expected ElasticsearchParseException but did not happen");
} catch (ElasticsearchParseException e) {
}
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.is;
/**
*
*/
public class IncidentEventDefaultsTests extends ESTestCase {
public void testConstructor() throws Exception {
Settings settings = randomSettings();
IncidentEventDefaults defaults = new IncidentEventDefaults(settings);
assertThat(defaults.incidentKey, is(settings.get("incident_key", null)));
assertThat(defaults.description, is(settings.get("description", null)));
assertThat(defaults.clientUrl, is(settings.get("client_url", null)));
assertThat(defaults.client, is(settings.get("client", null)));
assertThat(defaults.eventType, is(settings.get("event_type", null)));
assertThat(defaults.attachPayload, is(settings.getAsBoolean("attach_payload", false)));
if (settings.getAsSettings("link").names().isEmpty()) {
IncidentEventDefaults.Context.LinkDefaults linkDefaults = new IncidentEventDefaults.Context.LinkDefaults(Settings.EMPTY);
assertThat(defaults.link, is(linkDefaults));
} else {
assertThat(defaults.link, notNullValue());
assertThat(defaults.link.href, is(settings.get("link.href", null)));
assertThat(defaults.link.text, is(settings.get("link.text", null)));
}
if (settings.getAsSettings("image").names().isEmpty()) {
IncidentEventDefaults.Context.ImageDefaults imageDefaults = new IncidentEventDefaults.Context.ImageDefaults(Settings.EMPTY);
assertThat(defaults.image, is(imageDefaults));
} else {
assertThat(defaults.image, notNullValue());
assertThat(defaults.image.href, is(settings.get("image.href", null)));
assertThat(defaults.image.alt, is(settings.get("image.alt", null)));
assertThat(defaults.image.src, is(settings.get("image.src", null)));
}
}
public static Settings randomSettings() {
Settings.Builder settings = Settings.builder();
if (randomBoolean()) {
settings.put("from", randomAsciiOfLength(10));
}
if (randomBoolean()) {
String[] to = new String[randomIntBetween(1, 3)];
for (int i = 0; i < to.length; i++) {
to[i] = randomAsciiOfLength(10);
}
settings.putArray("to", to);
}
if (randomBoolean()) {
settings.put("text", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("event_type", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("icon", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.fallback", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.color", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.pretext", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.author_name", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.author_link", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.author_icon", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.title", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.title_link", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.text", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.image_url", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.thumb_url", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.field.title", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.field.value", randomAsciiOfLength(10));
}
if (randomBoolean()) {
settings.put("attachment.field.short", randomBoolean());
}
return settings.build();
}
}

View File

@ -0,0 +1,133 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.actions.slack.service.message.SlackMessageDefaultsTests;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.junit.Before;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.isOneOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
/**
*
*/
public class PagerDutyAccountsTests extends ESTestCase {
private HttpClient httpClient;
@Before
public void init() throws Exception {
httpClient = mock(HttpClient.class);
}
public void testSingleAccount() throws Exception {
Settings.Builder builder = Settings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
PagerDutyAccounts accounts = new PagerDutyAccounts(builder.build(), httpClient, logger);
PagerDutyAccount account = accounts.account("account1");
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account1"));
account = accounts.account(null); // falling back on the default
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account1"));
}
public void testSingleAccountNoExplicitDefault() throws Exception {
Settings.Builder builder = Settings.builder();
addAccountSettings("account1", builder);
PagerDutyAccounts accounts = new PagerDutyAccounts(builder.build(), httpClient, logger);
PagerDutyAccount account = accounts.account("account1");
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account1"));
account = accounts.account(null); // falling back on the default
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account1"));
}
public void testMultipleAccounts() throws Exception {
Settings.Builder builder = Settings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
PagerDutyAccounts accounts = new PagerDutyAccounts(builder.build(), httpClient, logger);
PagerDutyAccount account = accounts.account("account1");
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account1"));
account = accounts.account("account2");
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account2"));
account = accounts.account(null); // falling back on the default
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account1"));
}
public void testMultipleAccounts_NoExplicitDefault() throws Exception {
Settings.Builder builder = Settings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
PagerDutyAccounts accounts = new PagerDutyAccounts(builder.build(), httpClient, logger);
PagerDutyAccount account = accounts.account("account1");
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account1"));
account = accounts.account("account2");
assertThat(account, notNullValue());
assertThat(account.name, equalTo("account2"));
account = accounts.account(null);
assertThat(account, notNullValue());
assertThat(account.name, isOneOf("account1", "account2"));
}
public void testMultipleAccounts_UnknownDefault() throws Exception {
try {
Settings.Builder builder = Settings.builder()
.put("default_account", "unknown");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
new PagerDutyAccounts(builder.build(), httpClient, logger);
fail("Expected a SettingsException to happen");
} catch (SettingsException e) {}
}
public void testNoAccount() throws Exception {
try {
Settings.Builder builder = Settings.builder();
PagerDutyAccounts accounts = new PagerDutyAccounts(builder.build(), httpClient, logger);
accounts.account(null);
fail("no accounts are configured so trying to get the default account should throw an IllegalStateException");
} catch (IllegalStateException e) {}
}
public void testNoAccount_WithDefaultAccount() throws Exception {
try {
Settings.Builder builder = Settings.builder()
.put("default_account", "unknown");
new PagerDutyAccounts(builder.build(), httpClient, logger);
fail("Expected a SettingsException to happen");
} catch (SettingsException e) {}
}
private void addAccountSettings(String name, Settings.Builder builder) {
builder.put("account." + name + ".service_api_key", randomAsciiOfLength(50));
Settings defaults = SlackMessageDefaultsTests.randomSettings();
for (Map.Entry<String, String> setting : defaults.getAsMap().entrySet()) {
builder.put("message_defaults." + setting.getKey(), setting.getValue());
}
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.watcher.actions.pagerduty.service;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.junit.annotations.Network;
import org.elasticsearch.watcher.actions.pagerduty.PagerDutyAction;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import org.elasticsearch.watcher.watch.Payload;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.watcher.actions.ActionBuilders.pagerDutyAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
import static org.elasticsearch.watcher.condition.ConditionBuilders.alwaysCondition;
import static org.elasticsearch.watcher.input.InputBuilders.simpleInput;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.interval;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.notNullValue;
/**
*
*/
@Network
public class PagerDutyServiceTests extends AbstractWatcherIntegrationTestCase {
@Override
protected boolean timeWarped() {
return true;
}
@Override
protected boolean enableShield() {
return false;
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put("watcher.actions.pagerduty.service.account.test_account.service_api_key", "fc082467005d4072a914e0bb041882d0")
.build();
}
public void testSendTriggerEvent() throws Exception {
PagerDutyService service = getInstanceFromMaster(PagerDutyService.class);
IncidentEvent event = new IncidentEvent("#testIncidentEvent()", null, null, "PagerDutyServiceTests", "_client_url", "_account", true, new IncidentEventContext[] {
IncidentEventContext.link("_href", "_text"),
IncidentEventContext.image("_src", "_href", "_alt")
});
Payload payload = new Payload.Simple("_key", "_val");
PagerDutyAccount account = service.getAccount("test_account");
assertThat(account, notNullValue());
SentEvent sentEvent = account.send(event, payload);
assertThat(sentEvent, notNullValue());
assertThat(sentEvent.successful(), is(true));
assertThat(sentEvent.request, notNullValue());
assertThat(sentEvent.response, notNullValue());
assertThat(sentEvent.response.status(), lessThan(300));
}
public void testWatchWithPagerDutyAction() throws Exception {
String account = "test_account";
PagerDutyAction.Builder actionBuilder = pagerDutyAction(IncidentEvent
.templateBuilder("pager duty integration test `{{ctx.payload.ref}}`").setAccount(account));
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("1").setSource(watchBuilder()
.trigger(schedule(interval("10m")))
.input(simpleInput("ref", "testWatchWithPagerDutyAction()"))
.condition(alwaysCondition())
.addAction("pd", actionBuilder))
.execute().get();
assertThat(putWatchResponse.isCreated(), is(true));
timeWarp().scheduler().trigger("1");
flush();
refresh();
assertWatchWithMinimumPerformedActionsCount("1", 1L, false);
SearchResponse response = searchHistory(searchSource().query(boolQuery()
.must(termQuery("result.actions.id", "pd"))
.must(termQuery("result.actions.type", "pagerduty"))
.must(termQuery("result.actions.status", "success"))
.must(termQuery("result.actions.pagerduty.sent_event.event.account", account))));
assertThat(response, notNullValue());
assertHitCount(response, 1L);
}
}