Introducing HipChat Action

An action capable of sending notifications to rooms and users on hipchat. This actions support three types of HipChat APIs:

- `v1` - The (now deprecated) legacy API where a token can be registered at the group level, and the `v1` version of the API can be used. This API only supports room notification (users cannot be notified). multi-room notification is supported.

- `integration` - The basic integration that one can create in HipChat (it is using the `v2` API version), where notifications can be sent to a single room. User notification is unsupported by this API

- `user` - this API uses an API token of a specific user. An admin user can create an API token and configure it to have access to room notification and user private messaging. This API supports multi-room and multi-user notifications.

The settings for `hipchat` are very similar to the `email` infrastructure in nature. It is possible to configure multiple/different hipchat account, each is associated with the api type (a.k.a profile) - can be `v1`, `integration` or `user`, and the respective `auth_token`. When configuring the action in the watch, one can specify what hipchat account they would like to use (when not specifying an account, the `default_account` will be used). Each account can also specify its own unique `host`/`port` for the hipchat server - for full flexibility.

Closes elastic/elasticsearch#462

Original commit: elastic/x-pack-elasticsearch@9d9ee13542
This commit is contained in:
uboness 2015-08-18 13:13:42 +02:00
parent 78a70b5f65
commit 0731a98e97
38 changed files with 4176 additions and 48 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View File

@ -157,9 +157,9 @@ during its execution:
image::images/action-throttling.jpg[align="center"] image::images/action-throttling.jpg[align="center"]
Watcher supports four action types: <<actions-email, Email>>, Watcher supports five action types: <<actions-email, Email>>,
<<actions-webhook, Webhook>>, <<actions-index, Index>> and <<actions-webhook, Webhook>>, <<actions-index, Index>>,
<<actions-logging, Logging>>. <<actions-logging, Logging>> and <<actions-hipchat, HipChat>>
include::actions/email.asciidoc[] include::actions/email.asciidoc[]
@ -167,4 +167,6 @@ include::actions/webhook.asciidoc[]
include::actions/index.asciidoc[] include::actions/index.asciidoc[]
include::actions/logging.asciidoc[] include::actions/logging.asciidoc[]
include::actions/hipchat.asciidoc[]

View File

@ -0,0 +1,235 @@
[[actions-hipchat]]
==== HipChat Action
A watch <<actions, action>> that can connect to a https://www.hipchat.com[HipChat] server and send
messages to users and rooms of a specific group.
[[configuring-hipchat-actions]]
===== Configuring HipChat Actions
You configure hipchat actions in a watch's `actions` array. Action-specific attributes are
specified using the `hipchat` keyword.
The following snippet shows a simple hipchat action definition:
[source,json]
--------------------------------------------------
"actions" : {
"notify-hipchat" : { <1>
"transform" : { ... }, <2>
"throttle_period" : "5m", <3>
"hipchat" : {
"to" : {
"room" : "server-status" <4>
},
"message" : "Encountered {{ctx.payload.hits.total}} errors in the last 5 minutes (facepalm)" <5>
}
}
}
--------------------------------------------------
<1> The id of the action
<2> An optional <<transform, transform>> to transform the payload before executing the `webhook` action
<3> An optional <<actions-ack-throttle, throttle period>> for the action (5 minutes in this example)
<4> The room where the message is sent to
<5> The content of the message.
HipChat provides an extensive set of APIs via which Watcher sends messages to users and rooms. These APIs
are exposed via different integration mechanism. Watcher refers to these as *Profiles*, each can be identified
with its own unique name: `v1`, `integration` and `user`.
Different profiles support different features and require different set of configuration (both on watcher
side and on the HipChat server side).
Before using the `hipchat` action in a watch, Watcher's internal HipChat service needs to be configured. This
Service enable the configuration of multiple HipChat accounts. An HipChat Account defines the following:
* `name` - (required) uniquely identifies the account. HipChat actions may specify the name of the account with which
the messages should be sent.
* `profile` - (required) the profile that is associated with this account, effectively defining what APIs this account uses.
* `auth_token` - (required) the authentication token that is used to execute the HipChat API in the account.
* `host` - (optional) defines the host of the HipChat server. When not defined it fall back on the default host (see bellow)
* `port` - (optional) defines the port of the HipChat server. When not defined it fall back on the default port (see bellow)
* `message_defaults` - (optional) a set of settings to define the default settings of the messages that are sent via this account
* `room` - Some account are bound to a single room (messages that are sent using their associated profiles can only be
sent to a specific room). For those account, this setting defines the room the account is bound to.
Here's an example settings for HipChat service:
[source,yaml]
--------------------------------------------------
watcher.actions.hipchat:
default_account: v1
account:
account1:
profile: v1
auth_token: XXXXXXXXX
message_default:
color: yello
message_format: text
account2:
profile: integration
auth_token: YYYYYYYYY
room: mission-control
message_default:
color: red
message_format: text
--------------------------------------------------
[[hipchat-api-v1]]
===== `v1` Account
WARNING: This account uses a deprecated API and is expected to be removed by HipChat in the future.
The `v1` API was the first API HipChat ever exposed and therefore the most commonly used one. It is also the simplest
one to set up. To create the `v1` API token, please follow the instructions listed on https://www.hipchat.com/docs/api.
NOTE: User private messages are not supported by this API. If private messages is what you are after, please
consider the <<`user`>> API instead.
Once the an API is created, add the following settings in `elasticsearch.yml` file:
The following table lists the available fields when setting up the `hipchat` action for the `v1` API:
[[hipchat-api-v1-action-attributes]]
.v1 API Action Attributes
[options="header"]
|======
| Name |Required | Default | Description
| `from` | no | the watch id | The name that will appear as the sender of the notification
| `room` | yes | - | The room/s that the notification should go to. Accepts a string value or an array of string values.
| `message` | yes | - | The content of the notification message (size is limited by HipChat to 1000 characters)
| `message_format` | no | html* | The format of the message. Possible options are: `text` or `html`
| `color` | no | yellow* | The background color of the notification in the room
| `notify` | no | false | Indicates whether people in the room should be actively notified
|======
Here is an example for how it looks like as part of the action definition:
[source,json]
--------------------------------------------------
"actions" : {
"notify-hipchat" : {
"transform" : { ... },
"throttle_period" : "5m",
"hipchat" : {
"account" : "v1-account",
"message" : {
"from" : "watcher",
"room" : [ "server-status", "infra-team" ],
"message" : "Encountered {{ctx.payload.hits.total}} errors in the last 5 minutes (facepalm)",
"message_format" : "text",
"color" : "red",
"notify" : true
}
}
}
}
--------------------------------------------------
[[hipchat-api-integration]]
===== `integration` Accounts
This profiles uses HipChat https://www.hipchat.com/docs/apiv2/addons[Integrations]. More specifically,
it uses a built-in integration in HipChat that enables external systems to send notifications to a
specific room. To create the `integration` API token:
* For HipChat.com, please follow the "Build your own integration" https://www.hipchat.com/docs/apiv2[instructions]
* For HipChat Server, please follow the "Build your own integration" https://confluence.atlassian.com/hc/administering-hipchat-server/integrations-with-hipchat-server[instructions]
In both cases, the api token can be copied from the listed example (marked in red bellow)
image:images/hipchat-integration-example.png[]
NOTE: This API is the most limited APIs of the three as it only supports sending notifications to a single room and
does not support user private messages. If you are looking for multi-room notifications, please consider either
the <<hipchat-api-v1, `v1`>> or <<hipchat-api-user, `user`>> APIs. Only the latter supports user private
messages.
When creating an account with the `integration` profile, you must configure the `room` setting as part
of the account setting.
The following table lists the available fields when setting up the `hipchat` action an `integration` account:
[[hipchat-api-integration-action-attributes]]
.v1 API Action Attributes
[options="header"]
|======
| Name |Required | Default | Description
| `message` | yes | - | The content of the notification message (size is limited by HipChat to 1000 characters)
| `message_format` | no | html* | The format of the message. Possible options are: `text` or `html`
| `color` | no | yellow* | The background color of the notification in the room
| `notify` | no | false | Indicates whether people in the room should be actively notified
|======
Here is an example for how it looks like as part of the action definition:
[source,json]
--------------------------------------------------
"actions" : {
"notify-hipchat" : {
"transform" : { ... },
"throttle_period" : "5m",
"hipchat" : {
"account" : "integration-account",
"message" : {
"message" : "Encountered {{ctx.payload.hits.total}} errors in the last 5 minutes (facepalm)",
"message_format" : "text",
"color" : "red",
"notify" : true
}
}
}
}
--------------------------------------------------
[[hipchat-api-user]]
===== `user` Accounts
The `user` API is arguably the most flexible API. It is also safe to use as it and is based on HipChat's `v2` API version.
To use this API you will require to add a new HipChat user. With this the user in place, all messages sent via this
account will be sent on this user behalf (make sure you name the user appropriately). After creating the user, you need
to create an API token for it. To create a user token please follow the instructions on HipChat's online documentation.
////
TODO: could not find a good link for that... we might need to show screenshots of the UI
////
While not supported by `v1` and `integration` accounts, the `user` account enables private user notification.
The following table lists the available fields when setting up the `hipchat` action for the `user` API:
[[hipchat-api-user-action-attributes]]
.v1 API Action Attributes
[options="header"]
|======
| Name |Required | Default | Description
| `message` | yes | - | The content of the notification message (size is limited by HipChat to 1000 characters)
| `message_format` | no | html* | The format of the message. Possible options are: `text` or `html`
| `color` | no | yellow* | The background color of the notification in the room
| `notify` | no | false | Indicates whether people in the room should be actively notified
|======
Here is an example for how it looks like as part of the action definition:
[source,json]
--------------------------------------------------
"actions" : {
"notify-hipchat" : {
"transform" : { ... },
"throttle_period" : "5m",
"hipchat" : {
"account" : "integration-account",
"message" : {
"room" : [ "mission-control", "devops" ],
"user" : "website-admin@example.com",
"message" : "Encountered {{ctx.payload.hits.total}} errors in the last 5 minutes (facepalm)",
"message_format" : "text",
"color" : "red",
"notify" : true
}
}
}
}
--------------------------------------------------

View File

@ -20,6 +20,7 @@ import org.elasticsearch.script.ScriptModule;
import org.elasticsearch.shield.authz.AuthorizationModule; import org.elasticsearch.shield.authz.AuthorizationModule;
import org.elasticsearch.watcher.actions.WatcherActionModule; import org.elasticsearch.watcher.actions.WatcherActionModule;
import org.elasticsearch.watcher.actions.email.service.InternalEmailService; import org.elasticsearch.watcher.actions.email.service.InternalEmailService;
import org.elasticsearch.watcher.actions.hipchat.service.InternalHipChatService;
import org.elasticsearch.watcher.client.WatcherClientModule; import org.elasticsearch.watcher.client.WatcherClientModule;
import org.elasticsearch.watcher.condition.ConditionModule; import org.elasticsearch.watcher.condition.ConditionModule;
import org.elasticsearch.watcher.execution.ExecutionModule; import org.elasticsearch.watcher.execution.ExecutionModule;
@ -27,15 +28,7 @@ import org.elasticsearch.watcher.history.HistoryModule;
import org.elasticsearch.watcher.input.InputModule; import org.elasticsearch.watcher.input.InputModule;
import org.elasticsearch.watcher.license.LicenseModule; import org.elasticsearch.watcher.license.LicenseModule;
import org.elasticsearch.watcher.license.LicenseService; import org.elasticsearch.watcher.license.LicenseService;
import org.elasticsearch.watcher.rest.action.RestAckWatchAction; import org.elasticsearch.watcher.rest.action.*;
import org.elasticsearch.watcher.rest.action.RestDeleteWatchAction;
import org.elasticsearch.watcher.rest.action.RestExecuteWatchAction;
import org.elasticsearch.watcher.rest.action.RestGetWatchAction;
import org.elasticsearch.watcher.rest.action.RestHijackOperationAction;
import org.elasticsearch.watcher.rest.action.RestPutWatchAction;
import org.elasticsearch.watcher.rest.action.RestWatchServiceAction;
import org.elasticsearch.watcher.rest.action.RestWatcherInfoAction;
import org.elasticsearch.watcher.rest.action.RestWatcherStatsAction;
import org.elasticsearch.watcher.shield.ShieldIntegration; import org.elasticsearch.watcher.shield.ShieldIntegration;
import org.elasticsearch.watcher.shield.WatcherShieldModule; import org.elasticsearch.watcher.shield.WatcherShieldModule;
import org.elasticsearch.watcher.shield.WatcherUserHolder; import org.elasticsearch.watcher.shield.WatcherUserHolder;
@ -142,6 +135,7 @@ public class WatcherPlugin extends Plugin {
InitializingService.class, InitializingService.class,
LicenseService.class, LicenseService.class,
InternalEmailService.class, InternalEmailService.class,
InternalHipChatService.class,
HttpClient.class, HttpClient.class,
WatcherSettingsValidation.class); WatcherSettingsValidation.class);
} }

View File

@ -25,6 +25,7 @@ public interface Action extends ToXContent {
public enum Status implements ToXContent { public enum Status implements ToXContent {
SUCCESS, SUCCESS,
FAILURE, FAILURE,
PARTIAL_FAILURE,
THROTTLED, THROTTLED,
SIMULATED; SIMULATED;
@ -95,7 +96,6 @@ public interface Action extends ToXContent {
} }
interface Field { interface Field {
ParseField STATUS = new ParseField("status");
ParseField REASON = new ParseField("reason"); ParseField REASON = new ParseField("reason");
} }
} }

View File

@ -7,6 +7,7 @@ package org.elasticsearch.watcher.actions;
import org.elasticsearch.watcher.actions.email.EmailAction; import org.elasticsearch.watcher.actions.email.EmailAction;
import org.elasticsearch.watcher.actions.email.service.EmailTemplate; 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.index.IndexAction;
import org.elasticsearch.watcher.actions.logging.LoggingAction; import org.elasticsearch.watcher.actions.logging.LoggingAction;
import org.elasticsearch.watcher.actions.webhook.WebhookAction; import org.elasticsearch.watcher.actions.webhook.WebhookAction;
@ -53,4 +54,27 @@ public final class ActionBuilders {
return LoggingAction.builder(text); return LoggingAction.builder(text);
} }
public static HipChatAction.Builder hipchatAction(String message) {
return hipchatAction(Template.inline(message));
}
public static HipChatAction.Builder hipchatAction(String account, String body) {
return hipchatAction(account, Template.inline(body));
}
public static HipChatAction.Builder hipchatAction(Template.Builder body) {
return hipchatAction(body.build());
}
public static HipChatAction.Builder hipchatAction(String account, Template.Builder body) {
return hipchatAction(account, body.build());
}
public static HipChatAction.Builder hipchatAction(Template body) {
return hipchatAction(null, body);
}
public static HipChatAction.Builder hipchatAction(String account, Template body) {
return HipChatAction.builder(account, body);
}
} }

View File

@ -12,6 +12,10 @@ import org.elasticsearch.watcher.actions.email.EmailActionFactory;
import org.elasticsearch.watcher.actions.email.service.EmailService; import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.HtmlSanitizer; import org.elasticsearch.watcher.actions.email.service.HtmlSanitizer;
import org.elasticsearch.watcher.actions.email.service.InternalEmailService; import org.elasticsearch.watcher.actions.email.service.InternalEmailService;
import org.elasticsearch.watcher.actions.hipchat.HipChatAction;
import org.elasticsearch.watcher.actions.hipchat.HipChatActionFactory;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatService;
import org.elasticsearch.watcher.actions.hipchat.service.InternalHipChatService;
import org.elasticsearch.watcher.actions.index.IndexAction; import org.elasticsearch.watcher.actions.index.IndexAction;
import org.elasticsearch.watcher.actions.index.IndexActionFactory; import org.elasticsearch.watcher.actions.index.IndexActionFactory;
import org.elasticsearch.watcher.actions.logging.LoggingAction; import org.elasticsearch.watcher.actions.logging.LoggingAction;
@ -28,6 +32,14 @@ public class WatcherActionModule extends AbstractModule {
private final Map<String, Class<? extends ActionFactory>> parsers = new HashMap<>(); private final Map<String, Class<? extends ActionFactory>> parsers = new HashMap<>();
public WatcherActionModule() {
registerAction(EmailAction.TYPE, EmailActionFactory.class);
registerAction(WebhookAction.TYPE, WebhookActionFactory.class);
registerAction(IndexAction.TYPE, IndexActionFactory.class);
registerAction(LoggingAction.TYPE, LoggingActionFactory.class);
registerAction(HipChatAction.TYPE, HipChatActionFactory.class);
}
public void registerAction(String type, Class<? extends ActionFactory> parserType) { public void registerAction(String type, Class<? extends ActionFactory> parserType) {
parsers.put(type, parserType); parsers.put(type, parserType);
} }
@ -36,27 +48,17 @@ public class WatcherActionModule extends AbstractModule {
protected void configure() { protected void configure() {
MapBinder<String, ActionFactory> parsersBinder = MapBinder.newMapBinder(binder(), String.class, ActionFactory.class); MapBinder<String, ActionFactory> parsersBinder = MapBinder.newMapBinder(binder(), String.class, ActionFactory.class);
bind(EmailActionFactory.class).asEagerSingleton();
parsersBinder.addBinding(EmailAction.TYPE).to(EmailActionFactory.class);
bind(WebhookActionFactory.class).asEagerSingleton();
parsersBinder.addBinding(WebhookAction.TYPE).to(WebhookActionFactory.class);
bind(IndexActionFactory.class).asEagerSingleton();
parsersBinder.addBinding(IndexAction.TYPE).to(IndexActionFactory.class);
bind(LoggingActionFactory.class).asEagerSingleton();
parsersBinder.addBinding(LoggingAction.TYPE).to(LoggingActionFactory.class);
for (Map.Entry<String, Class<? extends ActionFactory>> entry : parsers.entrySet()) { for (Map.Entry<String, Class<? extends ActionFactory>> entry : parsers.entrySet()) {
bind(entry.getValue()).asEagerSingleton(); bind(entry.getValue()).asEagerSingleton();
parsersBinder.addBinding(entry.getKey()).to(entry.getValue()); parsersBinder.addBinding(entry.getKey()).to(entry.getValue());
} }
bind(ActionRegistry.class).asEagerSingleton(); bind(ActionRegistry.class).asEagerSingleton();
bind(HtmlSanitizer.class).asEagerSingleton(); bind(HtmlSanitizer.class).asEagerSingleton();
bind(EmailService.class).to(InternalEmailService.class).asEagerSingleton(); bind(EmailService.class).to(InternalEmailService.class).asEagerSingleton();
bind(HipChatService.class).to(InternalHipChatService.class).asEagerSingleton();
} }

View File

@ -79,13 +79,15 @@ public class InternalEmailService extends AbstractLifecycleComponent<InternalEma
Settings.Builder builder = Settings.builder(); Settings.Builder builder = Settings.builder();
String prefix = "watcher.actions.email.service"; String prefix = "watcher.actions.email.service";
for (String setting : settings.getAsMap().keySet()) { for (String setting : settings.getAsMap().keySet()) {
if (setting.startsWith("watcher.actions.email.service")) { if (setting.startsWith(prefix)) {
builder.put(setting.substring(prefix.length()+1), settings.get(setting)); builder.put(setting.substring(prefix.length()+1), settings.get(setting));
} }
} }
for (String setting : nodeSettings.getAsMap().keySet()) { if (nodeSettings != settings) { // if it's the same settings, no point in re-applying it
if (setting.startsWith("watcher.actions.email.service")) { for (String setting : nodeSettings.getAsMap().keySet()) {
builder.put(setting.substring(prefix.length()+1), settings.get(setting)); if (setting.startsWith(prefix)) {
builder.put(setting.substring(prefix.length() + 1), nodeSettings.get(setting));
}
} }
} }
accounts = createAccounts(builder.build(), logger); accounts = createAccounts(builder.build(), logger);

View File

@ -0,0 +1,58 @@
/*
* 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.hipchat;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ExecutableAction;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatAccount;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatService;
import org.elasticsearch.watcher.actions.hipchat.service.SentMessages;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Variables;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import org.elasticsearch.watcher.watch.Payload;
import java.util.Map;
/**
*
*/
public class ExecutableHipChatAction extends ExecutableAction<HipChatAction> {
private final TemplateEngine templateEngine;
private final HipChatService hipchatService;
public ExecutableHipChatAction(HipChatAction action, ESLogger logger, HipChatService hipchatService, TemplateEngine templateEngine) {
super(action, logger);
this.hipchatService = hipchatService;
this.templateEngine = templateEngine;
}
@Override
public Action.Result execute(final String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
HipChatAccount account = action.account != null ?
hipchatService.getAccount(action.account) :
hipchatService.getDefaultAccount();
// lets validate the message again, in case the hipchat service were updated since the
// watch/action were created.
account.validateParsedTemplate(ctx.id().watchId(), actionId, action.message);
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
HipChatMessage message = account.render(ctx.id().watchId(), actionId, templateEngine, action.message, model);
if (ctx.simulateAction(actionId)) {
return new HipChatAction.Result.Simulated(message);
}
SentMessages sentMessages = account.send(message);
return new HipChatAction.Result.Executed(sentMessages);
}
}

View File

@ -0,0 +1,257 @@
/*
* 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.hipchat;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage;
import org.elasticsearch.watcher.actions.hipchat.service.SentMessages;
import org.elasticsearch.watcher.support.template.Template;
import javax.annotation.Nullable;
import java.io.IOException;
/**
*
*/
public class HipChatAction implements Action {
public static final String TYPE = "hipchat";
final @Nullable String account;
final HipChatMessage.Template message;
public HipChatAction(@Nullable String account, HipChatMessage.Template message) {
this.account = account;
this.message = message;
}
@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;
HipChatAction that = (HipChatAction) o;
if (!account.equals(that.account)) return false;
return message.equals(that.message);
}
@Override
public int hashCode() {
int result = account.hashCode();
result = 31 * result + message.hashCode();
return result;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (account != null) {
builder.field(Field.ACCOUNT.getPreferredName(), account);
}
builder.field(Field.MESSAGE.getPreferredName(), message);
return builder.endObject();
}
public static HipChatAction parse(String watchId, String actionId, XContentParser parser) throws IOException {
String account = null;
HipChatMessage.Template message = 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, Field.ACCOUNT)) {
if (token == XContentParser.Token.VALUE_STRING) {
account = parser.text();
} else {
throw new ElasticsearchParseException("failed to parse [{}] action [{}/{}]. expected [{}] to be of type string, but found [{}] instead", TYPE, watchId, actionId, Field.ACCOUNT.getPreferredName(), token);
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.MESSAGE)) {
try {
message = HipChatMessage.Template.parse(parser);
} catch (Exception e) {
throw new ElasticsearchParseException("failed to parse [{}] action [{}/{}]. failed to parse [{}] field", e, TYPE, watchId, actionId, Field.MESSAGE.getPreferredName());
}
} else {
throw new ElasticsearchParseException("failed to parse [{}] action [{}/{}]. unexpected token [{}]", TYPE, watchId, actionId, token);
}
}
if (message == null) {
throw new ElasticsearchParseException("failed to parse [{}] action [{}/{}]. missing required [{}] field", TYPE, watchId, actionId, Field.MESSAGE.getPreferredName());
}
return new HipChatAction(account, message);
}
public static Builder builder(String account, Template body) {
return new Builder(account, body);
}
public interface Result {
class Executed extends Action.Result implements Result {
private final SentMessages sentMessages;
public Executed(SentMessages sentMessages) {
super(TYPE, status(sentMessages));
this.sentMessages = sentMessages;
}
public SentMessages sentMessages() {
return sentMessages;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.field(type, sentMessages, params);
}
static Status status(SentMessages sentMessages) {
boolean hasSuccesses = false;
boolean hasFailures = false;
for (SentMessages.SentMessage message : sentMessages) {
if (message.successful()) {
hasSuccesses = true;
} else {
hasFailures = true;
}
if (hasFailures && hasSuccesses) {
return Status.PARTIAL_FAILURE;
}
}
return hasFailures ? Status.FAILURE : Status.SUCCESS;
}
}
class Simulated extends Action.Result implements Result {
private final HipChatMessage message;
protected Simulated(HipChatMessage message) {
super(TYPE, Status.SIMULATED);
this.message = message;
}
public HipChatMessage getMessage() {
return message;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject(type)
.field(Field.MESSAGE.getPreferredName(), message, params)
.endObject();
}
}
}
public static class Builder implements Action.Builder<HipChatAction> {
final String account;
final HipChatMessage.Template.Builder messageBuilder;
public Builder(String account, Template body) {
this.account = account;
this.messageBuilder = new HipChatMessage.Template.Builder(body);
}
public Builder addRooms(org.elasticsearch.watcher.support.template.Template... rooms) {
messageBuilder.addRooms(rooms);
return this;
}
public Builder addRooms(org.elasticsearch.watcher.support.template.Template.Builder... rooms) {
org.elasticsearch.watcher.support.template.Template[] templates = new org.elasticsearch.watcher.support.template.Template[rooms.length];
for (int i = 0; i < rooms.length; i++) {
templates[i] = rooms[i].build();
}
return addRooms(templates);
}
public Builder addRooms(String... rooms) {
org.elasticsearch.watcher.support.template.Template[] templates = new org.elasticsearch.watcher.support.template.Template[rooms.length];
for (int i = 0; i < rooms.length; i++) {
templates[i] = Template.inline(rooms[i]).build();
}
return addRooms(templates);
}
public Builder addUsers(org.elasticsearch.watcher.support.template.Template... users) {
messageBuilder.addUsers(users);
return this;
}
public Builder addUsers(org.elasticsearch.watcher.support.template.Template.Builder... users) {
org.elasticsearch.watcher.support.template.Template[] templates = new org.elasticsearch.watcher.support.template.Template[users.length];
for (int i = 0; i < users.length; i++) {
templates[i] = users[i].build();
}
return addUsers(templates);
}
public Builder addUsers(String... users) {
org.elasticsearch.watcher.support.template.Template[] templates = new org.elasticsearch.watcher.support.template.Template[users.length];
for (int i = 0; i < users.length; i++) {
templates[i] = Template.inline(users[i]).build();
}
return addUsers(templates);
}
public Builder setFrom(String from) {
messageBuilder.setFrom(from);
return this;
}
public Builder setFormat(HipChatMessage.Format format) {
messageBuilder.setFormat(format);
return this;
}
public Builder setColor(org.elasticsearch.watcher.support.template.Template color) {
messageBuilder.setColor(color);
return this;
}
public Builder setColor(org.elasticsearch.watcher.support.template.Template.Builder color) {
return setColor(color.build());
}
public Builder setColor(HipChatMessage.Color color) {
return setColor(color.asTemplate());
}
public Builder setNotify(boolean notify) {
messageBuilder.setNotify(notify);
return this;
}
@Override
public HipChatAction build() {
return new HipChatAction(account, messageBuilder.build());
}
}
public interface Field {
ParseField ACCOUNT = new ParseField("account");
ParseField MESSAGE = new ParseField("message");
}
}

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.hipchat;
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.service.HipChatAccount;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatService;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import java.io.IOException;
/**
*
*/
public class HipChatActionFactory extends ActionFactory<HipChatAction, ExecutableHipChatAction> {
private final TemplateEngine templateEngine;
private final HipChatService hipchatService;
@Inject
public HipChatActionFactory(Settings settings, TemplateEngine templateEngine, HipChatService hipchatService) {
super(Loggers.getLogger(ExecutableHipChatAction.class, settings));
this.templateEngine = templateEngine;
this.hipchatService = hipchatService;
}
@Override
public String type() {
return HipChatAction.TYPE;
}
@Override
public HipChatAction parseAction(String watchId, String actionId, XContentParser parser) throws IOException {
HipChatAction action = HipChatAction.parse(watchId, actionId, parser);
HipChatAccount account = hipchatService.getAccount(action.account);
if (account == null) {
throw new ElasticsearchParseException("could not parse [hipchat] action [{}/{}]. unknown hipchat account [{}]", watchId, account, action.account);
}
account.validateParsedTemplate(watchId, actionId, action.message);
return action;
}
@Override
public ExecutableHipChatAction createExecutable(HipChatAction action) {
return new ExecutableHipChatAction(action, actionLogger, hipchatService, templateEngine);
}
}

View File

@ -0,0 +1,120 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
/**
*
*/
public abstract class HipChatAccount {
public static final String AUTH_TOKEN_SETTING = "auth_token";
public static final String ROOM_SETTING = HipChatMessage.Field.ROOM.getPreferredName();
public static final String DEFAULT_ROOM_SETTING = "message_defaults." + HipChatMessage.Field.ROOM.getPreferredName();
public static final String DEFAULT_USER_SETTING = "message_defaults." + HipChatMessage.Field.USER.getPreferredName();
public static final String DEFAULT_FROM_SETTING = "message_defaults." + HipChatMessage.Field.FROM.getPreferredName();
public static final String DEFAULT_FORMAT_SETTING = "message_defaults." + HipChatMessage.Field.FORMAT.getPreferredName();
public static final String DEFAULT_COLOR_SETTING = "message_defaults." + HipChatMessage.Field.COLOR.getPreferredName();
public static final String DEFAULT_NOTIFY_SETTING = "message_defaults." + HipChatMessage.Field.NOTIFY.getPreferredName();
protected final ESLogger logger;
protected final String name;
protected final Profile profile;
protected final HipChatServer server;
protected final HttpClient httpClient;
protected final String authToken;
protected HipChatAccount(String name, Profile profile, Settings settings, HipChatServer defaultServer, HttpClient httpClient, ESLogger logger) {
this.name = name;
this.profile = profile;
this.server = new HipChatServer(settings, defaultServer);
this.httpClient = httpClient;
this.authToken = settings.get(AUTH_TOKEN_SETTING);
if (this.authToken == null || this.authToken.length() == 0) {
throw new SettingsException("hipchat account [" + name + "] missing required [" + AUTH_TOKEN_SETTING + "] setting");
}
this.logger = logger;
}
public abstract String type();
public abstract void validateParsedTemplate(String watchId, String actionId, HipChatMessage.Template message) throws SettingsException;
public abstract HipChatMessage render(String watchId, String actionId, TemplateEngine engine, HipChatMessage.Template template, Map<String, Object> model);
public abstract SentMessages send(HipChatMessage message);
enum Profile implements ToXContent {
V1() {
@Override
HipChatAccount createAccount(String name, Settings settings, HipChatServer defaultServer, HttpClient httpClient, ESLogger logger) {
return new V1Account(name, settings, defaultServer, httpClient, logger);
}
},
INTEGRATION() {
@Override
HipChatAccount createAccount(String name, Settings settings, HipChatServer defaultServer, HttpClient httpClient, ESLogger logger) {
return new IntegrationAccount(name, settings, defaultServer, httpClient, logger);
}
},
USER() {
@Override
HipChatAccount createAccount(String name, Settings settings, HipChatServer defaultServer, HttpClient httpClient, ESLogger logger) {
return new UserAccount(name, settings, defaultServer, httpClient, logger);
}
};
abstract HipChatAccount createAccount(String name, Settings settings, HipChatServer defaultServer, HttpClient httpClient, ESLogger logger);
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.value(name().toLowerCase(Locale.ROOT));
}
public String value() {
return name().toLowerCase(Locale.ROOT);
}
public static Profile parse(XContentParser parser) throws IOException {
return Profile.valueOf(parser.text().toUpperCase(Locale.ROOT));
}
public static Profile resolve(String value, Profile defaultValue) {
if (value == null) {
return defaultValue;
}
return Profile.valueOf(value.toUpperCase(Locale.ROOT));
}
public static Profile resolve(Settings settings, String setting, Profile defaultValue) {
return resolve(settings.get(setting), defaultValue);
}
public static boolean validate(String value) {
try {
Profile.valueOf(value.toUpperCase(Locale.ROOT));
return true;
} catch (IllegalArgumentException ilae) {
return false;
}
}
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatAccount.Profile;
import org.elasticsearch.watcher.support.http.HttpClient;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class HipChatAccounts {
private final Map<String, HipChatAccount> accounts;
private final String defaultAccountName;
public HipChatAccounts(Settings settings, HttpClient httpClient, ESLogger logger) {
HipChatServer defaultServer = new HipChatServer(settings);
Settings accountsSettings = settings.getAsSettings("account");
accounts = new HashMap<>();
for (String name : accountsSettings.names()) {
Settings accountSettings = accountsSettings.getAsSettings(name);
Profile profile = Profile.resolve(accountSettings, "profile", null);
if (profile == null) {
throw new SettingsException("missing [profile] setting for hipchat account [" + name + "]");
}
HipChatAccount account = profile.createAccount(name, accountSettings, defaultServer, httpClient, logger);
accounts.put(name, account);
}
String defaultAccountName = settings.get("default_account");
if (defaultAccountName == null) {
if (accounts.isEmpty()) {
this.defaultAccountName = null;
} else {
HipChatAccount account = accounts.values().iterator().next();
logger.info("default hipchat account set to [{}]", account.name);
this.defaultAccountName = account.name;
}
} else if (!accounts.containsKey(defaultAccountName)) {
throw new SettingsException("could not find default hipchat 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 unkonwn account.
* @throws IllegalStateException if the name is null and the default account is null.
*/
public HipChatAccount account(String name) throws IllegalStateException {
if (name == null) {
if (defaultAccountName == null) {
throw new IllegalStateException("cannot find default hipchat account as no accounts have been configured");
}
name = defaultAccountName;
}
return accounts.get(name);
}
}

View File

@ -0,0 +1,489 @@
/*
* 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.hipchat.service;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
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.watcher.support.template.TemplateEngine;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
/**
*
*/
public class HipChatMessage implements ToXContent {
final String body;
final @Nullable String[] rooms;
final @Nullable String[] users;
final @Nullable String from;
final @Nullable Format format;
final @Nullable Color color;
final @Nullable Boolean notify;
public HipChatMessage(String body, String[] rooms, String[] users, String from, Format format, Color color, Boolean notify) {
this.body = body;
this.rooms = rooms;
this.users = users;
this.from = from;
this.format = format;
this.color = color;
this.notify = notify;
}
public String getBody() {
return body;
}
public String[] getRooms() {
return rooms;
}
@Nullable
public String[] getUsers() {
return users;
}
@Nullable
public String getFrom() {
return from;
}
@Nullable
public Format getFormat() {
return format;
}
@Nullable
public Color getColor() {
return color;
}
@Nullable
public Boolean getNotify() {
return notify;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HipChatMessage that = (HipChatMessage) o;
if (!body.equals(that.body)) return false;
if (!Arrays.equals(rooms, that.rooms)) return false;
if (!Arrays.equals(users, that.users)) return false;
if (from != null ? !from.equals(that.from) : that.from != null) return false;
if (format != that.format) return false;
if (color != that.color) return false;
return !(notify != null ? !notify.equals(that.notify) : that.notify != null);
}
@Override
public int hashCode() {
int result = body.hashCode();
result = 31 * result + (rooms != null ? Arrays.hashCode(rooms) : 0);
result = 31 * result + (users != null ? Arrays.hashCode(users) : 0);
result = 31 * result + (from != null ? from.hashCode() : 0);
result = 31 * result + (format != null ? format.hashCode() : 0);
result = 31 * result + (color != null ? color.hashCode() : 0);
result = 31 * result + (notify != null ? notify.hashCode() : 0);
return result;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return toXContent(builder, params, true);
}
public XContentBuilder toXContent(XContentBuilder builder, Params params, boolean includeTargets) throws IOException {
builder.startObject();
if (from != null) {
builder.field(Field.FROM.getPreferredName(), from);
}
if (includeTargets) {
if (rooms != null && rooms.length > 0) {
builder.array(Field.ROOM.getPreferredName(), rooms);
}
if (users != null && users.length > 0) {
builder.array(Field.USER.getPreferredName(), users);
}
}
builder.field(Field.BODY.getPreferredName(), body);
if (format != null) {
builder.field(Field.FORMAT.getPreferredName(), format, params);
}
if (color != null) {
builder.field(Field.COLOR.getPreferredName(), color, params);
}
if (notify != null) {
builder.field(Field.NOTIFY.getPreferredName(), notify);
}
return builder.endObject();
}
public static class Template implements ToXContent {
final org.elasticsearch.watcher.support.template.Template body;
final @Nullable org.elasticsearch.watcher.support.template.Template[] rooms;
final @Nullable org.elasticsearch.watcher.support.template.Template[] users;
final @Nullable String from;
final @Nullable Format format;
final @Nullable org.elasticsearch.watcher.support.template.Template color;
final @Nullable Boolean notify;
public Template(org.elasticsearch.watcher.support.template.Template body,
org.elasticsearch.watcher.support.template.Template[] rooms,
org.elasticsearch.watcher.support.template.Template[] users,
String from,
Format format,
org.elasticsearch.watcher.support.template.Template color,
Boolean notify) {
this.rooms = rooms;
this.users = users;
this.body = body;
this.from = from;
this.format = format;
this.color = color;
this.notify = notify;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Template template = (Template) o;
if (!body.equals(template.body)) return false;
if (!Arrays.equals(rooms, template.rooms)) return false;
if (!Arrays.equals(users, template.users)) return false;
if (from != null ? !from.equals(template.from) : template.from != null) return false;
if (format != template.format) return false;
if (color != null ? !color.equals(template.color) : template.color != null) return false;
return !(notify != null ? !notify.equals(template.notify) : template.notify != null);
}
@Override
public int hashCode() {
int result = body.hashCode();
result = 31 * result + (rooms != null ? Arrays.hashCode(rooms) : 0);
result = 31 * result + (users != null ? Arrays.hashCode(users) : 0);
result = 31 * result + (from != null ? from.hashCode() : 0);
result = 31 * result + (format != null ? format.hashCode() : 0);
result = 31 * result + (color != null ? color.hashCode() : 0);
result = 31 * result + (notify != null ? notify.hashCode() : 0);
return result;
}
public HipChatMessage render(TemplateEngine engine, Map<String, Object> model) {
String body = engine.render(this.body, model);
String[] rooms = null;
if (this.rooms != null) {
rooms = new String[this.rooms.length];
for (int i = 0; i < this.rooms.length; i++) {
rooms[i] = engine.render(this.rooms[i], model);
}
}
String[] users = null;
if (this.users != null) {
users = new String[this.users.length];
for (int i = 0; i < this.users.length; i++) {
users[i] = engine.render(this.users[i], model);
}
}
Color color = this.color == null ? null : Color.resolve(engine.render(this.color, model), null);
return new HipChatMessage(body, rooms, users, from, format, color, notify);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (from != null) {
builder.field(Field.FROM.getPreferredName(), from);
}
if (rooms != null && rooms.length > 0) {
builder.startArray(Field.ROOM.getPreferredName());
for (org.elasticsearch.watcher.support.template.Template room : rooms) {
room.toXContent(builder, params);
}
builder.endArray();
}
if (users != null && users.length > 0) {
builder.startArray(Field.USER.getPreferredName());
for (org.elasticsearch.watcher.support.template.Template user : users) {
user.toXContent(builder, params);
}
builder.endArray();
}
builder.field(Field.BODY.getPreferredName(), body, params);
if (format != null) {
builder.field(Field.FORMAT.getPreferredName(), format, params);
}
if (color != null) {
builder.field(Field.COLOR.getPreferredName(), color, params);
}
if (notify != null) {
builder.field(Field.NOTIFY.getPreferredName(), notify);
}
return builder.endObject();
}
public static Template parse(XContentParser parser) throws IOException {
org.elasticsearch.watcher.support.template.Template body = null;
org.elasticsearch.watcher.support.template.Template[] rooms = null;
org.elasticsearch.watcher.support.template.Template[] users = null;
String from = null;
org.elasticsearch.watcher.support.template.Template color = null;
Boolean notify = null;
HipChatMessage.Format messageFormat = 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, Field.FROM)) {
from = parser.text();
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.ROOM)) {
List<org.elasticsearch.watcher.support.template.Template> templates = new ArrayList<>();
if (token == XContentParser.Token.START_ARRAY) {
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
try {
templates.add(org.elasticsearch.watcher.support.template.Template.parse(parser));
} catch (ElasticsearchParseException epe) {
throw new ElasticsearchParseException("failed to parse hipchat message. failed to parse [{}] field", epe, Field.ROOM.getPreferredName());
}
}
} else {
try {
templates.add(org.elasticsearch.watcher.support.template.Template.parse(parser));
} catch (ElasticsearchParseException epe) {
throw new ElasticsearchParseException("failed to parse hipchat message. failed to parse [{}] field", epe, Field.ROOM.getPreferredName());
}
}
rooms = templates.toArray(new org.elasticsearch.watcher.support.template.Template[templates.size()]);
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.USER)) {
List<org.elasticsearch.watcher.support.template.Template> templates = new ArrayList<>();
if (token == XContentParser.Token.START_ARRAY) {
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
try {
templates.add(org.elasticsearch.watcher.support.template.Template.parse(parser));
} catch (ElasticsearchParseException epe) {
throw new ElasticsearchParseException("failed to parse hipchat message. failed to parse [{}] field", epe, Field.USER.getPreferredName());
}
}
} else {
try {
templates.add(org.elasticsearch.watcher.support.template.Template.parse(parser));
} catch (ElasticsearchParseException epe) {
throw new ElasticsearchParseException("failed to parse hipchat message. failed to parse [{}] field", epe, Field.USER.getPreferredName());
}
}
users = templates.toArray(new org.elasticsearch.watcher.support.template.Template[templates.size()]);
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.COLOR)) {
try {
color = org.elasticsearch.watcher.support.template.Template.parse(parser);
} catch (ElasticsearchParseException | IllegalArgumentException e) {
throw new ElasticsearchParseException("failed to parse hipchat message. failed to parse [{}] field", e, Field.COLOR.getPreferredName());
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.NOTIFY)) {
if (token == XContentParser.Token.VALUE_BOOLEAN) {
notify = parser.booleanValue();
} else {
throw new ElasticsearchParseException("failed to parse hipchat message. failed to parse [{}] field, expected a boolean value but found [{}]", Field.NOTIFY.getPreferredName(), token);
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.BODY)) {
try {
body = org.elasticsearch.watcher.support.template.Template.parse(parser);
} catch (ElasticsearchParseException pe) {
throw new ElasticsearchParseException("failed to parse hipchat message. failed to parse [{}] field", pe, Field.BODY.getPreferredName());
}
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.FORMAT)) {
try {
messageFormat = HipChatMessage.Format.parse(parser);
} catch (IllegalArgumentException ilae) {
throw new ElasticsearchParseException("failed to parse hipchat message. failed to parse [{}] field", ilae, Field.FORMAT.getPreferredName());
}
} else {
throw new ElasticsearchParseException("failed to parse hipchat message. unexpected token [{}]", token);
}
}
if (body == null) {
throw new ElasticsearchParseException("failed to parse hipchat message. missing required [{}] field", Field.BODY.getPreferredName());
}
return new HipChatMessage.Template(body, rooms, users, from, messageFormat, color, notify);
}
public static class Builder {
final org.elasticsearch.watcher.support.template.Template body;
final List<org.elasticsearch.watcher.support.template.Template> rooms = new ArrayList<>();
final List<org.elasticsearch.watcher.support.template.Template> users = new ArrayList<>();
@Nullable String from;
@Nullable Format format;
@Nullable org.elasticsearch.watcher.support.template.Template color;
@Nullable Boolean notify;
public Builder(org.elasticsearch.watcher.support.template.Template body) {
this.body = body;
}
public Builder addRooms(org.elasticsearch.watcher.support.template.Template... rooms) {
this.rooms.addAll(Arrays.asList(rooms));
return this;
}
public Builder addUsers(org.elasticsearch.watcher.support.template.Template... users) {
this.users.addAll(Arrays.asList(users));
return this;
}
public Builder setFrom(String from) {
this.from = from;
return this;
}
public Builder setFormat(Format format) {
this.format = format;
return this;
}
public Builder setColor(org.elasticsearch.watcher.support.template.Template color) {
this.color = color;
return this;
}
public Builder setNotify(boolean notify) {
this.notify = notify;
return this;
}
public Template build() {
return new Template(
body,
rooms.isEmpty() ? null : rooms.toArray(new org.elasticsearch.watcher.support.template.Template[rooms.size()]),
users.isEmpty() ? null : users.toArray(new org.elasticsearch.watcher.support.template.Template[users.size()]),
from,
format,
color,
notify);
}
}
}
public enum Color implements ToXContent {
YELLOW, GREEN, RED, PURPLE, GRAY, RANDOM;
private final org.elasticsearch.watcher.support.template.Template template = org.elasticsearch.watcher.support.template.Template.inline(name()).build();
public org.elasticsearch.watcher.support.template.Template asTemplate() {
return template;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.value(name().toLowerCase(Locale.ROOT));
}
public String value() {
return name().toLowerCase(Locale.ROOT);
}
public static Color parse(XContentParser parser) throws IOException {
return Color.valueOf(parser.text().toUpperCase(Locale.ROOT));
}
public static Color resolve(String value, Color defaultValue) {
if (value == null) {
return defaultValue;
}
return Color.valueOf(value.toUpperCase(Locale.ROOT));
}
public static Color resolve(Settings settings, String setting, Color defaultValue) {
return resolve(settings.get(setting), defaultValue);
}
public static boolean validate(String value) {
try {
Color.valueOf(value.toUpperCase(Locale.ROOT));
return true;
} catch (IllegalArgumentException ilae) {
return false;
}
}
}
/**
*
*/
public enum Format implements ToXContent {
TEXT,
HTML;
private final org.elasticsearch.watcher.support.template.Template template = org.elasticsearch.watcher.support.template.Template.inline(name()).build();
public org.elasticsearch.watcher.support.template.Template asTemplate() {
return template;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.value(name().toLowerCase(Locale.ROOT));
}
public String value() {
return name().toLowerCase(Locale.ROOT);
}
public static Format parse(XContentParser parser) throws IOException {
return Format.valueOf(parser.text().toUpperCase(Locale.ROOT));
}
public static Format resolve(String value, Format defaultValue) {
if (value == null) {
return defaultValue;
}
return Format.valueOf(value.toUpperCase(Locale.ROOT));
}
public static Format resolve(Settings settings, String setting, Format defaultValue) {
return resolve(settings.get(setting), defaultValue);
}
public static boolean validate(String value) {
try {
Format.valueOf(value.toUpperCase(Locale.ROOT));
return true;
} catch (IllegalArgumentException ilae) {
return false;
}
}
}
public interface Field {
ParseField ROOM = new ParseField("room");
ParseField USER = new ParseField("user");
ParseField BODY = new ParseField("body");
ParseField FROM = new ParseField("from");
ParseField COLOR = new ParseField("color");
ParseField NOTIFY = new ParseField("notify");
ParseField FORMAT = new ParseField("format");
}
}

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.hipchat.service;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.watcher.support.http.HttpRequest;
/**
*
*/
public class HipChatServer {
public static final String HOST_SETTING = "host";
public static final String PORT_SETTING = "port";
public static final HipChatServer DEFAULT = new HipChatServer("api.hipchat.com", 443, null);
private final String host;
private final int port;
private final HipChatServer fallback;
public HipChatServer(Settings settings) {
this(settings, DEFAULT);
}
public HipChatServer(Settings settings, HipChatServer fallback) {
this(settings.get(HOST_SETTING, null), settings.getAsInt(PORT_SETTING, -1), fallback);
}
public HipChatServer(String host, int port, HipChatServer fallback) {
this.host = host;
this.port = port;
this.fallback = fallback;
}
public String host() {
return host != null ? host : fallback.host();
}
public int port() {
return port > 0 ? port : fallback.port();
}
public HipChatServer fallback() {
return fallback != null ? fallback : DEFAULT;
}
public HipChatServer rebuild(Settings settings, HipChatServer fallback) {
return new HipChatServer(settings.get(HOST_SETTING, host), settings.getAsInt(PORT_SETTING, port), fallback);
}
public synchronized HttpRequest.Builder httpRequest() {
return HttpRequest.builder(host(), port());
}
}

View File

@ -0,0 +1,24 @@
/*
* 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.hipchat.service;
/**
*
*/
public interface HipChatService {
/**
* @return The default hipchat account.
*/
HipChatAccount getDefaultAccount();
/**
* @return The account identified by the given name. If the given name is {@code null} the default
* account will be returned.
*/
HipChatAccount getAccount(String accountName);
}

View File

@ -0,0 +1,130 @@
/*
* 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.hipchat.service;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.watcher.actions.hipchat.HipChatAction;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage.Color;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage.Format;
import org.elasticsearch.watcher.support.http.*;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
*
*/
public class IntegrationAccount extends HipChatAccount {
public static final String TYPE = "integration";
final String room;
final Defaults defaults;
public IntegrationAccount(String name, Settings settings, HipChatServer defaultServer, HttpClient httpClient, ESLogger logger) {
super(name, Profile.INTEGRATION, settings, defaultServer, httpClient, logger);
String[] rooms = settings.getAsArray(ROOM_SETTING, null);
if (rooms == null || rooms.length == 0) {
throw new SettingsException("invalid hipchat account [" + name + "]. missing required [" + ROOM_SETTING + "] setting for [" + TYPE + "] account profile");
}
if (rooms.length > 1) {
throw new SettingsException("invalid hipchat account [" + name + "]. [" + ROOM_SETTING + "] setting for [" + TYPE + "] account must only be set with a single value");
}
this.room = rooms[0];
defaults = new Defaults(settings);
}
@Override
public String type() {
return TYPE;
}
@Override
public void validateParsedTemplate(String watchId, String actionId, HipChatMessage.Template template) throws SettingsException {
if (template.rooms != null) {
throw new ElasticsearchParseException("invalid [" + HipChatAction.TYPE + "] action for [" + watchId + "/" + actionId + "] action. [" + name + "] hipchat account doesn't support custom rooms");
}
if (template.users != null) {
throw new ElasticsearchParseException("invalid [" + HipChatAction.TYPE + "] action for [" + watchId + "/" + actionId + "] action. [" + name + "] hipchat account doesn't support user private messages");
}
if (template.from != null) {
throw new ElasticsearchParseException("invalid [" + HipChatAction.TYPE + "] action for [" + watchId + "/" + actionId + "] action. [" + name + "] hipchat account doesn't support custom `from` fields");
}
}
@Override
public HipChatMessage render(String watchId, String actionId, TemplateEngine engine, HipChatMessage.Template template, Map<String, Object> model) {
String message = engine.render(template.body, model);
Color color = template.color != null ? Color.resolve(engine.render(template.color, model), defaults.color) : defaults.color;
Boolean notify = template.notify != null ? template.notify : defaults.notify;
Format messageFormat = template.format != null ? template.format : defaults.format;
return new HipChatMessage(message, null, null, null, messageFormat, color, notify);
}
@Override
public SentMessages send(HipChatMessage message) {
List<SentMessages.SentMessage> sentMessages = new ArrayList<>();
HttpRequest request = buildRoomRequest(room, message);
try {
HttpResponse response = httpClient.execute(request);
sentMessages.add(SentMessages.SentMessage.responded(room, SentMessages.SentMessage.TargetType.ROOM, message, request, response));
} catch (IOException e) {
logger.error("failed to execute hipchat api http request", e);
sentMessages.add(SentMessages.SentMessage.error(room, SentMessages.SentMessage.TargetType.ROOM, message, ExceptionsHelper.detailedMessage(e)));
}
return new SentMessages(name, sentMessages);
}
public HttpRequest buildRoomRequest(String room, final HipChatMessage message) {
return server.httpRequest()
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v2/room/" + room + "/notification")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer " + authToken)
.body(XContentHelper.toString(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder xbuilder, Params params) throws IOException {
xbuilder.field("message", message.body);
if (message.format != null) {
xbuilder.field("message_format", message.format.value());
}
if (message.notify != null) {
xbuilder.field("notify", message.notify);
}
if (message.color != null) {
xbuilder.field("color", String.valueOf(message.color.value()));
}
return xbuilder;
}
}))
.build();
}
static class Defaults {
final @Nullable Format format;
final @Nullable Color color;
final @Nullable Boolean notify;
public Defaults(Settings settings) {
this.format = Format.resolve(settings, DEFAULT_FORMAT_SETTING, null);
this.color = Color.resolve(settings, DEFAULT_COLOR_SETTING, null);
this.notify = settings.getAsBoolean(DEFAULT_NOTIFY_SETTING, null);
}
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.watcher.shield.WatcherSettingsFilter;
import org.elasticsearch.watcher.support.http.HttpClient;
/**
*
*/
public class InternalHipChatService extends AbstractLifecycleComponent<InternalHipChatService> implements HipChatService {
private final HttpClient httpClient;
private volatile HipChatAccounts accounts;
@Inject
public InternalHipChatService(Settings settings, HttpClient httpClient, NodeSettingsService nodeSettingsService, WatcherSettingsFilter settingsFilter) {
super(settings);
this.httpClient = httpClient;
nodeSettingsService.addListener(new NodeSettingsService.Listener() {
@Override
public void onRefreshSettings(Settings settings) {
reset(settings);
}
});
settingsFilter.filterOut("watcher.actions.hipchat.service.account.*.auth_token");
}
@Override
protected void doStart() {
reset(settings);
}
@Override
protected void doStop() {
}
@Override
protected void doClose() {
}
@Override
public HipChatAccount getDefaultAccount() {
return accounts.account(null);
}
@Override
public HipChatAccount getAccount(String name) {
return accounts.account(name);
}
void reset(Settings nodeSettings) {
Settings.Builder builder = Settings.builder();
String prefix = "watcher.actions.hipchat.service";
for (String setting : settings.getAsMap().keySet()) {
if (setting.startsWith(prefix)) {
builder.put(setting.substring(prefix.length()+1), settings.get(setting));
}
}
if (nodeSettings != settings) { // if it's the same settings, no point in re-applying it
for (String setting : nodeSettings.getAsMap().keySet()) {
if (setting.startsWith(prefix)) {
builder.put(setting.substring(prefix.length() + 1), nodeSettings.get(setting));
}
}
}
accounts = new HipChatAccounts(builder.build(), httpClient, logger);
}
}

View File

@ -0,0 +1,152 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentBuilderString;
import org.elasticsearch.watcher.support.http.HttpRequest;
import org.elasticsearch.watcher.support.http.HttpResponse;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
/**
*
*/
public class SentMessages implements ToXContent, Iterable<SentMessages.SentMessage> {
private String accountName;
private List<SentMessage> messages;
public SentMessages(String accountName, List<SentMessage> messages) {
this.accountName = accountName;
this.messages = messages;
}
public String getAccountName() {
return accountName;
}
@Override
public Iterator<SentMessage> iterator() {
return messages.iterator();
}
public int count() {
return messages.size();
}
public List<SentMessage> asList() {
return Collections.unmodifiableList(messages);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(Field.ACCOUNT, accountName);
builder.startArray(Field.SENT_MESSAGES);
for (SentMessage message : messages) {
message.toXContent(builder, params);
}
builder.endArray();
return builder.endObject();
}
public static class SentMessage implements ToXContent {
public enum TargetType {
ROOM, USER;
final XContentBuilderString fieldName = new XContentBuilderString(name().toLowerCase(Locale.ROOT));
}
final String targetName;
final TargetType targetType;
final HipChatMessage message;
final @Nullable HttpRequest request;
final @Nullable HttpResponse response;
final @Nullable String failureReason;
public static SentMessage responded(String targetName, TargetType targetType, HipChatMessage message, HttpRequest request, HttpResponse response) {
String failureReason = resolveFailureReason(response);
return new SentMessage(targetName, targetType, message, request, response, failureReason);
}
public static SentMessage error(String targetName, TargetType targetType, HipChatMessage message, String reason) {
return new SentMessage(targetName, targetType, message, null, null, reason);
}
private SentMessage(String targetName, TargetType targetType, HipChatMessage message, HttpRequest request, HttpResponse response, String failureReason) {
this.targetName = targetName;
this.targetType = targetType;
this.message = message;
this.request = request;
this.response = response;
this.failureReason = failureReason;
}
public boolean successful() {
return failureReason == null;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (failureReason != null) {
builder.field(Field.STATUS, "failure");
builder.field(Field.REASON, failureReason);
if (request != null) {
builder.field(Field.REQUEST);
request.toXContent(builder, params);
}
if (response != null) {
builder.field(Field.RESPONSE);
response.toXContent(builder, params);
}
} else {
builder.field(Field.STATUS, "success");
}
builder.field(targetType.fieldName, targetName);
builder.field(Field.MESSAGE);
message.toXContent(builder, params, false);
return builder.endObject();
}
private static String resolveFailureReason(HttpResponse response) {
int status = response.status();
if (status < 300) {
return null;
}
switch (status) {
case 400: return "Bad Request";
case 401: return "Unauthorized. The provided authentication token is invalid.";
case 403: return "Forbidden. The account doesn't have permission to send this message.";
case 404: // Not Found
case 405: // Method Not Allowed
case 406: return "The account used invalid HipChat APIs"; // Not Acceptable
case 503:
case 500: return "HipChat Server Error.";
default:
return "Unknown Error";
}
}
}
interface Field {
XContentBuilderString ACCOUNT = new XContentBuilderString("account");
XContentBuilderString SENT_MESSAGES = new XContentBuilderString("sent_messages");
XContentBuilderString STATUS = new XContentBuilderString("status");
XContentBuilderString REASON = new XContentBuilderString("reason");
XContentBuilderString REQUEST = new XContentBuilderString("request");
XContentBuilderString RESPONSE = new XContentBuilderString("response");
XContentBuilderString MESSAGE = new XContentBuilderString("message");
}
}

View File

@ -0,0 +1,172 @@
/*
* 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.hipchat.service;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.watcher.actions.hipchat.HipChatAction;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage.Color;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage.Format;
import org.elasticsearch.watcher.support.http.*;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
*
*/
public class UserAccount extends HipChatAccount {
public static final String TYPE = "user";
final Defaults defaults;
public UserAccount(String name, Settings settings, HipChatServer defaultServer, HttpClient httpClient, ESLogger logger) {
super(name, Profile.USER, settings, defaultServer, httpClient, logger);
defaults = new Defaults(settings);
}
@Override
public String type() {
return TYPE;
}
@Override
public void validateParsedTemplate(String watchId, String actionId, HipChatMessage.Template template) throws SettingsException {
if (template.from != null) {
throw new ElasticsearchParseException("invalid [" + HipChatAction.TYPE + "] action for [" + watchId + "/" + actionId + "]. [" + name + "] hipchat account doesn't support custom `from` fields");
}
}
@Override
public HipChatMessage render(String watchId, String actionId, TemplateEngine engine, HipChatMessage.Template template, Map<String, Object> model) {
String[] rooms = defaults.rooms;
if (template.rooms != null) {
rooms = new String[template.rooms.length];
for (int i = 0; i < template.rooms.length; i++) {
rooms[i] = engine.render(template.rooms[i], model);
}
}
String[] users = defaults.users;
if (template.users != null) {
users = new String[template.users.length];
for (int i = 0; i < template.users.length; i++) {
users[i] = engine.render(template.users[i], model);
}
}
String message = engine.render(template.body, model);
Color color = Color.resolve(engine.render(template.color, model), defaults.color);
Boolean notify = template.notify != null ? template.notify : defaults.notify;
Format messageFormat = template.format != null ? template.format : defaults.format;
return new HipChatMessage(message, rooms, users, null, messageFormat, color, notify);
}
@Override
public SentMessages send(HipChatMessage message) {
List<SentMessages.SentMessage> sentMessages = new ArrayList<>();
if (message.rooms != null) {
for (String room : message.rooms) {
HttpRequest request = buildRoomRequest(room, message);
try {
HttpResponse response = httpClient.execute(request);
sentMessages.add(SentMessages.SentMessage.responded(room, SentMessages.SentMessage.TargetType.ROOM, message, request, response));
} catch (IOException e) {
logger.error("failed to execute hipchat api http request", e);
sentMessages.add(SentMessages.SentMessage.error(room, SentMessages.SentMessage.TargetType.ROOM, message, ExceptionsHelper.detailedMessage(e)));
}
}
}
if (message.users != null) {
for (String user : message.users) {
HttpRequest request = buildUserRequest(user, message);
try {
HttpResponse response = httpClient.execute(request);
sentMessages.add(SentMessages.SentMessage.responded(user, SentMessages.SentMessage.TargetType.USER, message, request, response));
} catch (IOException e) {
logger.error("failed to execute hipchat api http request", e);
sentMessages.add(SentMessages.SentMessage.error(user, SentMessages.SentMessage.TargetType.USER, message, ExceptionsHelper.detailedMessage(e)));
}
}
}
return new SentMessages(name, sentMessages);
}
public HttpRequest buildRoomRequest(String room, final HipChatMessage message) {
return server.httpRequest()
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v2/room/" + room + "/notification")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer " + authToken)
.body(XContentHelper.toString(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder xbuilder, Params params) throws IOException {
xbuilder.field("message", message.body);
if (message.format != null) {
xbuilder.field("message_format", message.format.value());
}
if (message.notify != null) {
xbuilder.field("notify", message.notify);
}
if (message.color != null) {
xbuilder.field("color", String.valueOf(message.color.value()));
}
return xbuilder;
}
}))
.build();
}
public HttpRequest buildUserRequest(String user, final HipChatMessage message) {
return server.httpRequest()
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v2/user/" + user + "/message")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer " + authToken)
.body(XContentHelper.toString(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder xbuilder, Params params) throws IOException {
xbuilder.field("message", message.body);
if (message.format != null) {
xbuilder.field("message_format", message.format.value());
}
if (message.notify != null) {
xbuilder.field("notify", message.notify);
}
return xbuilder;
}
}))
.build();
}
static class Defaults {
final @Nullable String[] rooms;
final @Nullable String[] users;
final @Nullable Format format;
final @Nullable Color color;
final @Nullable Boolean notify;
public Defaults(Settings settings) {
this.rooms = settings.getAsArray(DEFAULT_ROOM_SETTING, null);
this.users = settings.getAsArray(DEFAULT_USER_SETTING, null);
this.format = Format.resolve(settings, DEFAULT_FORMAT_SETTING, null);
this.color = Color.resolve(settings, DEFAULT_COLOR_SETTING, null);
this.notify = settings.getAsBoolean(DEFAULT_NOTIFY_SETTING, null);
}
}
}

View File

@ -0,0 +1,130 @@
/*
* 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.hipchat.service;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.watcher.actions.hipchat.HipChatAction;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage.Color;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage.Format;
import org.elasticsearch.watcher.support.http.*;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
*
*/
public class V1Account extends HipChatAccount {
public static final String TYPE = "v1";
final Defaults defaults;
public V1Account(String name, Settings settings, HipChatServer defaultServer, HttpClient httpClient, ESLogger logger) {
super(name, Profile.V1, settings, defaultServer, httpClient, logger);
defaults = new Defaults(settings);
}
@Override
public String type() {
return TYPE;
}
@Override
public void validateParsedTemplate(String watchId, String actionId, HipChatMessage.Template template) throws ElasticsearchParseException {
if (template.users != null) {
throw new ElasticsearchParseException("invalid [" + HipChatAction.TYPE + "] action for [" + watchId + "/" + actionId + "]. [" + name + "] hipchat account doesn't support user private messaging");
}
if ((template.rooms == null || template.rooms.length == 0) && (defaults.rooms == null || defaults.rooms.length == 0)) {
throw new ElasticsearchParseException("invalid [" + HipChatAction.TYPE + "] action for [" + watchId + "/" + actionId + "]. missing required [" + HipChatMessage.Field.ROOM + "] field for [" + name + "] hipchat account");
}
}
@Override
public HipChatMessage render(String watchId, String actionId, TemplateEngine engine, HipChatMessage.Template template, Map<String, Object> model) {
String message = engine.render(template.body, model);
String[] rooms = defaults.rooms;
if (template.rooms != null) {
rooms = new String[template.rooms.length];
for (int i = 0; i < template.rooms.length; i++) {
rooms[i] = engine.render(template.rooms[i], model);
}
}
String from = template.from != null ? template.from : defaults.from != null ? defaults.from : watchId;
Color color = Color.resolve(engine.render(template.color, model), defaults.color);
Boolean notify = template.notify != null ? template.notify : defaults.notify;
Format messageFormat = template.format != null ? template.format : defaults.format;
return new HipChatMessage(message, rooms, null, from, messageFormat, color, notify);
}
@Override
public SentMessages send(HipChatMessage message) {
List<SentMessages.SentMessage> sentMessages = new ArrayList<>();
if (message.rooms != null) {
for (String room : message.rooms) {
HttpRequest request = buildRoomRequest(room, message);
try {
HttpResponse response = httpClient.execute(request);
sentMessages.add(SentMessages.SentMessage.responded(room, SentMessages.SentMessage.TargetType.ROOM, message, request, response));
} catch (IOException e) {
logger.error("failed to execute hipchat api http request", e);
sentMessages.add(SentMessages.SentMessage.error(room, SentMessages.SentMessage.TargetType.ROOM, message, ExceptionsHelper.detailedMessage(e)));
}
}
}
return new SentMessages(name, sentMessages);
}
public HttpRequest buildRoomRequest(String room, HipChatMessage message) {
HttpRequest.Builder builder = server.httpRequest();
builder.method(HttpMethod.POST);
builder.scheme(Scheme.HTTPS);
builder.path("/v1/rooms/message");
builder.setHeader("Content-Type", "application/x-www-form-urlencoded");
builder.setParam("format", "json");
builder.setParam("auth_token", authToken);
StringBuilder body = new StringBuilder();
body.append("room_id=").append(room);
body.append("&from=").append(HttpRequest.encodeUrl(message.from));
body.append("&message=").append(HttpRequest.encodeUrl(message.body));
if (message.format != null) {
body.append("&message_format=").append(message.format.value());
}
if (message.color != null) {
body.append("&color=").append(message.color.value());
}
if (message.notify != null) {
body.append("&notify=").append(message.notify ? "1" : "0");
}
builder.body(body.toString());
return builder.build();
}
static class Defaults {
final @Nullable String[] rooms;
final @Nullable String from;
final @Nullable Format format;
final @Nullable Color color;
final @Nullable Boolean notify;
public Defaults(Settings settings) {
this.rooms = settings.getAsArray(DEFAULT_ROOM_SETTING, null);
this.from = settings.get(DEFAULT_FROM_SETTING);
this.format = Format.resolve(settings, DEFAULT_FORMAT_SETTING, null);
this.color = Color.resolve(settings, DEFAULT_COLOR_SETTING, null);
this.notify = settings.getAsBoolean(DEFAULT_NOTIFY_SETTING, null);
}
}
}

View File

@ -21,6 +21,9 @@ import org.elasticsearch.watcher.support.http.auth.HttpAuth;
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry; import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Map; import java.util.Map;
public class HttpRequest implements ToXContent { public class HttpRequest implements ToXContent {
@ -101,6 +104,22 @@ public class HttpRequest implements ToXContent {
return readTimeout; return readTimeout;
} }
public static String encodeUrl(String text) {
try {
return URLEncoder.encode(text, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("failed to URL encode text [" + text + "]", e);
}
}
public static String decodeUrl(String text) {
try {
return URLDecoder.decode(text, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("failed to URL decode text [" + text + "]", e);
}
}
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject(); builder.startObject();
@ -171,16 +190,28 @@ public class HttpRequest implements ToXContent {
@Override @Override
public String toString() { public String toString() {
return "HttpRequest{" + StringBuilder sb = new StringBuilder();
"auth=[" + (auth != null ? "******" : null) + sb.append("method=[").append(method).append("], ");
"], body=[" + body + '\'' + sb.append("scheme=[").append(scheme).append("], ");
"], path=[" + path + '\'' + sb.append("host=[").append(host).append("], ");
"], method=[" + method + sb.append("port=[").append(port).append("], ");
"], port=[" + port + sb.append("path=[").append(path).append("], ");
"], host=[" + host + '\'' + if (!headers.isEmpty()) {
"], connection_timeout=[" + connectionTimeout + '\'' + sb.append(", headers=[");
"], read_timeout=[" + readTimeout + '\'' + boolean first = true;
"]}"; for (Map.Entry<String, String> header : headers.entrySet()) {
if (!first) {
sb.append(", ");
}
sb.append("[").append(header.getKey()).append(": ").append(header.getValue()).append("]");
first = false;
}
sb.append("], ");
}
sb.append("connection_timeout=[").append(connectionTimeout).append("], ");
sb.append("read_timeout=[").append(readTimeout).append("], ");
sb.append("body=[").append(body).append("], ");
return sb.toString();
} }
public static Builder builder(String host, int port) { public static Builder builder(String host, int port) {

View File

@ -20,6 +20,7 @@ import org.jboss.netty.handler.codec.http.HttpHeaders;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -111,6 +112,28 @@ public class HttpResponse implements ToXContent {
return result; return result;
} }
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("status=[").append(status).append("]");
if (!headers.isEmpty()) {
sb.append(", headers=[");
boolean first = true;
for (Map.Entry<String, String[]> header : headers.entrySet()) {
if (!first) {
sb.append(", ");
}
sb.append("[").append(header.getKey()).append(": ").append(Arrays.toString(header.getValue())).append("]");
first = false;
}
sb.append("]");
}
if (hasContent()) {
sb.append(", body=[").append(body.toUtf8()).append("]");
}
return sb.toString();
}
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder = builder.startObject().field(Field.STATUS.getPreferredName(), status); builder = builder.startObject().field(Field.STATUS.getPreferredName(), status);

View File

@ -8,11 +8,6 @@ package org.elasticsearch.watcher.support.xcontent;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.compress.CompressedStreamInput;
import org.elasticsearch.common.compress.Compressor;
import org.elasticsearch.common.compress.CompressorFactory;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
@ -38,6 +33,29 @@ public class WatcherXContentUtils {
} }
} }
public static String[] readStringArray(XContentParser parser, boolean allowNull) throws IOException {
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
if (allowNull) {
return null;
}
throw new ElasticsearchParseException("could not parse [{}] field. expected a string array but found null value instead", parser.currentName());
}
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
throw new ElasticsearchParseException("could not parse [{}] field. expected a string array but found [{}] value instead", parser.currentName(), parser.currentToken());
}
List<String> list = new ArrayList<>();
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token == XContentParser.Token.VALUE_STRING) {
list.add(parser.text());
} else {
throw new ElasticsearchParseException("could not parse [{}] field. expected a string array but one of the value in the array is [{}]", parser.currentName(), token);
}
}
return list.toArray(new String[list.size()]);
}
// TODO open this up in core // TODO open this up in core
public static List<Object> readList(XContentParser parser, XContentParser.Token token) throws IOException { public static List<Object> readList(XContentParser parser, XContentParser.Token token) throws IOException {
List<Object> list = new ArrayList<>(); List<Object> list = new ArrayList<>();

View File

@ -321,6 +321,69 @@
} }
} }
} }
},
"hipchat" : {
"type": "object",
"dynamic": true,
"properties": {
"account": {
"type": "string",
"index": "not_analyzed"
},
"sent_messages": {
"type": "nested",
"include_in_parent": true,
"dynamic": true,
"properties": {
"status": {
"type": "string",
"index": "not_analyzed"
},
"reason": {
"type": "string"
},
"request" : {
"type" : "object",
"enabled" : false
},
"response" : {
"type" : "object",
"enabled" : false
},
"room" : {
"type": "string",
"index": "not_analyzed"
},
"user" : {
"type": "string",
"index": "not_analyzed"
},
"message" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"message_format" : {
"type" : "string",
"index" : "not_analyzed"
},
"color" : {
"type" : "string",
"index" : "not_analyzed"
},
"notify" : {
"type" : "boolean"
},
"message" : {
"type" : "string"
},
"from" : {
"type" : "string"
}
}
}
}
}
}
} }
} }
} }

View File

@ -24,6 +24,22 @@ public class WatcherF {
System.setProperty("es.shield.enabled", "false"); System.setProperty("es.shield.enabled", "false");
System.setProperty("es.security.manager.enabled", "false"); System.setProperty("es.security.manager.enabled", "false");
System.setProperty("es.plugins.load_classpath_plugins", "false"); System.setProperty("es.plugins.load_classpath_plugins", "false");
// this is for the `test-watcher-integration` group level integration in HipChat
System.setProperty("es.watcher.actions.hipchat.service.account.integration.profile", "integration");
System.setProperty("es.watcher.actions.hipchat.service.account.integration.auth_token", "huuS9v7ccuOy3ZBWWWr1vt8Lqu3sQnLUE81nrLZU");
System.setProperty("es.watcher.actions.hipchat.service.account.integration.room", "test-watcher");
// this is for the Watcher Test account in HipChat
System.setProperty("es.watcher.actions.hipchat.service.account.user.profile", "user");
System.setProperty("es.watcher.actions.hipchat.service.account.user.auth_token", "FYVx16oDH78ZW9r13wtXbcszyoyA7oX5tiMWg9X0");
// this is for the `test-watcher-v1` notification token
System.setProperty("es.watcher.actions.hipchat.service.account.v1.profile", "v1");
System.setProperty("es.watcher.actions.hipchat.service.account.v1.auth_token", "a734baf62df618b96dda55b323fc30");
System.setProperty("es.plugin.types", WatcherPlugin.class.getName() + "," + LicensePlugin.class.getName()); System.setProperty("es.plugin.types", WatcherPlugin.class.getName() + "," + LicensePlugin.class.getName());
System.setProperty("es.cluster.name", WatcherF.class.getSimpleName()); System.setProperty("es.cluster.name", WatcherF.class.getSimpleName());

View File

@ -0,0 +1,209 @@
/*
* 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.hipchat;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.bytes.BytesReference;
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.hipchat.service.HipChatAccount;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatService;
import org.elasticsearch.watcher.support.template.Template;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import org.junit.Before;
import org.junit.Test;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.watcher.actions.ActionBuilders.hipchatAction;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.*;
/**
*
*/
public class HipChatActionFactoryTests extends ESTestCase {
private HipChatActionFactory factory;
private HipChatService hipchatService;
@Before
public void init() throws Exception {
hipchatService = mock(HipChatService.class);
factory = new HipChatActionFactory(Settings.EMPTY, mock(TemplateEngine.class), hipchatService);
}
@Test
public void testParseAction() throws Exception {
HipChatAccount account = mock(HipChatAccount.class);
when(hipchatService.getAccount("_account1")).thenReturn(account);
HipChatAction action = hipchatAction("_account1", "_body").build();
XContentBuilder jsonBuilder = jsonBuilder().value(action);
XContentParser parser = JsonXContent.jsonXContent.createParser(jsonBuilder.bytes());
parser.nextToken();
HipChatAction parsedAction = factory.parseAction("_w1", "_a1", parser);
assertThat(parsedAction, is(action));
verify(account, times(1)).validateParsedTemplate("_w1", "_a1", action.message);
}
@Test(expected = ElasticsearchParseException.class)
public void testtestParseAction_UnknownAccount() throws Exception {
when(hipchatService.getAccount("_unknown")).thenReturn(null);
HipChatAction action = hipchatAction("_unknown", "_body").build();
XContentBuilder jsonBuilder = jsonBuilder().value(action);
XContentParser parser = JsonXContent.jsonXContent.createParser(jsonBuilder.bytes());
parser.nextToken();
factory.parseAction("_w1", "_a1", parser);
}
@Test
public void testParser() throws Exception {
XContentBuilder builder = jsonBuilder().startObject();
String accountName = randomAsciiOfLength(10);
builder.field("account", accountName);
builder.startObject("message");
Template body = Template.inline("_body").build();
builder.field("body", body);
Template[] rooms = null;
if (randomBoolean()) {
Template r1 = Template.inline("_r1").build();
Template r2 = Template.inline("_r2").build();
rooms = new Template[] { r1, r2 };
builder.array("room", r1, r2);
}
Template[] users = null;
if (randomBoolean()) {
Template u1 = Template.inline("_u1").build();
Template u2 = Template.inline("_u2").build();
users = new Template[] { u1, u2 };
builder.array("user", u1, u2);
}
String from = null;
if (randomBoolean()) {
from = randomAsciiOfLength(10);
builder.field("from", from);
}
HipChatMessage.Format format = null;
if (randomBoolean()) {
format = randomFrom(HipChatMessage.Format.values());
builder.field("format", format.value());
}
Template color = null;
if (randomBoolean()) {
color = Template.inline(randomFrom(HipChatMessage.Color.values()).value()).build();
builder.field("color", color);
}
Boolean notify = null;
if (randomBoolean()) {
notify = randomBoolean();
builder.field("notify", notify);
}
builder.endObject();
builder.endObject();
BytesReference bytes = builder.bytes();
logger.info("hipchat action json [{}]", bytes.toUtf8());
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
HipChatAction action = HipChatAction.parse("_watch", "_action", parser);
assertThat(action, notNullValue());
assertThat(action.account, is(accountName));
assertThat(action.message, notNullValue());
assertThat(action.message, is(new HipChatMessage.Template(body, rooms, users, from, format, color, notify)));
}
@Test
public void testParser_SelfGenerated() throws Exception {
String accountName = randomAsciiOfLength(10);
Template body = Template.inline("_body").build();
HipChatMessage.Template.Builder templateBuilder = new HipChatMessage.Template.Builder(body);
XContentBuilder builder = jsonBuilder().startObject();
builder.field("account", accountName);
builder.startObject("message");
builder.field("body", body);
if (randomBoolean()) {
Template r1 = Template.inline("_r1").build();
Template r2 = Template.inline("_r2").build();
templateBuilder.addRooms(r1, r2);
builder.array("room", r1, r2);
}
if (randomBoolean()) {
Template u1 = Template.inline("_u1").build();
Template u2 = Template.inline("_u2").build();
templateBuilder.addUsers(u1, u2);
builder.array("user", u1, u2);
}
if (randomBoolean()) {
String from = randomAsciiOfLength(10);
templateBuilder.setFrom(from);
builder.field("from", from);
}
if (randomBoolean()) {
HipChatMessage.Format format = randomFrom(HipChatMessage.Format.values());
templateBuilder.setFormat(format);
builder.field("format", format.value());
}
if (randomBoolean()) {
Template color = Template.inline(randomFrom(HipChatMessage.Color.values()).value()).build();
templateBuilder.setColor(color);
builder.field("color", color);
}
if (randomBoolean()) {
boolean notify = randomBoolean();
templateBuilder.setNotify(notify);
builder.field("notify", notify);
}
builder.endObject();
builder.endObject();
HipChatMessage.Template template = templateBuilder.build();
HipChatAction action = new HipChatAction(accountName, template);
XContentBuilder jsonBuilder = jsonBuilder();
action.toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS);
BytesReference bytes = builder.bytes();
logger.info(bytes.toUtf8());
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
HipChatAction parsedAction = HipChatAction.parse("_watch", "_action", parser);
assertThat(parsedAction, notNullValue());
assertThat(parsedAction, is(action));
}
@Test(expected = ElasticsearchParseException.class)
public void testParser_Invalid() throws Exception {
XContentBuilder builder = jsonBuilder().startObject().field("unknown_field", "value");
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
HipChatAction.parse("_watch", "_action", parser);
}
}

View File

@ -0,0 +1,262 @@
/*
* 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.hipchat;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.MapBuilder;
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.hipchat.service.HipChatAccount;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatMessage;
import org.elasticsearch.watcher.actions.hipchat.service.HipChatService;
import org.elasticsearch.watcher.actions.hipchat.service.SentMessages;
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.template.Template;
import org.elasticsearch.watcher.support.template.TemplateEngine;
import org.elasticsearch.watcher.watch.Payload;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
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.test.WatcherTestUtils.mockExecutionContextBuilder;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
*/
public class HipChatActionTests extends ESTestCase {
private HipChatService service;
@Before
public void init() throws Exception {
service = mock(HipChatService.class);
}
@Test
public void testExecute() throws Exception {
final String accountName = "account1";
TemplateEngine templateEngine = mock(TemplateEngine.class);
Template body = Template.inline("_body").build();
HipChatMessage.Template.Builder messageBuilder = new HipChatMessage.Template.Builder(body);
HipChatMessage.Template messageTemplate = messageBuilder.build();
HipChatAction action = new HipChatAction(accountName, messageTemplate);
ExecutableHipChatAction executable = new ExecutableHipChatAction(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> expectedModel = ImmutableMap.<String, Object>builder()
.put("ctx", ImmutableMap.<String, Object>builder()
.put("id", ctx.id().value())
.put("watch_id", wid.watchId())
.put("payload", data)
.put("metadata", metadata)
.put("execution_time", now)
.put("trigger", ImmutableMap.<String, Object>builder()
.put("triggered_time", now)
.put("scheduled_time", now)
.build())
.put("vars", Collections.emptyMap())
.build())
.build();
if (body != null) {
when(templateEngine.render(body, expectedModel)).thenReturn(body.getTemplate());
}
String[] rooms = new String[] { "_r1" };
HipChatMessage message = new HipChatMessage(body.getTemplate(), rooms, null, null, null, null, null);
HipChatAccount account = mock(HipChatAccount.class);
when(account.render(wid.watchId(), "_id", templateEngine, messageTemplate, expectedModel)).thenReturn(message);
HttpResponse response = mock(HttpResponse.class);
when(response.status()).thenReturn(200);
HttpRequest request = mock(HttpRequest.class);
SentMessages sentMessages = new SentMessages(accountName, Arrays.asList(
SentMessages.SentMessage.responded("_r1", SentMessages.SentMessage.TargetType.ROOM, message, request, response)
));
when(account.send(message)).thenReturn(sentMessages);
when(service.getAccount(accountName)).thenReturn(account);
Action.Result result = executable.execute("_id", ctx, payload);
assertThat(result, notNullValue());
assertThat(result, instanceOf(HipChatAction.Result.Executed.class));
assertThat(result.status(), equalTo(Action.Result.Status.SUCCESS));
assertThat(((HipChatAction.Result.Executed) result).sentMessages(), sameInstance(sentMessages));
}
@Test
public void testParser() throws Exception {
XContentBuilder builder = jsonBuilder().startObject();
String accountName = randomAsciiOfLength(10);
builder.field("account", accountName);
builder.startObject("message");
Template body = Template.inline("_body").build();
builder.field("body", body);
Template[] rooms = null;
if (randomBoolean()) {
Template r1 = Template.inline("_r1").build();
Template r2 = Template.inline("_r2").build();
rooms = new Template[] { r1, r2 };
builder.array("room", r1, r2);
}
Template[] users = null;
if (randomBoolean()) {
Template u1 = Template.inline("_u1").build();
Template u2 = Template.inline("_u2").build();
users = new Template[] { u1, u2 };
builder.array("user", u1, u2);
}
String from = null;
if (randomBoolean()) {
from = randomAsciiOfLength(10);
builder.field("from", from);
}
HipChatMessage.Format format = null;
if (randomBoolean()) {
format = randomFrom(HipChatMessage.Format.values());
builder.field("format", format.value());
}
Template color = null;
if (randomBoolean()) {
color = Template.inline(randomFrom(HipChatMessage.Color.values()).value()).build();
builder.field("color", color);
}
Boolean notify = null;
if (randomBoolean()) {
notify = randomBoolean();
builder.field("notify", notify);
}
builder.endObject();
builder.endObject();
BytesReference bytes = builder.bytes();
logger.info("hipchat action json [{}]", bytes.toUtf8());
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
HipChatAction action = HipChatAction.parse("_watch", "_action", parser);
assertThat(action, notNullValue());
assertThat(action.account, is(accountName));
assertThat(action.message, notNullValue());
assertThat(action.message, is(new HipChatMessage.Template(body, rooms, users, from, format, color, notify)));
}
@Test
public void testParser_SelfGenerated() throws Exception {
String accountName = randomAsciiOfLength(10);
Template body = Template.inline("_body").build();
HipChatMessage.Template.Builder templateBuilder = new HipChatMessage.Template.Builder(body);
XContentBuilder builder = jsonBuilder().startObject();
builder.field("account", accountName);
builder.startObject("message");
builder.field("body", body);
if (randomBoolean()) {
Template r1 = Template.inline("_r1").build();
Template r2 = Template.inline("_r2").build();
templateBuilder.addRooms(r1, r2);
builder.array("room", r1, r2);
}
if (randomBoolean()) {
Template u1 = Template.inline("_u1").build();
Template u2 = Template.inline("_u2").build();
templateBuilder.addUsers(u1, u2);
builder.array("user", u1, u2);
}
if (randomBoolean()) {
String from = randomAsciiOfLength(10);
templateBuilder.setFrom(from);
builder.field("from", from);
}
if (randomBoolean()) {
HipChatMessage.Format format = randomFrom(HipChatMessage.Format.values());
templateBuilder.setFormat(format);
builder.field("format", format.value());
}
if (randomBoolean()) {
Template color = Template.inline(randomFrom(HipChatMessage.Color.values()).value()).build();
templateBuilder.setColor(color);
builder.field("color", color);
}
if (randomBoolean()) {
boolean notify = randomBoolean();
templateBuilder.setNotify(notify);
builder.field("notify", notify);
}
builder.endObject();
builder.endObject();
HipChatMessage.Template template = templateBuilder.build();
HipChatAction action = new HipChatAction(accountName, template);
XContentBuilder jsonBuilder = jsonBuilder();
action.toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS);
BytesReference bytes = builder.bytes();
logger.info(bytes.toUtf8());
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
HipChatAction parsedAction = HipChatAction.parse("_watch", "_action", parser);
assertThat(parsedAction, notNullValue());
assertThat(parsedAction, is(action));
}
@Test(expected = ElasticsearchParseException.class)
public void testParser_Invalid() throws Exception {
XContentBuilder builder = jsonBuilder().startObject().field("unknown_field", "value");
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
HipChatAction.parse("_watch", "_action", parser);
}
}

View File

@ -0,0 +1,129 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
/**
*
*/
public class HipChatAccountsTests extends ESTestCase {
private HttpClient httpClient;
@Before
public void init() throws Exception {
httpClient = mock(HttpClient.class);
}
@Test
public void testSingleAccount() throws Exception {
Settings.Builder builder = Settings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
HipChatAccounts accounts = new HipChatAccounts(builder.build(), httpClient, logger);
HipChatAccount 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"));
}
@Test
public void testSingleAccount_NoExplicitDefault() throws Exception {
Settings.Builder builder = Settings.builder();
addAccountSettings("account1", builder);
HipChatAccounts accounts = new HipChatAccounts(builder.build(), httpClient, logger);
HipChatAccount 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"));
}
@Test
public void testMultipleAccounts() throws Exception {
Settings.Builder builder = Settings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
HipChatAccounts accounts = new HipChatAccounts(builder.build(), httpClient, logger);
HipChatAccount 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"));
}
@Test
public void testMultipleAccounts_NoExplicitDefault() throws Exception {
Settings.Builder builder = Settings.builder()
.put("default_account", "account1");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
HipChatAccounts accounts = new HipChatAccounts(builder.build(), httpClient, logger);
HipChatAccount 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"));
}
@Test(expected = SettingsException.class)
public void testMultipleAccounts_UnknownDefault() throws Exception {
Settings.Builder builder = Settings.builder()
.put("default_account", "unknown");
addAccountSettings("account1", builder);
addAccountSettings("account2", builder);
new HipChatAccounts(builder.build(), httpClient, logger);
}
@Test(expected = IllegalStateException.class)
public void testNoAccount() throws Exception {
Settings.Builder builder = Settings.builder();
HipChatAccounts accounts = new HipChatAccounts(builder.build(), httpClient, logger);
accounts.account(null);
fail("no accounts are configured so trying to get the default account should throw an IllegalStateException");
}
@Test(expected = SettingsException.class)
public void testNoAccount_WithDefaultAccount() throws Exception {
Settings.Builder builder = Settings.builder()
.put("default_account", "unknown");
new HipChatAccounts(builder.build(), httpClient, logger);
}
private void addAccountSettings(String name, Settings.Builder builder) {
HipChatAccount.Profile profile = randomFrom(HipChatAccount.Profile.values());
builder.put("account." + name + ".profile", profile.value());
builder.put("account." + name + ".auth_token", randomAsciiOfLength(50));
if (profile == HipChatAccount.Profile.INTEGRATION) {
builder.put("account." + name + ".room", randomAsciiOfLength(10));
}
}
}

View File

@ -0,0 +1,291 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.bytes.BytesReference;
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.support.template.Template;
import org.elasticsearch.watcher.support.xcontent.WatcherXContentUtils;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.*;
/**
*
*/
public class HipChatMessageTests extends ESTestCase {
@Test
public void testToXContent() throws Exception {
String message = randomAsciiOfLength(10);
String[] rooms = generateRandomStringArray(3, 10, true);
String[] users = generateRandomStringArray(3, 10, true);
String from = randomBoolean() ? null : randomAsciiOfLength(10);
HipChatMessage.Format format = rarely() ? null : randomFrom(HipChatMessage.Format.values());
HipChatMessage.Color color = rarely() ? null : randomFrom(HipChatMessage.Color.values());
Boolean notify = rarely() ? null : randomBoolean();
HipChatMessage msg = new HipChatMessage(message, rooms, users, from, format, color, notify);
XContentBuilder builder = jsonBuilder();
boolean includeTarget = randomBoolean();
if (includeTarget && randomBoolean()) {
msg.toXContent(builder, ToXContent.EMPTY_PARAMS);
} else {
msg.toXContent(builder, ToXContent.EMPTY_PARAMS, includeTarget);
}
BytesReference bytes = builder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.START_OBJECT));
message = null;
rooms = null;
users = null;
from = null;
format = null;
color = null;
notify = null;
XContentParser.Token token = null;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if ("body".equals(currentFieldName)) {
message = parser.text();
} else if ("room".equals(currentFieldName)) {
rooms = WatcherXContentUtils.readStringArray(parser, false);
} else if ("user".equals(currentFieldName)) {
users = WatcherXContentUtils.readStringArray(parser, false);
} else if ("from".equals(currentFieldName)) {
from = parser.text();
} else if ("format".equals(currentFieldName)) {
format = HipChatMessage.Format.parse(parser);
} else if ("color".equals(currentFieldName)) {
color = HipChatMessage.Color.parse(parser);
} else if ("notify".equals(currentFieldName)) {
notify = parser.booleanValue();
} else {
fail("unexpected xconent field [" + currentFieldName + "] in hipchat message");
}
}
assertThat(message, notNullValue());
assertThat(message, is(msg.body));
if (includeTarget) {
if (msg.rooms == null || msg.rooms.length == 0) {
assertThat(rooms, nullValue());
} else {
assertThat(rooms, arrayContaining(msg.rooms));
}
if (msg.users == null || msg.users.length == 0) {
assertThat(users, nullValue());
} else {
assertThat(users, arrayContaining(msg.users));
}
}
assertThat(from, is(msg.from));
assertThat(format, is(msg.format));
assertThat(color, is(msg.color));
assertThat(notify, is(msg.notify));
}
@Test
public void testEquals() throws Exception {
String message = randomAsciiOfLength(10);
String[] rooms = generateRandomStringArray(3, 10, true);
String[] users = generateRandomStringArray(3, 10, true);
String from = randomBoolean() ? null : randomAsciiOfLength(10);
HipChatMessage.Format format = rarely() ? null : randomFrom(HipChatMessage.Format.values());
HipChatMessage.Color color = rarely() ? null : randomFrom(HipChatMessage.Color.values());
Boolean notify = rarely() ? null : randomBoolean();
HipChatMessage msg1 = new HipChatMessage(message, rooms, users, from, format, color, notify);
boolean equals = randomBoolean();
if (!equals) {
equals = true;
if (rarely()) {
equals = false;
message = "another message";
}
if (rarely()) {
equals = false;
rooms = rooms == null ? new String[] { "roomX" } : randomBoolean() ? null : new String[] { "roomX" , "roomY"};
}
if (rarely()) {
equals = false;
users = users == null ? new String[] { "userX" } : randomBoolean() ? null : new String[] { "userX", "userY" };
}
if (rarely()) {
equals = false;
from = from == null ? "fromX" : randomBoolean() ? null : "fromY";
}
if (rarely()) {
equals = false;
format = format == null ?
randomFrom(HipChatMessage.Format.values()) :
randomBoolean() ?
null :
randomFrom(HipChatMessage.Format.values(), format);
}
if (rarely()) {
equals = false;
color = color == null ?
randomFrom(HipChatMessage.Color.values()) :
randomBoolean() ?
null :
randomFrom(HipChatMessage.Color.values(), color);
}
if (rarely()) {
equals = false;
notify = notify == null ? (Boolean) randomBoolean() : randomBoolean() ? null : (Boolean) randomBoolean();
}
}
HipChatMessage msg2 = new HipChatMessage(message, rooms, users, from, format, color, notify);
assertThat(msg1.equals(msg2), is(equals));
}
@Test
public void testTemplate_Parse() throws Exception {
XContentBuilder jsonBuilder = jsonBuilder();
jsonBuilder.startObject();
Template body = Template.inline(randomAsciiOfLength(200)).build();
jsonBuilder.field("body", body, ToXContent.EMPTY_PARAMS);
Template[] rooms = null;
if (randomBoolean()) {
jsonBuilder.startArray("room");
rooms = new Template[randomIntBetween(1, 3)];
for (int i = 0; i < rooms.length; i++) {
rooms[i] = Template.inline(randomAsciiOfLength(10)).build();
rooms[i].toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS);
}
jsonBuilder.endArray();
}
Template[] users = null;
if (randomBoolean()) {
jsonBuilder.startArray("user");
users = new Template[randomIntBetween(1, 3)];
for (int i = 0; i < users.length; i++) {
users[i] = Template.inline(randomAsciiOfLength(10)).build();
users[i].toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS);
}
jsonBuilder.endArray();
}
String from = null;
if (randomBoolean()) {
from = randomAsciiOfLength(10);
jsonBuilder.field("from", from);
}
Template color = null;
if (randomBoolean()) {
color = Template.inline(randomAsciiOfLength(10)).build();
jsonBuilder.field("color", color, ToXContent.EMPTY_PARAMS);
}
HipChatMessage.Format format = null;
if (randomBoolean()) {
format = randomFrom(HipChatMessage.Format.values());
jsonBuilder.field("format", format, ToXContent.EMPTY_PARAMS);
}
Boolean notify = null;
if (randomBoolean()) {
notify = randomBoolean();
jsonBuilder.field("notify", notify);
}
BytesReference bytes = jsonBuilder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
HipChatMessage.Template template = HipChatMessage.Template.parse(parser);
assertThat(template, notNullValue());
assertThat(template.body, is(body));
if (rooms == null) {
assertThat(template.rooms, nullValue());
} else {
assertThat(template.rooms, arrayContaining(rooms));
}
if (users == null) {
assertThat(template.users, nullValue());
} else {
assertThat(template.users, arrayContaining(users));
}
assertThat(template.from, is(from));
assertThat(template.color, is(color));
assertThat(template.format, is(format));
assertThat(template.notify, is(notify));
}
@Test
public void testTemplate_ParseSelfGenerated() throws Exception {
Template body = Template.inline(randomAsciiOfLength(10)).build();
HipChatMessage.Template.Builder templateBuilder = new HipChatMessage.Template.Builder(body);
if (randomBoolean()) {
int count = randomIntBetween(1, 3);
for (int i = 0; i < count; i++) {
templateBuilder.addRooms(Template.inline(randomAsciiOfLength(10)).build());
}
}
if (randomBoolean()) {
int count = randomIntBetween(1, 3);
for (int i = 0; i < count; i++) {
templateBuilder.addUsers(Template.inline(randomAsciiOfLength(10)).build());
}
}
if (randomBoolean()) {
templateBuilder.setFrom(randomAsciiOfLength(10));
}
if (randomBoolean()) {
templateBuilder.setColor(Template.inline(randomAsciiOfLength(5)).build());
}
if (randomBoolean()) {
templateBuilder.setFormat(randomFrom(HipChatMessage.Format.values()));
}
if (randomBoolean()) {
templateBuilder.setNotify(randomBoolean());
}
HipChatMessage.Template template = templateBuilder.build();
XContentBuilder jsonBuilder = jsonBuilder();
template.toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS);
BytesReference bytes = jsonBuilder.bytes();
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
parser.nextToken();
HipChatMessage.Template parsed = HipChatMessage.Template.parse(parser);
assertThat(parsed, equalTo(template));
}
static <E extends Enum> E randomFrom(E[] values, E... exclude) {
List<E> excludes = Arrays.asList(exclude);
List<E> includes = new ArrayList<>();
for (E value : values) {
if (!excludes.contains(value)) {
includes.add(value);
}
}
return randomFrom(includes);
}
}

View File

@ -0,0 +1,202 @@
/*
* 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.hipchat.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.hipchat.HipChatAction;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import org.junit.Before;
import org.junit.Test;
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.watcher.actions.ActionBuilders.hipchatAction;
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.*;
/**
*
*/
@Network
public class HipChatServiceIT extends AbstractWatcherIntegrationTests {
private HipChatService service;
@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))
// this is for the `test-watcher-integration` group level integration in HipChat
.put("watcher.actions.hipchat.service.account.integration_account.profile", "integration")
.put("watcher.actions.hipchat.service.account.integration_account.auth_token", "huuS9v7ccuOy3ZBWWWr1vt8Lqu3sQnLUE81nrLZU")
.put("watcher.actions.hipchat.service.account.integration_account.room", "test-watcher")
// this is for the Watcher Test account in HipChat
.put("watcher.actions.hipchat.service.account.user_account.profile", "user")
.put("watcher.actions.hipchat.service.account.user_account.auth_token", "FYVx16oDH78ZW9r13wtXbcszyoyA7oX5tiMWg9X0")
// this is for the `test-watcher-v1` notification token
.put("watcher.actions.hipchat.service.account.v1_account.profile", "v1")
.put("watcher.actions.hipchat.service.account.v1_account.auth_token", "a734baf62df618b96dda55b323fc30")
.build();
}
@Before
public void init() throws Exception {
service = getInstanceFromMaster(HipChatService.class);
}
@Test
public void testSendMessage_V1Account() throws Exception {
HipChatMessage hipChatMessage = new HipChatMessage(
"/code HipChatServiceIT#testSendMessage_V1Account",
new String[] { "test-watcher", "test-watcher-2" },
null, // users are unsupported in v1
"watcher-tests",
HipChatMessage.Format.TEXT,
randomFrom(HipChatMessage.Color.values()),
true);
HipChatAccount account = service.getAccount("v1_account");
assertThat(account, notNullValue());
SentMessages messages = account.send(hipChatMessage);
assertThat(messages.count(), is(2));
for (SentMessages.SentMessage message : messages) {
assertThat(message.successful(), is(true));
assertThat(message.request, notNullValue());
assertThat(message.response, notNullValue());
assertThat(message.response.status(), lessThan(300));
}
}
@Test
public void testSendMessage_IntegrationAccount() throws Exception {
HipChatMessage hipChatMessage = new HipChatMessage(
"/code HipChatServiceIT#testSendMessage_IntegrationAccount",
null, // custom rooms are unsupported by integration profiles
null, // users are unsupported by integration profiles
null, // custom "from" is not supported by integration profiles
HipChatMessage.Format.TEXT,
randomFrom(HipChatMessage.Color.values()),
true);
HipChatAccount account = service.getAccount("integration_account");
assertThat(account, notNullValue());
SentMessages messages = account.send(hipChatMessage);
assertThat(messages.count(), is(1));
for (SentMessages.SentMessage message : messages) {
assertThat(message.successful(), is(true));
assertThat(message.request, notNullValue());
assertThat(message.response, notNullValue());
assertThat(message.response.status(), lessThan(300));
}
}
@Test
public void testSendMessage_UserAccount() throws Exception {
HipChatMessage hipChatMessage = new HipChatMessage(
"/code HipChatServiceIT#testSendMessage_UserAccount",
new String[] { "test-watcher", "test-watcher-2" },
new String[] { "watcher@elastic.co" },
null, // custom "from" is not supported by integration profiles
HipChatMessage.Format.TEXT,
randomFrom(HipChatMessage.Color.values()),
false);
HipChatAccount account = service.getAccount("user_account");
assertThat(account, notNullValue());
SentMessages messages = account.send(hipChatMessage);
assertThat(messages.count(), is(3));
for (SentMessages.SentMessage message : messages) {
assertThat(message.successful(), is(true));
assertThat(message.request, notNullValue());
assertThat(message.response, notNullValue());
assertThat(message.response.status(), lessThan(300));
}
}
@Test
public void testWatchWithHipChatAction() throws Exception {
HipChatAccount.Profile profile = randomFrom(HipChatAccount.Profile.values());
String account;
HipChatAction.Builder actionBuilder;
switch (profile) {
case USER:
account = "user_account";
actionBuilder = hipchatAction(account, "/code {{ctx.payload.ref}}")
.addRooms("test-watcher", "test-watcher-2")
.addUsers("watcher@elastic.co")
.setFormat(HipChatMessage.Format.TEXT)
.setColor(randomFrom(HipChatMessage.Color.values()))
.setNotify(false);
break;
case INTEGRATION:
account = "integration_account";
actionBuilder = hipchatAction(account, "/code {{ctx.payload.ref}}")
.setFormat(HipChatMessage.Format.TEXT)
.setColor(randomFrom(HipChatMessage.Color.values()))
.setNotify(false);
break;
default:
assertThat(profile, is(HipChatAccount.Profile.V1));
account = "v1_account";
actionBuilder = hipchatAction(account, "/code {{ctx.payload.ref}}")
.addRooms("test-watcher", "test-watcher-2")
.setFrom("watcher-test")
.setFormat(HipChatMessage.Format.TEXT)
.setColor(randomFrom(HipChatMessage.Color.values()))
.setNotify(false);
}
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("1").setSource(watchBuilder()
.trigger(schedule(interval("10m")))
.input(simpleInput("ref", "HipChatServiceIT#testWatchWithHipChatAction"))
.condition(alwaysCondition())
.addAction("hipchat", 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", "hipchat"))
.must(termQuery("result.actions.type", "hipchat"))
.must(termQuery("result.actions.status", "success"))
.must(termQuery("result.actions.hipchat.account", account))
.must(termQuery("result.actions.hipchat.sent_messages.status", "success"))));
assertThat(response, notNullValue());
assertThat(response.getHits().getTotalHits(), is(1L));
}
}

View File

@ -0,0 +1,151 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.support.http.*;
import org.junit.Test;
import java.io.IOException;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
/**
*
*/
public class IntegrationAccountTests extends ESTestCase {
@Test
public void testSettings() throws Exception {
String accountName = "_name";
Settings.Builder sb = Settings.builder();
String authToken = randomAsciiOfLength(50);
sb.put(IntegrationAccount.AUTH_TOKEN_SETTING, authToken);
String host = HipChatServer.DEFAULT.host();
if (randomBoolean()) {
host = randomAsciiOfLength(10);
sb.put(HipChatServer.HOST_SETTING, host);
}
int port = HipChatServer.DEFAULT.port();
if (randomBoolean()) {
port = randomIntBetween(300, 400);
sb.put(HipChatServer.PORT_SETTING, port);
}
String room = randomAsciiOfLength(10);
sb.put(IntegrationAccount.ROOM_SETTING, room);
HipChatMessage.Format defaultFormat = null;
if (randomBoolean()) {
defaultFormat = randomFrom(HipChatMessage.Format.values());
sb.put(HipChatAccount.DEFAULT_FORMAT_SETTING, defaultFormat);
}
HipChatMessage.Color defaultColor = null;
if (randomBoolean()) {
defaultColor = randomFrom(HipChatMessage.Color.values());
sb.put(HipChatAccount.DEFAULT_COLOR_SETTING, defaultColor);
}
Boolean defaultNotify = null;
if (randomBoolean()) {
defaultNotify = randomBoolean();
sb.put(HipChatAccount.DEFAULT_NOTIFY_SETTING, defaultNotify);
}
Settings settings = sb.build();
IntegrationAccount account = new IntegrationAccount(accountName, settings, HipChatServer.DEFAULT, mock(HttpClient.class), mock(ESLogger.class));
assertThat(account.profile, is(HipChatAccount.Profile.INTEGRATION));
assertThat(account.name, equalTo(accountName));
assertThat(account.server.host(), is(host));
assertThat(account.server.port(), is(port));
assertThat(account.authToken, is(authToken));
assertThat(account.room, is(room));
assertThat(account.defaults.format, is(defaultFormat));
assertThat(account.defaults.color, is(defaultColor));
assertThat(account.defaults.notify, is(defaultNotify));
}
@Test(expected = SettingsException.class)
public void testSettings_NoAuthToken() throws Exception {
Settings.Builder sb = Settings.builder();
sb.put(IntegrationAccount.ROOM_SETTING, randomAsciiOfLength(10));
new IntegrationAccount("_name", sb.build(), HipChatServer.DEFAULT, mock(HttpClient.class), mock(ESLogger.class));
}
@Test(expected = SettingsException.class)
public void testSettings_WithoutRoom() throws Exception {
Settings.Builder sb = Settings.builder();
sb.put(IntegrationAccount.AUTH_TOKEN_SETTING, randomAsciiOfLength(50));
new IntegrationAccount("_name", sb.build(), HipChatServer.DEFAULT, mock(HttpClient.class), mock(ESLogger.class));
}
@Test(expected = SettingsException.class)
public void testSettings_WithoutMultipleRooms() throws Exception {
Settings.Builder sb = Settings.builder();
sb.put(IntegrationAccount.AUTH_TOKEN_SETTING, randomAsciiOfLength(50));
sb.put(IntegrationAccount.ROOM_SETTING, "_r1,_r2");
new IntegrationAccount("_name", sb.build(), HipChatServer.DEFAULT, mock(HttpClient.class), mock(ESLogger.class));
}
@Test
public void testSend() throws Exception {
HttpClient httpClient = mock(HttpClient.class);
IntegrationAccount account = new IntegrationAccount("_name", Settings.builder()
.put("host", "_host")
.put("port", "443")
.put("auth_token", "_token")
.put("room", "_room")
.build(), HipChatServer.DEFAULT, httpClient, mock(ESLogger.class));
HipChatMessage.Format format = randomFrom(HipChatMessage.Format.values());
HipChatMessage.Color color = randomFrom(HipChatMessage.Color.values());
Boolean notify = randomBoolean();
final HipChatMessage message = new HipChatMessage("_body", null, null, null, format, color, notify);
HttpRequest req = HttpRequest.builder("_host", 443)
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v2/room/_room/notification")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer _token")
.body(XContentHelper.toString(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("message", message.body);
if (message.format != null) {
builder.field("message_format", message.format.value());
}
if (message.notify != null) {
builder.field("notify", message.notify);
}
if (message.color != null) {
builder.field("color", String.valueOf(message.color.value()));
}
return builder;
}
}))
.build();
HttpResponse res = mock(HttpResponse.class);
when(res.status()).thenReturn(200);
when(httpClient.execute(req)).thenReturn(res);
account.send(message);
verify(httpClient).execute(req);
}
}

View File

@ -0,0 +1,281 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.shield.WatcherSettingsFilter;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
/**
*
*/
public class InternalHipChatServiceTests extends ESTestCase {
private HttpClient httpClient;
private NodeSettingsService nodeSettingsService;
private WatcherSettingsFilter settingsFilter;
@Before
public void init() throws Exception {
httpClient = mock(HttpClient.class);
nodeSettingsService = mock(NodeSettingsService.class);
settingsFilter = mock(WatcherSettingsFilter.class);
}
@Test
public void testSingleAccount_V1() throws Exception {
String accountName = randomAsciiOfLength(10);
String host = randomBoolean() ? null : "_host";
int port = randomBoolean() ? -1 : randomIntBetween(300, 400);
String defaultRoom = randomBoolean() ? null : "_r1, _r2";
String defaultFrom = randomBoolean() ? null : "_from";
HipChatMessage.Color defaultColor = randomBoolean() ? null : randomFrom(HipChatMessage.Color.values());
HipChatMessage.Format defaultFormat = randomBoolean() ? null : randomFrom(HipChatMessage.Format.values());
Boolean defaultNotify = randomBoolean() ? null : (Boolean) randomBoolean();
Settings.Builder settingsBuilder = Settings.builder()
.put("watcher.actions.hipchat.service.account." + accountName + ".profile", HipChatAccount.Profile.V1.value())
.put("watcher.actions.hipchat.service.account." + accountName + ".auth_token", "_token");
if (host != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + accountName + ".host", host);
}
if (port > 0) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + accountName + ".port", port);
}
buildMessageDefaults(accountName, settingsBuilder, defaultRoom, null, defaultFrom, defaultColor, defaultFormat, defaultNotify);
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient, nodeSettingsService, settingsFilter);
service.start();
HipChatAccount account = service.getAccount(accountName);
assertThat(account, notNullValue());
assertThat(account.name, is(accountName));
assertThat(account.authToken, is("_token"));
assertThat(account.profile, is(HipChatAccount.Profile.V1));
assertThat(account.httpClient, is(httpClient));
assertThat(account.server, notNullValue());
assertThat(account.server.host(), is(host != null ? host : HipChatServer.DEFAULT.host()));
assertThat(account.server.port(), is(port > 0 ? port : HipChatServer.DEFAULT.port()));
assertThat(account, instanceOf(V1Account.class));
if (defaultRoom == null) {
assertThat(((V1Account) account).defaults.rooms, nullValue());
} else {
assertThat(((V1Account) account).defaults.rooms, arrayContaining("_r1", "_r2"));
}
assertThat(((V1Account) account).defaults.from, is(defaultFrom));
assertThat(((V1Account) account).defaults.color, is(defaultColor));
assertThat(((V1Account) account).defaults.format, is(defaultFormat));
assertThat(((V1Account) account).defaults.notify, is(defaultNotify));
// with a single account defined, making sure that that account is set to the default one.
assertThat(service.getDefaultAccount(), sameInstance(account));
assertThatSettingsFilterWasAdded();
}
@Test
public void testSingleAccount_Integration() throws Exception {
String accountName = randomAsciiOfLength(10);
String host = randomBoolean() ? null : "_host";
int port = randomBoolean() ? -1 : randomIntBetween(300, 400);
String room = randomAsciiOfLength(10);
String defaultFrom = randomBoolean() ? null : "_from";
HipChatMessage.Color defaultColor = randomBoolean() ? null : randomFrom(HipChatMessage.Color.values());
HipChatMessage.Format defaultFormat = randomBoolean() ? null : randomFrom(HipChatMessage.Format.values());
Boolean defaultNotify = randomBoolean() ? null : (Boolean) randomBoolean();
Settings.Builder settingsBuilder = Settings.builder()
.put("watcher.actions.hipchat.service.account." + accountName + ".profile", HipChatAccount.Profile.INTEGRATION.value())
.put("watcher.actions.hipchat.service.account." + accountName + ".auth_token", "_token")
.put("watcher.actions.hipchat.service.account." + accountName + ".room", room);
if (host != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + accountName + ".host", host);
}
if (port > 0) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + accountName + ".port", port);
}
buildMessageDefaults(accountName, settingsBuilder, null, null, defaultFrom, defaultColor, defaultFormat, defaultNotify);
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient, nodeSettingsService, settingsFilter);
service.start();
HipChatAccount account = service.getAccount(accountName);
assertThat(account, notNullValue());
assertThat(account.name, is(accountName));
assertThat(account.authToken, is("_token"));
assertThat(account.profile, is(HipChatAccount.Profile.INTEGRATION));
assertThat(account.httpClient, is(httpClient));
assertThat(account.server, notNullValue());
assertThat(account.server.host(), is(host != null ? host : HipChatServer.DEFAULT.host()));
assertThat(account.server.port(), is(port > 0 ? port : HipChatServer.DEFAULT.port()));
assertThat(account, instanceOf(IntegrationAccount.class));
assertThat(((IntegrationAccount) account).room, is(room));
assertThat(((IntegrationAccount) account).defaults.color, is(defaultColor));
assertThat(((IntegrationAccount) account).defaults.format, is(defaultFormat));
assertThat(((IntegrationAccount) account).defaults.notify, is(defaultNotify));
// with a single account defined, making sure that that account is set to the default one.
assertThat(service.getDefaultAccount(), sameInstance(account));
assertThatSettingsFilterWasAdded();
}
@Test(expected = SettingsException.class)
public void testSingleAccount_Integration_NoRoomSetting() throws Exception {
String accountName = randomAsciiOfLength(10);
Settings.Builder settingsBuilder = Settings.builder()
.put("watcher.actions.hipchat.service.account." + accountName + ".profile", HipChatAccount.Profile.INTEGRATION.value())
.put("watcher.actions.hipchat.service.account." + accountName + ".auth_token", "_token");
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient, nodeSettingsService, settingsFilter);
service.start();
}
@Test
public void testSingleAccount_User() throws Exception {
String accountName = randomAsciiOfLength(10);
String host = randomBoolean() ? null : "_host";
int port = randomBoolean() ? -1 : randomIntBetween(300, 400);
String defaultRoom = randomBoolean() ? null : "_r1, _r2";
String defaultUser = randomBoolean() ? null : "_u1, _u2";
HipChatMessage.Color defaultColor = randomBoolean() ? null : randomFrom(HipChatMessage.Color.values());
HipChatMessage.Format defaultFormat = randomBoolean() ? null : randomFrom(HipChatMessage.Format.values());
Boolean defaultNotify = randomBoolean() ? null : (Boolean) randomBoolean();
Settings.Builder settingsBuilder = Settings.builder()
.put("watcher.actions.hipchat.service.account." + accountName + ".profile", HipChatAccount.Profile.USER.value())
.put("watcher.actions.hipchat.service.account." + accountName + ".auth_token", "_token");
if (host != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + accountName + ".host", host);
}
if (port > 0) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + accountName + ".port", port);
}
buildMessageDefaults(accountName, settingsBuilder, defaultRoom, defaultUser, null, defaultColor, defaultFormat, defaultNotify);
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient, nodeSettingsService, settingsFilter);
service.start();
HipChatAccount account = service.getAccount(accountName);
assertThat(account, notNullValue());
assertThat(account.name, is(accountName));
assertThat(account.authToken, is("_token"));
assertThat(account.profile, is(HipChatAccount.Profile.USER));
assertThat(account.httpClient, is(httpClient));
assertThat(account.server, notNullValue());
assertThat(account.server.host(), is(host != null ? host : HipChatServer.DEFAULT.host()));
assertThat(account.server.port(), is(port > 0 ? port : HipChatServer.DEFAULT.port()));
assertThat(account, instanceOf(UserAccount.class));
if (defaultRoom == null) {
assertThat(((UserAccount) account).defaults.rooms, nullValue());
} else {
assertThat(((UserAccount) account).defaults.rooms, arrayContaining("_r1", "_r2"));
}
if (defaultUser == null) {
assertThat(((UserAccount) account).defaults.users, nullValue());
} else {
assertThat(((UserAccount) account).defaults.users, arrayContaining("_u1", "_u2"));
}
assertThat(((UserAccount) account).defaults.color, is(defaultColor));
assertThat(((UserAccount) account).defaults.format, is(defaultFormat));
assertThat(((UserAccount) account).defaults.notify, is(defaultNotify));
// with a single account defined, making sure that that account is set to the default one.
assertThat(service.getDefaultAccount(), sameInstance(account));
assertThatSettingsFilterWasAdded();
}
@Test
public void testMultipleAccounts() throws Exception {
HipChatMessage.Color defaultColor = randomBoolean() ? null : randomFrom(HipChatMessage.Color.values());
HipChatMessage.Format defaultFormat = randomBoolean() ? null : randomFrom(HipChatMessage.Format.values());
Boolean defaultNotify = randomBoolean() ? null : (Boolean) randomBoolean();
Settings.Builder settingsBuilder = Settings.builder();
String defaultAccount = "_a" + randomIntBetween(0, 4);
settingsBuilder.put("watcher.actions.hipchat.service.default_account", defaultAccount);
boolean customGlobalServer = randomBoolean();
if (customGlobalServer) {
settingsBuilder.put("watcher.actions.hipchat.service.host", "_host_global");
settingsBuilder.put("watcher.actions.hipchat.service.port", 299);
}
for (int i = 0; i < 5; i++) {
String name = "_a" + i;
String prefix = "watcher.actions.hipchat.service.account." + name;
HipChatAccount.Profile profile = randomFrom(HipChatAccount.Profile.values());
settingsBuilder.put(prefix + ".profile", profile);
settingsBuilder.put(prefix + ".auth_token", "_token" + i);
if (profile == HipChatAccount.Profile.INTEGRATION) {
settingsBuilder.put(prefix + ".room", "_room" + i);
}
if (i % 2 == 0) {
settingsBuilder.put(prefix + ".host", "_host" + i);
settingsBuilder.put(prefix + ".port", 300 + i);
}
buildMessageDefaults(name, settingsBuilder, null, null, null, defaultColor, defaultFormat, defaultNotify);
}
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient, nodeSettingsService, settingsFilter);
service.start();
for (int i = 0; i < 5; i++) {
String name = "_a" + i;
HipChatAccount account = service.getAccount(name);
assertThat(account, notNullValue());
assertThat(account.name, is(name));
assertThat(account.authToken, is("_token" + i));
assertThat(account.profile, notNullValue());
if (account.profile == HipChatAccount.Profile.INTEGRATION) {
assertThat(account, instanceOf(IntegrationAccount.class));
assertThat(((IntegrationAccount) account).room, is("_room" + i));
}
assertThat(account.httpClient, is(httpClient));
assertThat(account.server, notNullValue());
if (i % 2 == 0) {
assertThat(account.server.host(), is("_host" + i));
assertThat(account.server.port(), is(300 + i));
} else if (customGlobalServer) {
assertThat(account.server.host(), is("_host_global"));
assertThat(account.server.port(), is(299));
} else {
assertThat(account.server.host(), is(HipChatServer.DEFAULT.host()));
assertThat(account.server.port(), is(HipChatServer.DEFAULT.port()));
}
}
assertThat(service.getDefaultAccount(), sameInstance(service.getAccount(defaultAccount)));
assertThatSettingsFilterWasAdded();
}
private void assertThatSettingsFilterWasAdded() {
verify(settingsFilter, times(1)).filterOut("watcher.actions.hipchat.service.account.*.auth_token");
}
private void buildMessageDefaults(String account, Settings.Builder settingsBuilder, String room, String user, String from, HipChatMessage.Color color, HipChatMessage.Format format, Boolean notify) {
if (room != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + account + ".message_defaults.room", room);
}
if (user != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + account + ".message_defaults.user", user);
}
if (from != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + account + ".message_defaults.from", from);
}
if (color != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + account + ".message_defaults.color", color.value());
}
if (format != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + account + ".message_defaults.format", format);
}
if (notify != null) {
settingsBuilder.put("watcher.actions.hipchat.service.account." + account + ".message_defaults.notify", notify);
}
}
}

View File

@ -0,0 +1,239 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.support.http.*;
import org.junit.Test;
import java.io.IOException;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
/**
*
*/
public class UserAccountTests extends ESTestCase {
@Test
public void testSettings() throws Exception {
String accountName = "_name";
Settings.Builder sb = Settings.builder();
String authToken = randomAsciiOfLength(50);
sb.put(UserAccount.AUTH_TOKEN_SETTING, authToken);
String host = HipChatServer.DEFAULT.host();
if (randomBoolean()) {
host = randomAsciiOfLength(10);
sb.put(HipChatServer.HOST_SETTING, host);
}
int port = HipChatServer.DEFAULT.port();
if (randomBoolean()) {
port = randomIntBetween(300, 400);
sb.put(HipChatServer.PORT_SETTING, port);
}
String[] defaultRooms = null;
if (randomBoolean()) {
defaultRooms = new String[] { "_r1", "_r2" };
sb.put(HipChatAccount.DEFAULT_ROOM_SETTING, "_r1,_r2");
}
String[] defaultUsers = null;
if (randomBoolean()) {
defaultUsers = new String[] { "_u1", "_u2" };
sb.put(HipChatAccount.DEFAULT_USER_SETTING, "_u1,_u2");
}
HipChatMessage.Format defaultFormat = null;
if (randomBoolean()) {
defaultFormat = randomFrom(HipChatMessage.Format.values());
sb.put(HipChatAccount.DEFAULT_FORMAT_SETTING, defaultFormat);
}
HipChatMessage.Color defaultColor = null;
if (randomBoolean()) {
defaultColor = randomFrom(HipChatMessage.Color.values());
sb.put(HipChatAccount.DEFAULT_COLOR_SETTING, defaultColor);
}
Boolean defaultNotify = null;
if (randomBoolean()) {
defaultNotify = randomBoolean();
sb.put(HipChatAccount.DEFAULT_NOTIFY_SETTING, defaultNotify);
}
Settings settings = sb.build();
UserAccount account = new UserAccount(accountName, settings, HipChatServer.DEFAULT, mock(HttpClient.class), mock(ESLogger.class));
assertThat(account.profile, is(HipChatAccount.Profile.USER));
assertThat(account.name, equalTo(accountName));
assertThat(account.server.host(), is(host));
assertThat(account.server.port(), is(port));
assertThat(account.authToken, is(authToken));
if (defaultRooms != null) {
assertThat(account.defaults.rooms, arrayContaining(defaultRooms));
} else {
assertThat(account.defaults.rooms, nullValue());
}
if (defaultUsers != null) {
assertThat(account.defaults.users, arrayContaining(defaultUsers));
} else {
assertThat(account.defaults.users, nullValue());
}
assertThat(account.defaults.format, is(defaultFormat));
assertThat(account.defaults.color, is(defaultColor));
assertThat(account.defaults.notify, is(defaultNotify));
}
@Test(expected = SettingsException.class)
public void testSettings_NoAuthToken() throws Exception {
Settings.Builder sb = Settings.builder();
new UserAccount("_name", sb.build(), HipChatServer.DEFAULT, mock(HttpClient.class), mock(ESLogger.class));
}
@Test
public void testSend() throws Exception {
HttpClient httpClient = mock(HttpClient.class);
UserAccount account = new UserAccount("_name", Settings.builder()
.put("host", "_host")
.put("port", "443")
.put("auth_token", "_token")
.build(), HipChatServer.DEFAULT, httpClient, mock(ESLogger.class));
HipChatMessage.Format format = randomFrom(HipChatMessage.Format.values());
HipChatMessage.Color color = randomFrom(HipChatMessage.Color.values());
Boolean notify = randomBoolean();
final HipChatMessage message = new HipChatMessage("_body", new String[] { "_r1", "_r2" }, new String[] { "_u1", "_u2" }, null, format, color, notify);
HttpRequest reqR1 = HttpRequest.builder("_host", 443)
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v2/room/_r1/notification")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer _token")
.body(XContentHelper.toString(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("message", message.body);
if (message.format != null) {
builder.field("message_format", message.format.value());
}
if (message.notify != null) {
builder.field("notify", message.notify);
}
if (message.color != null) {
builder.field("color", String.valueOf(message.color.value()));
}
return builder;
}
}))
.build();
logger.info("expected (r1): " + jsonBuilder().value(reqR1).bytes().toUtf8());
HttpResponse resR1 = mock(HttpResponse.class);
when(resR1.status()).thenReturn(200);
when(httpClient.execute(reqR1)).thenReturn(resR1);
HttpRequest reqR2 = HttpRequest.builder("_host", 443)
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v2/room/_r2/notification")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer _token")
.body(XContentHelper.toString(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("message", message.body);
if (message.format != null) {
builder.field("message_format", message.format.value());
}
if (message.notify != null) {
builder.field("notify", message.notify);
}
if (message.color != null) {
builder.field("color", String.valueOf(message.color.value()));
}
return builder;
}
}))
.build();
logger.info("expected (r2): " + jsonBuilder().value(reqR1).bytes().toUtf8());
HttpResponse resR2 = mock(HttpResponse.class);
when(resR2.status()).thenReturn(200);
when(httpClient.execute(reqR2)).thenReturn(resR2);
HttpRequest reqU1 = HttpRequest.builder("_host", 443)
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v2/user/_u1/message")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer _token")
.body(XContentHelper.toString(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("message", message.body);
if (message.format != null) {
builder.field("message_format", message.format.value());
}
if (message.notify != null) {
builder.field("notify", message.notify);
}
return builder;
}
}))
.build();
logger.info("expected (u1): " + jsonBuilder().value(reqU1).bytes().toUtf8());
HttpResponse resU1 = mock(HttpResponse.class);
when(resU1.status()).thenReturn(200);
when(httpClient.execute(reqU1)).thenReturn(resU1);
HttpRequest reqU2 = HttpRequest.builder("_host", 443)
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v2/user/_u2/message")
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer _token")
.body(XContentHelper.toString(new ToXContent() {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("message", message.body);
if (message.format != null) {
builder.field("message_format", message.format.value());
}
if (message.notify != null) {
builder.field("notify", message.notify);
}
return builder;
}
}))
.build();
logger.info("expected (u2): " + jsonBuilder().value(reqU2).bytes().toUtf8());
HttpResponse resU2 = mock(HttpResponse.class);
when(resU2.status()).thenReturn(200);
when(httpClient.execute(reqU2)).thenReturn(resU2);
account.send(message);
verify(httpClient).execute(reqR1);
verify(httpClient).execute(reqR2);
verify(httpClient).execute(reqU2);
verify(httpClient).execute(reqU2);
}
}

View File

@ -0,0 +1,160 @@
/*
* 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.hipchat.service;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.support.http.*;
import org.junit.Test;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
/**
*
*/
public class V1AccountTests extends ESTestCase {
@Test
public void testSettings() throws Exception {
String accountName = "_name";
Settings.Builder sb = Settings.builder();
String authToken = randomAsciiOfLength(50);
sb.put(V1Account.AUTH_TOKEN_SETTING, authToken);
String host = HipChatServer.DEFAULT.host();
if (randomBoolean()) {
host = randomAsciiOfLength(10);
sb.put(HipChatServer.HOST_SETTING, host);
}
int port = HipChatServer.DEFAULT.port();
if (randomBoolean()) {
port = randomIntBetween(300, 400);
sb.put(HipChatServer.PORT_SETTING, port);
}
String[] defaultRooms = null;
if (randomBoolean()) {
defaultRooms = new String[] { "_r1", "_r2" };
sb.put(HipChatAccount.DEFAULT_ROOM_SETTING, "_r1,_r2");
}
String defaultFrom = null;
if (randomBoolean()) {
defaultFrom = randomAsciiOfLength(10);
sb.put(HipChatAccount.DEFAULT_FROM_SETTING, defaultFrom);
}
HipChatMessage.Format defaultFormat = null;
if (randomBoolean()) {
defaultFormat = randomFrom(HipChatMessage.Format.values());
sb.put(HipChatAccount.DEFAULT_FORMAT_SETTING, defaultFormat);
}
HipChatMessage.Color defaultColor = null;
if (randomBoolean()) {
defaultColor = randomFrom(HipChatMessage.Color.values());
sb.put(HipChatAccount.DEFAULT_COLOR_SETTING, defaultColor);
}
Boolean defaultNotify = null;
if (randomBoolean()) {
defaultNotify = randomBoolean();
sb.put(HipChatAccount.DEFAULT_NOTIFY_SETTING, defaultNotify);
}
Settings settings = sb.build();
V1Account account = new V1Account(accountName, settings, HipChatServer.DEFAULT, mock(HttpClient.class), mock(ESLogger.class));
assertThat(account.profile, is(HipChatAccount.Profile.V1));
assertThat(account.name, equalTo(accountName));
assertThat(account.server.host(), is(host));
assertThat(account.server.port(), is(port));
assertThat(account.authToken, is(authToken));
if (defaultRooms != null) {
assertThat(account.defaults.rooms, arrayContaining(defaultRooms));
} else {
assertThat(account.defaults.rooms, nullValue());
}
assertThat(account.defaults.from, is(defaultFrom));
assertThat(account.defaults.format, is(defaultFormat));
assertThat(account.defaults.color, is(defaultColor));
assertThat(account.defaults.notify, is(defaultNotify));
}
@Test(expected = SettingsException.class)
public void testSettings_NoAuthToken() throws Exception {
Settings.Builder sb = Settings.builder();
new V1Account("_name", sb.build(), HipChatServer.DEFAULT, mock(HttpClient.class), mock(ESLogger.class));
}
@Test
public void testSend() throws Exception {
HttpClient httpClient = mock(HttpClient.class);
V1Account account = new V1Account("_name", Settings.builder()
.put("host", "_host")
.put("port", "443")
.put("auth_token", "_token")
.build(), HipChatServer.DEFAULT, httpClient, mock(ESLogger.class));
HipChatMessage.Format format = randomFrom(HipChatMessage.Format.values());
HipChatMessage.Color color = randomFrom(HipChatMessage.Color.values());
Boolean notify = randomBoolean();
HipChatMessage message = new HipChatMessage("_body", new String[] { "_r1", "_r2" }, null, "_from", format, color, notify);
HttpRequest req1 = HttpRequest.builder("_host", 443)
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v1/rooms/message")
.setHeader("Content-Type", "application/x-www-form-urlencoded")
.setParam("format", "json")
.setParam("auth_token", "_token")
.body(new StringBuilder()
.append("room_id=").append("_r1&")
.append("from=").append("_from&")
.append("message=").append("_body&")
.append("message_format=").append(format.value()).append("&")
.append("color=").append(color.value()).append("&")
.append("notify=").append(notify ? "1" : "0")
.toString())
.build();
logger.info("expected (r1): " + jsonBuilder().value(req1).bytes().toUtf8());
HttpResponse res1 = mock(HttpResponse.class);
when(res1.status()).thenReturn(200);
when(httpClient.execute(req1)).thenReturn(res1);
HttpRequest req2 = HttpRequest.builder("_host", 443)
.method(HttpMethod.POST)
.scheme(Scheme.HTTPS)
.path("/v1/rooms/message")
.setHeader("Content-Type", "application/x-www-form-urlencoded")
.setParam("format", "json")
.setParam("auth_token", "_token")
.body(new StringBuilder()
.append("room_id=").append("_r2&")
.append("from=").append("_from&")
.append("message=").append("_body&")
.append("message_format=").append(format.value()).append("&")
.append("color=").append(color.value()).append("&")
.append("notify=").append(notify ? "1" : "0")
.toString())
.build();
logger.info("expected (r2): " + jsonBuilder().value(req2).bytes().toUtf8());
HttpResponse res2 = mock(HttpResponse.class);
when(res2.status()).thenReturn(200);
when(httpClient.execute(req2)).thenReturn(res2);
account.send(message);
verify(httpClient).execute(req1);
verify(httpClient).execute(req2);
}
}

View File

@ -15,7 +15,6 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;

View File

@ -327,6 +327,7 @@ public abstract class AbstractWatcherIntegrationTests extends ESIntegTestCase {
assertWatchWithMinimumPerformedActionsCount(watchName, minimumExpectedWatchActionsWithActionPerformed, true); assertWatchWithMinimumPerformedActionsCount(watchName, minimumExpectedWatchActionsWithActionPerformed, true);
} }
// TODO remove this shitty method... the `assertConditionMet` is bogus
protected void assertWatchWithMinimumPerformedActionsCount(final String watchName, final long minimumExpectedWatchActionsWithActionPerformed, final boolean assertConditionMet) throws Exception { protected void assertWatchWithMinimumPerformedActionsCount(final String watchName, final long minimumExpectedWatchActionsWithActionPerformed, final boolean assertConditionMet) throws Exception {
final AtomicReference<SearchResponse> lastResponse = new AtomicReference<>(); final AtomicReference<SearchResponse> lastResponse = new AtomicReference<>();
try { try {

View File

@ -5,7 +5,6 @@
*/ */
package org.elasticsearch.watcher.test.integration; package org.elasticsearch.watcher.test.integration;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder;