mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-03-25 01:19:02 +00:00
Watcher: Add JIRA action (elastic/elasticsearch#4014)
closes elastic/elasticsearch#493 Original commit: elastic/x-pack-elasticsearch@6b7387d3e4
This commit is contained in:
parent
74b0a1e71a
commit
18478d63c2
@ -1,9 +1,10 @@
|
||||
import org.elasticsearch.gradle.MavenFilteringHack
|
||||
import org.elasticsearch.gradle.test.NodeInfo
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import org.elasticsearch.gradle.MavenFilteringHack
|
||||
import org.elasticsearch.gradle.test.NodeInfo
|
||||
|
||||
group 'org.elasticsearch.plugin'
|
||||
|
||||
@ -247,3 +248,4 @@ thirdPartyAudit.excludes = [
|
||||
'javax.activation.URLDataSource',
|
||||
'javax.activation.UnsupportedDataTypeException'
|
||||
]
|
||||
|
||||
|
@ -71,6 +71,7 @@ import org.elasticsearch.xpack.notification.email.attachment.HttpEmailAttachemen
|
||||
import org.elasticsearch.xpack.notification.email.attachment.ReportingAttachmentParser;
|
||||
import org.elasticsearch.xpack.notification.email.support.BodyPartSource;
|
||||
import org.elasticsearch.xpack.notification.hipchat.HipChatService;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraService;
|
||||
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyAccount;
|
||||
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyService;
|
||||
import org.elasticsearch.xpack.notification.slack.SlackService;
|
||||
@ -261,6 +262,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
|
||||
List<Object> components = new ArrayList<>();
|
||||
components.add(new EmailService(settings, security.getCryptoService(), clusterSettings));
|
||||
components.add(new HipChatService(settings, httpClient, clusterSettings));
|
||||
components.add(new JiraService(settings, httpClient, clusterSettings));
|
||||
components.add(new SlackService(settings, httpClient, clusterSettings));
|
||||
components.add(new PagerDutyService(settings, httpClient, clusterSettings));
|
||||
|
||||
@ -320,6 +322,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
|
||||
settings.add(SlackService.SLACK_ACCOUNT_SETTING);
|
||||
settings.add(EmailService.EMAIL_ACCOUNT_SETTING);
|
||||
settings.add(HipChatService.HIPCHAT_ACCOUNT_SETTING);
|
||||
settings.add(JiraService.JIRA_ACCOUNT_SETTING);
|
||||
settings.add(PagerDutyService.PAGERDUTY_ACCOUNT_SETTING);
|
||||
settings.add(ReportingAttachmentParser.RETRIES_SETTING);
|
||||
settings.add(ReportingAttachmentParser.INTERVAL_SETTING);
|
||||
@ -336,6 +339,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
|
||||
public List<String> getSettingsFilter() {
|
||||
List<String> filters = new ArrayList<>();
|
||||
filters.add("xpack.notification.email.account.*.smtp.password");
|
||||
filters.add("xpack.notification.jira.account.*.password");
|
||||
filters.add("xpack.notification.slack.account.*.url");
|
||||
filters.add("xpack.notification.pagerduty.account.*.url");
|
||||
filters.add("xpack.notification.pagerduty." + PagerDutyAccount.SERVICE_KEY_SETTING);
|
||||
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.xpack.notification.jira;
|
||||
|
||||
import org.elasticsearch.common.Booleans;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsException;
|
||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||
import org.elasticsearch.xpack.common.http.HttpMethod;
|
||||
import org.elasticsearch.xpack.common.http.HttpProxy;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequest;
|
||||
import org.elasticsearch.xpack.common.http.HttpResponse;
|
||||
import org.elasticsearch.xpack.common.http.Scheme;
|
||||
import org.elasticsearch.xpack.common.http.auth.basic.BasicAuth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class JiraAccount {
|
||||
|
||||
/**
|
||||
* Default JIRA REST API path for create issues
|
||||
**/
|
||||
public static final String DEFAULT_PATH = "/rest/api/2/issue";
|
||||
|
||||
static final String USER_SETTING = "user";
|
||||
static final String PASSWORD_SETTING = "password";
|
||||
static final String URL_SETTING = "url";
|
||||
static final String ISSUE_DEFAULTS_SETTING = "issue_defaults";
|
||||
static final String ALLOW_HTTP_SETTING = "allow_http";
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final String name;
|
||||
private final String user;
|
||||
private final String password;
|
||||
private final URI url;
|
||||
private final Map<String, Object> issueDefaults;
|
||||
|
||||
public JiraAccount(String name, Settings settings, HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
this.name = name;
|
||||
String url = settings.get(URL_SETTING);
|
||||
if (url == null) {
|
||||
throw requiredSettingException(name, URL_SETTING);
|
||||
}
|
||||
try {
|
||||
URI uri = new URI(url);
|
||||
Scheme protocol = Scheme.parse(uri.getScheme());
|
||||
if ((protocol == Scheme.HTTP) && (Booleans.isExplicitTrue(settings.get(ALLOW_HTTP_SETTING)) == false)) {
|
||||
throw new SettingsException("invalid jira [" + name + "] account settings. unsecure scheme [" + protocol + "]");
|
||||
}
|
||||
this.url = uri;
|
||||
} catch (URISyntaxException | IllegalArgumentException e) {
|
||||
throw new SettingsException("invalid jira [" + name + "] account settings. invalid [" + URL_SETTING + "] setting", e);
|
||||
}
|
||||
this.user = settings.get(USER_SETTING);
|
||||
if (Strings.isEmpty(this.user)) {
|
||||
throw requiredSettingException(name, USER_SETTING);
|
||||
}
|
||||
this.password = settings.get(PASSWORD_SETTING);
|
||||
if (Strings.isEmpty(this.password)) {
|
||||
throw requiredSettingException(name, PASSWORD_SETTING);
|
||||
}
|
||||
this.issueDefaults = Collections.unmodifiableMap(settings.getAsSettings(ISSUE_DEFAULTS_SETTING).getAsStructuredMap());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Map<String, Object> getDefaults() {
|
||||
return issueDefaults;
|
||||
}
|
||||
|
||||
public JiraIssue createIssue(final Map<String, Object> fields, final HttpProxy proxy) throws IOException {
|
||||
HttpRequest request = HttpRequest.builder(url.getHost(), url.getPort())
|
||||
.scheme(Scheme.parse(url.getScheme()))
|
||||
.method(HttpMethod.POST)
|
||||
.path(DEFAULT_PATH)
|
||||
.jsonBody((builder, params) -> builder.field("fields", fields))
|
||||
.auth(new BasicAuth(user, password.toCharArray()))
|
||||
.proxy(proxy)
|
||||
.build();
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
return JiraIssue.responded(fields, request, response);
|
||||
}
|
||||
|
||||
private static SettingsException requiredSettingException(String account, String setting) {
|
||||
return new SettingsException("invalid jira [" + account + "] account settings. missing required [" + setting + "] setting");
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.xpack.notification.jira;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParseFieldMatcher;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequest;
|
||||
import org.elasticsearch.xpack.common.http.HttpResponse;
|
||||
import org.elasticsearch.xpack.watcher.actions.jira.JiraAction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class JiraIssue implements ToXContent {
|
||||
|
||||
private final Map<String, Object> fields;
|
||||
@Nullable private final HttpRequest request;
|
||||
@Nullable private final HttpResponse response;
|
||||
@Nullable private final String failureReason;
|
||||
|
||||
public static JiraIssue responded(Map<String, Object> fields, HttpRequest request, HttpResponse response) {
|
||||
return new JiraIssue(fields, request, response, resolveFailureReason(response));
|
||||
}
|
||||
|
||||
JiraIssue(Map<String, Object> fields, HttpRequest request, HttpResponse response, String failureReason) {
|
||||
this.fields = fields;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.failureReason = failureReason;
|
||||
}
|
||||
|
||||
public boolean successful() {
|
||||
return failureReason == null;
|
||||
}
|
||||
|
||||
public HttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public HttpResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public Map<String, Object> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public String getFailureReason() {
|
||||
return failureReason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
JiraIssue sentEvent = (JiraIssue) o;
|
||||
return Objects.equals(fields, sentEvent.fields) &&
|
||||
Objects.equals(request, sentEvent.request) &&
|
||||
Objects.equals(response, sentEvent.response) &&
|
||||
Objects.equals(failureReason, sentEvent.failureReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(fields, request, response, failureReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
if (fields != null) {
|
||||
builder.field(Field.FIELDS.getPreferredName(), fields);
|
||||
}
|
||||
if (successful() == false) {
|
||||
builder.field(Field.REASON.getPreferredName(), failureReason);
|
||||
if (request != null) {
|
||||
builder.field(Field.REQUEST.getPreferredName(), request, params);
|
||||
}
|
||||
if (response != null) {
|
||||
builder.field(Field.RESPONSE.getPreferredName(), response, params);
|
||||
}
|
||||
} else {
|
||||
builder.rawField(Field.RESULT.getPreferredName(), response.body());
|
||||
}
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the failure reason, when a reason can be extracted from the response body:
|
||||
* Ex: {"errorMessages":[],"errors":{"customfield_10004":"Epic Name is required."}}
|
||||
* <p>
|
||||
* See https://docs.atlassian.com/jira/REST/cloud/ for the format of the error response body.
|
||||
*/
|
||||
static String resolveFailureReason(HttpResponse response) {
|
||||
int status = response.status();
|
||||
if (status < 300) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
switch (status) {
|
||||
case HttpStatus.SC_BAD_REQUEST:
|
||||
message.append("Bad Request");
|
||||
break;
|
||||
case HttpStatus.SC_UNAUTHORIZED:
|
||||
message.append("Unauthorized (authentication credentials are invalid)");
|
||||
break;
|
||||
case HttpStatus.SC_FORBIDDEN:
|
||||
message.append("Forbidden (account doesn't have permission to create this issue)");
|
||||
break;
|
||||
case HttpStatus.SC_NOT_FOUND:
|
||||
message.append("Not Found (account uses invalid JIRA REST APIs)");
|
||||
break;
|
||||
case HttpStatus.SC_REQUEST_TIMEOUT:
|
||||
message.append("Request Timeout (request took too long to process)");
|
||||
break;
|
||||
case HttpStatus.SC_INTERNAL_SERVER_ERROR:
|
||||
message.append("JIRA Server Error (internal error occurred while processing request)");
|
||||
break;
|
||||
default:
|
||||
message.append("Unknown Error");
|
||||
break;
|
||||
}
|
||||
|
||||
if (response.hasContent()) {
|
||||
final List<String> errors = new ArrayList<>();
|
||||
|
||||
try (XContentParser parser = JsonXContent.jsonXContent.createParser(response.body())) {
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
if (token == null) {
|
||||
token = parser.nextToken();
|
||||
}
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new ElasticsearchParseException("failed to parse jira project. expected an object, but found [{}] instead",
|
||||
token);
|
||||
}
|
||||
String currentFieldName = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.ERRORS)) {
|
||||
Map<String, Object> fieldErrors = parser.mapOrdered();
|
||||
for (Map.Entry<String, Object> entry : fieldErrors.entrySet()) {
|
||||
errors.add("Field [" + entry.getKey() + "] has error [" + String.valueOf(entry.getValue()) + "]");
|
||||
}
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.ERROR_MESSAGES)) {
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||
errors.add(parser.text());
|
||||
}
|
||||
} else {
|
||||
throw new ElasticsearchParseException("could not parse jira response. unexpected field [{}]", currentFieldName);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors.add("Exception when parsing jira response [" + String.valueOf(e) + "]");
|
||||
}
|
||||
|
||||
if (errors.isEmpty() == false) {
|
||||
message.append(" - ");
|
||||
for (String error : errors) {
|
||||
message.append(error).append('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
private interface Field {
|
||||
ParseField FIELDS = JiraAction.Field.FIELDS;
|
||||
ParseField REASON = new ParseField("reason");
|
||||
ParseField REQUEST = new ParseField("request");
|
||||
ParseField RESPONSE = new ParseField("response");
|
||||
ParseField RESULT = new ParseField("result");
|
||||
|
||||
ParseField ERROR_MESSAGES = new ParseField("errorMessages");
|
||||
ParseField ERRORS = new ParseField("errors");
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.xpack.notification.jira;
|
||||
|
||||
import org.elasticsearch.common.settings.ClusterSettings;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||
import org.elasticsearch.xpack.notification.NotificationService;
|
||||
|
||||
/**
|
||||
* A component to store Atlassian's JIRA credentials.
|
||||
*
|
||||
* https://www.atlassian.com/software/jira
|
||||
*/
|
||||
public class JiraService extends NotificationService<JiraAccount> {
|
||||
|
||||
public static final Setting<Settings> JIRA_ACCOUNT_SETTING =
|
||||
Setting.groupSetting("xpack.notification.jira.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public JiraService(Settings settings, HttpClient httpClient, ClusterSettings clusterSettings) {
|
||||
super(settings);
|
||||
this.httpClient = httpClient;
|
||||
clusterSettings.addSettingsUpdateConsumer(JIRA_ACCOUNT_SETTING, this::setAccountSetting);
|
||||
setAccountSetting(JIRA_ACCOUNT_SETTING.get(settings));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JiraAccount createAccount(String name, Settings accountSettings) {
|
||||
return new JiraAccount(name, accountSettings, httpClient);
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@ import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.notification.email.EmailService;
|
||||
import org.elasticsearch.xpack.notification.email.attachment.EmailAttachmentsParser;
|
||||
import org.elasticsearch.xpack.notification.hipchat.HipChatService;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraService;
|
||||
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyService;
|
||||
import org.elasticsearch.xpack.notification.slack.SlackService;
|
||||
import org.elasticsearch.xpack.security.InternalClient;
|
||||
@ -53,6 +54,8 @@ import org.elasticsearch.xpack.watcher.actions.hipchat.HipChatAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.hipchat.HipChatActionFactory;
|
||||
import org.elasticsearch.xpack.watcher.actions.index.IndexAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.index.IndexActionFactory;
|
||||
import org.elasticsearch.xpack.watcher.actions.jira.JiraAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.jira.JiraActionFactory;
|
||||
import org.elasticsearch.xpack.watcher.actions.logging.LoggingAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.logging.LoggingActionFactory;
|
||||
import org.elasticsearch.xpack.watcher.actions.pagerduty.PagerDutyAction;
|
||||
@ -238,6 +241,8 @@ public class Watcher implements ActionPlugin, ScriptPlugin {
|
||||
actionFactoryMap.put(LoggingAction.TYPE, new LoggingActionFactory(settings, templateEngine));
|
||||
actionFactoryMap.put(HipChatAction.TYPE, new HipChatActionFactory(settings, templateEngine,
|
||||
getService(HipChatService.class, components)));
|
||||
actionFactoryMap.put(JiraAction.TYPE, new JiraActionFactory(settings, templateEngine,
|
||||
getService(JiraService.class, components)));
|
||||
actionFactoryMap.put(SlackAction.TYPE, new SlackActionFactory(settings, templateEngine,
|
||||
getService(SlackService.class, components)));
|
||||
actionFactoryMap.put(PagerDutyAction.TYPE, new PagerDutyActionFactory(settings, templateEngine,
|
||||
|
@ -5,18 +5,22 @@
|
||||
*/
|
||||
package org.elasticsearch.xpack.watcher.actions;
|
||||
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequestTemplate;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplate;
|
||||
import org.elasticsearch.xpack.notification.email.EmailTemplate;
|
||||
import org.elasticsearch.xpack.notification.pagerduty.IncidentEvent;
|
||||
import org.elasticsearch.xpack.notification.slack.message.SlackMessage;
|
||||
import org.elasticsearch.xpack.watcher.actions.email.EmailAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.hipchat.HipChatAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.index.IndexAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.jira.JiraAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.logging.LoggingAction;
|
||||
import org.elasticsearch.xpack.watcher.actions.pagerduty.PagerDutyAction;
|
||||
import org.elasticsearch.xpack.notification.email.EmailTemplate;
|
||||
import org.elasticsearch.xpack.notification.pagerduty.IncidentEvent;
|
||||
import org.elasticsearch.xpack.watcher.actions.slack.SlackAction;
|
||||
import org.elasticsearch.xpack.notification.slack.message.SlackMessage;
|
||||
import org.elasticsearch.xpack.watcher.actions.webhook.WebhookAction;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequestTemplate;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class ActionBuilders {
|
||||
|
||||
@ -35,6 +39,14 @@ public final class ActionBuilders {
|
||||
return IndexAction.builder(index, type);
|
||||
}
|
||||
|
||||
public static JiraAction.Builder jiraAction(String account, MapBuilder<String, Object> fields) {
|
||||
return jiraAction(account, fields.immutableMap());
|
||||
}
|
||||
|
||||
public static JiraAction.Builder jiraAction(String account, Map<String, Object> fields) {
|
||||
return JiraAction.builder(account, fields);
|
||||
}
|
||||
|
||||
public static WebhookAction.Builder webhookAction(HttpRequestTemplate.Builder httpRequest) {
|
||||
return webhookAction(httpRequest.build());
|
||||
}
|
||||
@ -59,7 +71,6 @@ public final class ActionBuilders {
|
||||
return hipchatAction(account, new TextTemplate(body));
|
||||
}
|
||||
|
||||
|
||||
public static HipChatAction.Builder hipchatAction(TextTemplate body) {
|
||||
return hipchatAction(null, body);
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.xpack.watcher.actions.jira;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplate;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraAccount;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraIssue;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraService;
|
||||
import org.elasticsearch.xpack.watcher.actions.Action;
|
||||
import org.elasticsearch.xpack.watcher.actions.ExecutableAction;
|
||||
import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext;
|
||||
import org.elasticsearch.xpack.watcher.support.Variables;
|
||||
import org.elasticsearch.xpack.watcher.watch.Payload;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ExecutableJiraAction extends ExecutableAction<JiraAction> {
|
||||
|
||||
private final TextTemplateEngine engine;
|
||||
private final JiraService jiraService;
|
||||
|
||||
public ExecutableJiraAction(JiraAction action, Logger logger, JiraService jiraService, TextTemplateEngine templateEngine) {
|
||||
super(action, logger);
|
||||
this.jiraService = jiraService;
|
||||
this.engine = templateEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action.Result execute(final String actionId, WatchExecutionContext ctx, Payload payload) throws Exception {
|
||||
JiraAccount account = jiraService.getAccount(action.account);
|
||||
if (account == null) {
|
||||
// the account associated with this action was deleted
|
||||
throw new IllegalStateException("account [" + action.account + "] was not found. perhaps it was deleted");
|
||||
}
|
||||
|
||||
final Function<String, String> render = s -> engine.render(new TextTemplate(s), Variables.createCtxModel(ctx, payload));
|
||||
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
// Apply action fields
|
||||
fields = merge(fields, action.fields, render);
|
||||
// Apply default fields
|
||||
fields = merge(fields, account.getDefaults(), render);
|
||||
|
||||
if (ctx.simulateAction(actionId)) {
|
||||
return new JiraAction.Simulated(fields);
|
||||
}
|
||||
|
||||
JiraIssue result = account.createIssue(fields, action.proxy);
|
||||
return JiraAction.executedResult(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the defaults provided as the second parameter into the content of the first
|
||||
* while applying a {@link Function} on both map key and map value.
|
||||
*/
|
||||
static Map<String, Object> merge(final Map<String, Object> fields, final Map<String, ?> defaults, final Function<String, String> fn) {
|
||||
if (defaults != null) {
|
||||
for (Map.Entry<String, ?> defaultEntry : defaults.entrySet()) {
|
||||
Object value = defaultEntry.getValue();
|
||||
if (value instanceof String) {
|
||||
// Apply the transformation to a simple string
|
||||
value = fn.apply((String) value);
|
||||
|
||||
} else if (value instanceof Map) {
|
||||
// Apply the transformation to a map
|
||||
value = merge(new HashMap<>(), (Map<String, ?>) value, fn);
|
||||
|
||||
} else if (value instanceof String[]) {
|
||||
// Apply the transformation to an array of strings
|
||||
String[] newValues = new String[((String[]) value).length];
|
||||
for (int i = 0; i < newValues.length; i++) {
|
||||
newValues[i] = fn.apply(((String[]) value)[i]);
|
||||
}
|
||||
value = newValues;
|
||||
|
||||
} else if (value instanceof List) {
|
||||
// Apply the transformation to a list of strings
|
||||
List<Object> newValues = new ArrayList<>(((List) value).size());
|
||||
for (Object v : (List) value) {
|
||||
if (v instanceof String) {
|
||||
newValues.add(fn.apply((String) v));
|
||||
} else {
|
||||
newValues.add(v);
|
||||
}
|
||||
}
|
||||
value = newValues;
|
||||
}
|
||||
|
||||
// Apply the transformation to the key
|
||||
String key = fn.apply(defaultEntry.getKey());
|
||||
|
||||
// Copy the value directly in the map if it does not exist yet.
|
||||
// We don't try to merge maps or list.
|
||||
if (fields.containsKey(key) == false) {
|
||||
fields.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.xpack.watcher.actions.jira;
|
||||
|
||||
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
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.xpack.common.http.HttpProxy;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraIssue;
|
||||
import org.elasticsearch.xpack.watcher.actions.Action;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class JiraAction implements Action {
|
||||
|
||||
public static final String TYPE = "jira";
|
||||
|
||||
@Nullable final String account;
|
||||
@Nullable final HttpProxy proxy;
|
||||
final Map<String, Object> fields;
|
||||
|
||||
public JiraAction(@Nullable String account, Map<String, Object> fields, HttpProxy proxy) {
|
||||
this.account = account;
|
||||
this.fields = fields;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
JiraAction that = (JiraAction) o;
|
||||
return Objects.equals(account, that.account) &&
|
||||
Objects.equals(fields, that.fields) &&
|
||||
Objects.equals(proxy, that.proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(account, fields, proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
if (account != null) {
|
||||
builder.field(Field.ACCOUNT.getPreferredName(), account);
|
||||
}
|
||||
if (proxy != null) {
|
||||
proxy.toXContent(builder, params);
|
||||
}
|
||||
builder.field(Field.FIELDS.getPreferredName(), fields);
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
public static JiraAction parse(String watchId, String actionId, XContentParser parser) throws IOException {
|
||||
String account = null;
|
||||
HttpProxy proxy = null;
|
||||
Map<String, Object> fields = 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.PROXY)) {
|
||||
proxy = HttpProxy.parse(parser);
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Field.FIELDS)) {
|
||||
try {
|
||||
fields = parser.map();
|
||||
} catch (Exception e) {
|
||||
throw new ElasticsearchParseException("failed to parse [{}] action [{}/{}]. failed to parse [{}] field", e, TYPE,
|
||||
watchId, actionId, Field.FIELDS.getPreferredName());
|
||||
}
|
||||
} else {
|
||||
throw new ElasticsearchParseException("failed to parse [{}] action [{}/{}]. unexpected token [{}/{}]", TYPE, watchId,
|
||||
actionId, token, currentFieldName);
|
||||
}
|
||||
}
|
||||
if (fields == null) {
|
||||
fields = Collections.emptyMap();
|
||||
}
|
||||
return new JiraAction(account, fields, proxy);
|
||||
}
|
||||
|
||||
static class Executed extends Action.Result {
|
||||
|
||||
private final JiraIssue result;
|
||||
|
||||
public Executed(JiraIssue result) {
|
||||
super(TYPE, result.successful() ? Status.SUCCESS : Status.FAILURE);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public JiraIssue getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.field(type, result, params);
|
||||
}
|
||||
}
|
||||
|
||||
static Executed executedResult(JiraIssue result) {
|
||||
return new Executed(result);
|
||||
}
|
||||
|
||||
static class Simulated extends Action.Result {
|
||||
|
||||
private final Map<String, Object> fields;
|
||||
|
||||
protected Simulated(Map<String, Object> fields) {
|
||||
super(TYPE, Status.SIMULATED);
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public Map<String, Object> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject(type)
|
||||
.field(Field.FIELDS.getPreferredName(), fields)
|
||||
.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder implements Action.Builder<JiraAction> {
|
||||
|
||||
final JiraAction action;
|
||||
|
||||
public Builder(JiraAction action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JiraAction build() {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder builder(String account, Map<String, Object> fields) {
|
||||
return new Builder(new JiraAction(account, fields, null));
|
||||
}
|
||||
|
||||
public interface Field {
|
||||
ParseField ACCOUNT = new ParseField("account");
|
||||
ParseField PROXY = new ParseField("proxy");
|
||||
ParseField FIELDS = new ParseField("fields");
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.xpack.watcher.actions.jira;
|
||||
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraService;
|
||||
import org.elasticsearch.xpack.watcher.actions.ActionFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class JiraActionFactory extends ActionFactory {
|
||||
|
||||
private final TextTemplateEngine templateEngine;
|
||||
private final JiraService jiraService;
|
||||
|
||||
public JiraActionFactory(Settings settings, TextTemplateEngine templateEngine, JiraService jiraService) {
|
||||
super(Loggers.getLogger(ExecutableJiraAction.class, settings));
|
||||
this.templateEngine = templateEngine;
|
||||
this.jiraService = jiraService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableJiraAction parseExecutable(String watchId, String actionId, XContentParser parser) throws IOException {
|
||||
JiraAction action = JiraAction.parse(watchId, actionId, parser);
|
||||
jiraService.getAccount(action.getAccount()); // for validation -- throws exception if account not present
|
||||
return new ExecutableJiraAction(action, actionLogger, jiraService, templateEngine);
|
||||
}
|
||||
}
|
@ -9,9 +9,8 @@ import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.watcher.actions.ActionFactory;
|
||||
import org.elasticsearch.xpack.watcher.actions.hipchat.ExecutableHipChatAction;
|
||||
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyService;
|
||||
import org.elasticsearch.xpack.watcher.actions.ActionFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@ -21,7 +20,7 @@ public class PagerDutyActionFactory extends ActionFactory {
|
||||
private final PagerDutyService pagerDutyService;
|
||||
|
||||
public PagerDutyActionFactory(Settings settings, TextTemplateEngine templateEngine, PagerDutyService pagerDutyService) {
|
||||
super(Loggers.getLogger(ExecutableHipChatAction.class, settings));
|
||||
super(Loggers.getLogger(ExecutablePagerDutyAction.class, settings));
|
||||
this.templateEngine = templateEngine;
|
||||
this.pagerDutyService = pagerDutyService;
|
||||
}
|
||||
|
@ -9,9 +9,8 @@ import org.elasticsearch.common.logging.Loggers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.watcher.actions.ActionFactory;
|
||||
import org.elasticsearch.xpack.watcher.actions.hipchat.ExecutableHipChatAction;
|
||||
import org.elasticsearch.xpack.notification.slack.SlackService;
|
||||
import org.elasticsearch.xpack.watcher.actions.ActionFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@ -20,7 +19,7 @@ public class SlackActionFactory extends ActionFactory {
|
||||
private final SlackService slackService;
|
||||
|
||||
public SlackActionFactory(Settings settings, TextTemplateEngine templateEngine, SlackService slackService) {
|
||||
super(Loggers.getLogger(ExecutableHipChatAction.class, settings));
|
||||
super(Loggers.getLogger(ExecutableSlackAction.class, settings));
|
||||
this.templateEngine = templateEngine;
|
||||
this.slackService = slackService;
|
||||
}
|
||||
|
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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.xpack.notification.jira;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.settings.ClusterSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsException;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequest;
|
||||
import org.elasticsearch.xpack.common.http.HttpResponse;
|
||||
import org.elasticsearch.xpack.common.http.Scheme;
|
||||
import org.junit.Before;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.elasticsearch.common.collect.Tuple.tuple;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.isOneOf;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JiraAccountTests extends ESTestCase {
|
||||
|
||||
private HttpClient httpClient;
|
||||
private ClusterSettings clusterSettings;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
httpClient = mock(HttpClient.class);
|
||||
clusterSettings = new ClusterSettings(Settings.EMPTY, Collections.singleton(JiraService.JIRA_ACCOUNT_SETTING));
|
||||
}
|
||||
|
||||
public void testJiraAccountSettings() {
|
||||
final String url = "https://internal-jira.elastic.co:443";
|
||||
|
||||
SettingsException e = expectThrows(SettingsException.class, () -> new JiraAccount(null, Settings.EMPTY, null));
|
||||
assertThat(e.getMessage(), containsString("invalid jira [null] account settings. missing required [url] setting"));
|
||||
|
||||
Settings settings1 = Settings.builder().put("url", url).build();
|
||||
e = expectThrows(SettingsException.class, () -> new JiraAccount("test", settings1, null));
|
||||
assertThat(e.getMessage(), containsString("invalid jira [test] account settings. missing required [user] setting"));
|
||||
|
||||
Settings settings2 = Settings.builder().put("url", url).put("user", "").build();
|
||||
e = expectThrows(SettingsException.class, () -> new JiraAccount("test", settings2, null));
|
||||
assertThat(e.getMessage(), containsString("invalid jira [test] account settings. missing required [user] setting"));
|
||||
|
||||
Settings settings3 = Settings.builder().put("url", url).put("user", "foo").build();
|
||||
e = expectThrows(SettingsException.class, () -> new JiraAccount("test", settings3, null));
|
||||
assertThat(e.getMessage(), containsString("invalid jira [test] account settings. missing required [password] setting"));
|
||||
|
||||
Settings settings4 = Settings.builder().put("url", url).put("user", "foo").put("password", "").build();
|
||||
e = expectThrows(SettingsException.class, () -> new JiraAccount("test", settings4, null));
|
||||
assertThat(e.getMessage(), containsString("invalid jira [test] account settings. missing required [password] setting"));
|
||||
}
|
||||
|
||||
public void testSingleAccount() throws Exception {
|
||||
Settings.Builder builder = Settings.builder().put("xpack.notification.jira.default_account", "account1");
|
||||
addAccountSettings("account1", builder);
|
||||
|
||||
JiraService service = new JiraService(builder.build(), httpClient, clusterSettings);
|
||||
JiraAccount account = service.getAccount("account1");
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account1"));
|
||||
account = service.getAccount(null); // falling back on the default
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account1"));
|
||||
}
|
||||
|
||||
public void testSingleAccountNoExplicitDefault() throws Exception {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
addAccountSettings("account1", builder);
|
||||
|
||||
JiraService service = new JiraService(builder.build(), httpClient, clusterSettings);
|
||||
JiraAccount account = service.getAccount("account1");
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account1"));
|
||||
account = service.getAccount(null); // falling back on the default
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account1"));
|
||||
}
|
||||
|
||||
public void testMultipleAccounts() throws Exception {
|
||||
Settings.Builder builder = Settings.builder().put("xpack.notification.jira.default_account", "account1");
|
||||
addAccountSettings("account1", builder);
|
||||
addAccountSettings("account2", builder);
|
||||
|
||||
JiraService service = new JiraService(builder.build(), httpClient, clusterSettings);
|
||||
JiraAccount account = service.getAccount("account1");
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account1"));
|
||||
account = service.getAccount("account2");
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account2"));
|
||||
account = service.getAccount(null); // falling back on the default
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account1"));
|
||||
}
|
||||
|
||||
public void testMultipleAccountsNoExplicitDefault() throws Exception {
|
||||
Settings.Builder builder = Settings.builder().put("xpack.notification.jira.default_account", "account1");
|
||||
addAccountSettings("account1", builder);
|
||||
addAccountSettings("account2", builder);
|
||||
|
||||
JiraService service = new JiraService(builder.build(), httpClient, clusterSettings);
|
||||
JiraAccount account = service.getAccount("account1");
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account1"));
|
||||
account = service.getAccount("account2");
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), equalTo("account2"));
|
||||
account = service.getAccount(null);
|
||||
assertThat(account, notNullValue());
|
||||
assertThat(account.getName(), isOneOf("account1", "account2"));
|
||||
}
|
||||
|
||||
public void testMultipleAccountsUnknownDefault() throws Exception {
|
||||
Settings.Builder builder = Settings.builder().put("xpack.notification.jira.default_account", "unknown");
|
||||
addAccountSettings("account1", builder);
|
||||
addAccountSettings("account2", builder);
|
||||
SettingsException e = expectThrows(SettingsException.class, () -> new JiraService(builder.build(), httpClient, clusterSettings)
|
||||
);
|
||||
assertThat(e.getMessage(), is("could not find default account [unknown]"));
|
||||
}
|
||||
|
||||
public void testNoAccount() throws Exception {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
JiraService service = new JiraService(builder.build(), httpClient, clusterSettings);
|
||||
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> service.getAccount(null));
|
||||
assertThat(e.getMessage(), is("no account found for name: [null]"));
|
||||
}
|
||||
|
||||
public void testNoAccountWithDefaultAccount() throws Exception {
|
||||
Settings.Builder builder = Settings.builder().put("xpack.notification.jira.default_account", "unknown");
|
||||
|
||||
SettingsException e = expectThrows(SettingsException.class, () -> new JiraService(builder.build(), httpClient, clusterSettings)
|
||||
);
|
||||
assertThat(e.getMessage(), is("could not find default account [unknown]"));
|
||||
}
|
||||
|
||||
public void testUnsecureAccountUrl() throws Exception {
|
||||
Settings settings = Settings.builder().put("url", "http://localhost").put("user", "foo").put("password", "bar").build();
|
||||
SettingsException e = expectThrows(SettingsException.class, () -> new JiraAccount("test", settings, null));
|
||||
assertThat(e.getMessage(), containsString("invalid jira [test] account settings. unsecure scheme [HTTP]"));
|
||||
|
||||
Settings disallowHttp = Settings.builder().put(settings).put("allow_http", false).build();
|
||||
e = expectThrows(SettingsException.class, () -> new JiraAccount("test", disallowHttp, null));
|
||||
assertThat(e.getMessage(), containsString("invalid jira [test] account settings. unsecure scheme [HTTP]"));
|
||||
|
||||
Settings allowHttp = Settings.builder().put(settings).put("allow_http", true).build();
|
||||
assertNotNull(new JiraAccount("test", allowHttp, null));
|
||||
}
|
||||
|
||||
public void testCreateIssueWithError() throws Exception {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
addAccountSettings("account1", builder);
|
||||
|
||||
JiraService service = new JiraService(builder.build(), httpClient, clusterSettings);
|
||||
JiraAccount account = service.getAccount("account1");
|
||||
|
||||
Tuple<Integer, String> error = randomHttpError();
|
||||
|
||||
when(httpClient.execute(any(HttpRequest.class))).thenReturn(new HttpResponse(error.v1()));
|
||||
JiraIssue issue = account.createIssue(emptyMap(), null);
|
||||
assertFalse(issue.successful());
|
||||
assertThat(issue.getFailureReason(), equalTo(error.v2()));
|
||||
}
|
||||
|
||||
public void testCreateIssue() throws Exception {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
addAccountSettings("account1", builder);
|
||||
|
||||
JiraService service = new JiraService(builder.build(), httpClient, clusterSettings);
|
||||
JiraAccount account = service.getAccount("account1");
|
||||
|
||||
ArgumentCaptor<HttpRequest> argumentCaptor = ArgumentCaptor.forClass(HttpRequest.class);
|
||||
when(httpClient.execute(argumentCaptor.capture())).thenReturn(new HttpResponse(HttpStatus.SC_CREATED));
|
||||
|
||||
Map<String, Object> fields = singletonMap("key", "value");
|
||||
|
||||
JiraIssue issue = account.createIssue(fields, null);
|
||||
assertTrue(issue.successful());
|
||||
assertNull(issue.getFailureReason());
|
||||
|
||||
HttpRequest sentRequest = argumentCaptor.getValue();
|
||||
assertThat(sentRequest.host(), equalTo("internal-jira.elastic.co"));
|
||||
assertThat(sentRequest.port(), equalTo(443));
|
||||
assertThat(sentRequest.scheme(), equalTo(Scheme.HTTPS));
|
||||
assertThat(sentRequest.path(), equalTo(JiraAccount.DEFAULT_PATH));
|
||||
assertThat(sentRequest.auth(), notNullValue());
|
||||
assertThat(sentRequest.body(), notNullValue());
|
||||
}
|
||||
|
||||
private void addAccountSettings(String name, Settings.Builder builder) {
|
||||
builder.put("xpack.notification.jira.account." + name + "." + JiraAccount.URL_SETTING, "https://internal-jira.elastic.co:443");
|
||||
builder.put("xpack.notification.jira.account." + name + "." + JiraAccount.USER_SETTING, randomAsciiOfLength(10));
|
||||
builder.put("xpack.notification.jira.account." + name + "." + JiraAccount.PASSWORD_SETTING, randomAsciiOfLength(10));
|
||||
|
||||
Map<String, Object> defaults = randomIssueDefaults();
|
||||
for (Map.Entry<String, Object> setting : defaults.entrySet()) {
|
||||
String key = "xpack.notification.jira.account." + name + "." + JiraAccount.ISSUE_DEFAULTS_SETTING + "." + setting.getKey();
|
||||
if (setting.getValue() instanceof String) {
|
||||
builder.put(key, setting.getValue());
|
||||
} else if (setting.getValue() instanceof Map) {
|
||||
builder.putProperties((Map) setting.getValue(), s -> true, s -> key + "." + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> randomIssueDefaults() {
|
||||
MapBuilder<String, Object> builder = MapBuilder.newMapBuilder();
|
||||
if (randomBoolean()) {
|
||||
Map<String, Object> project = new HashMap<>();
|
||||
project.put("project", singletonMap("id", randomAsciiOfLength(10)));
|
||||
builder.putAll(project);
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
Map<String, Object> project = new HashMap<>();
|
||||
project.put("issuetype", singletonMap("name", randomAsciiOfLength(5)));
|
||||
builder.putAll(project);
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
builder.put("summary", randomAsciiOfLength(10));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
builder.put("description", randomAsciiOfLength(50));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
int count = randomIntBetween(0, 5);
|
||||
for (int i = 0; i < count; i++) {
|
||||
builder.put("customfield_" + i, randomAsciiOfLengthBetween(5, 10));
|
||||
}
|
||||
}
|
||||
return builder.immutableMap();
|
||||
}
|
||||
|
||||
static Tuple<Integer, String> randomHttpError() {
|
||||
Tuple<Integer, String> error = randomFrom(
|
||||
tuple(400, "Bad Request"),
|
||||
tuple(401, "Unauthorized (authentication credentials are invalid)"),
|
||||
tuple(403, "Forbidden (account doesn't have permission to create this issue)"),
|
||||
tuple(404, "Not Found (account uses invalid JIRA REST APIs)"),
|
||||
tuple(408, "Request Timeout (request took too long to process)"),
|
||||
tuple(500, "JIRA Server Error (internal error occurred while processing request)"),
|
||||
tuple(666, "Unknown Error")
|
||||
);
|
||||
return error;
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.xpack.notification.jira;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.common.http.HttpMethod;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequest;
|
||||
import org.elasticsearch.xpack.common.http.HttpResponse;
|
||||
import org.elasticsearch.xpack.common.http.auth.HttpAuthRegistry;
|
||||
import org.elasticsearch.xpack.common.http.auth.basic.BasicAuth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.cborBuilder;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.smileBuilder;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.yamlBuilder;
|
||||
import static org.elasticsearch.xpack.notification.jira.JiraAccountTests.randomHttpError;
|
||||
import static org.elasticsearch.xpack.notification.jira.JiraAccountTests.randomIssueDefaults;
|
||||
import static org.elasticsearch.xpack.notification.jira.JiraIssue.resolveFailureReason;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class JiraIssueTests extends ESTestCase {
|
||||
|
||||
public void testToXContent() throws Exception {
|
||||
final JiraIssue issue = randomJiraIssue();
|
||||
|
||||
BytesReference bytes = null;
|
||||
try (XContentBuilder builder = randomFrom(jsonBuilder(), smileBuilder(), yamlBuilder(), cborBuilder())) {
|
||||
issue.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
bytes = builder.bytes();
|
||||
}
|
||||
|
||||
Map<String, Object> parsedFields = null;
|
||||
Map<String, Object> parsedResult = null;
|
||||
|
||||
HttpRequest parsedRequest = null;
|
||||
HttpResponse parsedResponse = null;
|
||||
String parsedReason = null;
|
||||
|
||||
try (XContentParser parser = XContentHelper.createParser(bytes)) {
|
||||
assertNull(parser.currentToken());
|
||||
parser.nextToken();
|
||||
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
assertThat(token, is(XContentParser.Token.START_OBJECT));
|
||||
|
||||
String currentFieldName = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if ("result".equals(currentFieldName)) {
|
||||
parsedResult = parser.map();
|
||||
} else if ("request".equals(currentFieldName)) {
|
||||
HttpRequest.Parser httpRequestParser = new HttpRequest.Parser(mock(HttpAuthRegistry.class));
|
||||
parsedRequest = httpRequestParser.parse(parser);
|
||||
} else if ("response".equals(currentFieldName)) {
|
||||
parsedResponse = HttpResponse.parse(parser);
|
||||
} else if ("fields".equals(currentFieldName)) {
|
||||
parsedFields = parser.map();
|
||||
} else if ("reason".equals(currentFieldName)) {
|
||||
parsedReason = parser.text();
|
||||
} else {
|
||||
fail("unknown field [" + currentFieldName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(parsedFields, equalTo(issue.getFields()));
|
||||
if (issue.successful()) {
|
||||
assertThat(parsedResult, hasEntry("key", "TEST"));
|
||||
assertNull(parsedRequest);
|
||||
assertNull(parsedResponse);
|
||||
} else {
|
||||
assertThat(parsedRequest, equalTo(issue.getRequest()));
|
||||
assertThat(parsedResponse, equalTo(issue.getResponse()));
|
||||
assertThat(parsedReason, equalTo(resolveFailureReason(issue.getResponse())));
|
||||
}
|
||||
}
|
||||
|
||||
public void testEquals() throws Exception {
|
||||
final JiraIssue issue1 = randomJiraIssue();
|
||||
final boolean equals = randomBoolean();
|
||||
|
||||
final Map<String, Object> fields = new HashMap<>(issue1.getFields());
|
||||
if (equals == false) {
|
||||
String key = randomFrom(fields.keySet());
|
||||
fields.remove(key);
|
||||
}
|
||||
|
||||
JiraIssue issue2 = new JiraIssue(fields, issue1.getRequest(), issue1.getResponse(), issue1.getFailureReason());
|
||||
assertThat(issue1.equals(issue2), is(equals));
|
||||
}
|
||||
|
||||
private static JiraIssue randomJiraIssue() throws IOException {
|
||||
Map<String, Object> fields = randomIssueDefaults();
|
||||
HttpRequest request = HttpRequest.builder(randomFrom("localhost", "internal-jira.elastic.co"), randomFrom(80, 443))
|
||||
.method(HttpMethod.POST)
|
||||
.path(JiraAccount.DEFAULT_PATH)
|
||||
.auth(new BasicAuth(randomAsciiOfLength(5), randomAsciiOfLength(5).toCharArray()))
|
||||
.build();
|
||||
if (rarely()) {
|
||||
Tuple<Integer, String> error = randomHttpError();
|
||||
return JiraIssue.responded(fields, request, new HttpResponse(error.v1(), "{\"error\": \"" + error.v2() + "\"}"));
|
||||
}
|
||||
return JiraIssue.responded(fields, request, new HttpResponse(HttpStatus.SC_CREATED, "{\"key\": \"TEST\"}"));
|
||||
}
|
||||
}
|
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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.xpack.watcher.actions.jira;
|
||||
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||
import org.elasticsearch.xpack.common.http.HttpProxy;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequest;
|
||||
import org.elasticsearch.xpack.common.http.HttpResponse;
|
||||
import org.elasticsearch.xpack.common.http.auth.HttpAuth;
|
||||
import org.elasticsearch.xpack.common.http.auth.basic.BasicAuth;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplate;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraAccount;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraService;
|
||||
import org.elasticsearch.xpack.watcher.actions.Action;
|
||||
import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext;
|
||||
import org.elasticsearch.xpack.watcher.execution.Wid;
|
||||
import org.elasticsearch.xpack.watcher.watch.Payload;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.mockExecutionContextBuilder;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ExecutableJiraActionTests extends ESTestCase {
|
||||
|
||||
public void testProxy() throws Exception {
|
||||
HttpProxy proxy = new HttpProxy("localhost", 8080);
|
||||
Map<String, Object> issueDefaults = Collections.singletonMap("customfield_0001", "test");
|
||||
JiraAction action = new JiraAction("account1", issueDefaults, proxy);
|
||||
|
||||
HttpClient httpClient = mock(HttpClient.class);
|
||||
ArgumentCaptor<HttpRequest> argumentCaptor = ArgumentCaptor.forClass(HttpRequest.class);
|
||||
when(httpClient.execute(argumentCaptor.capture())).thenReturn(new HttpResponse(200));
|
||||
|
||||
final String host = randomFrom("localhost", "internal-jira.elastic.co");
|
||||
final int port = randomFrom(80, 8080, 449, 9443);
|
||||
final String url = "https://" + host + ":" + port;
|
||||
final String user = randomAsciiOfLength(10);
|
||||
final String password = randomAsciiOfLength(10);
|
||||
|
||||
Settings accountSettings = Settings.builder()
|
||||
.put("url", url)
|
||||
.put("user", user)
|
||||
.put("password", password)
|
||||
.build();
|
||||
|
||||
JiraAccount account = new JiraAccount("account1", accountSettings, httpClient);
|
||||
|
||||
JiraService service = mock(JiraService.class);
|
||||
when(service.getAccount(eq("account1"))).thenReturn(account);
|
||||
|
||||
DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
|
||||
Wid wid = new Wid(randomAsciiOfLength(5), randomLong(), now);
|
||||
WatchExecutionContext ctx = mockExecutionContextBuilder(wid.watchId())
|
||||
.wid(wid)
|
||||
.payload(new Payload.Simple())
|
||||
.time(wid.watchId(), now)
|
||||
.buildMock();
|
||||
|
||||
ExecutableJiraAction executable = new ExecutableJiraAction(action, logger, service, new UpperCaseTextTemplateEngine());
|
||||
executable.execute("foo", ctx, new Payload.Simple());
|
||||
|
||||
HttpRequest request = argumentCaptor.getValue();
|
||||
assertThat(request.proxy(), is(proxy));
|
||||
assertThat(request.host(), is(host));
|
||||
assertThat(request.port(), is(port));
|
||||
assertThat(request.path(), is(JiraAccount.DEFAULT_PATH));
|
||||
|
||||
HttpAuth httpAuth = request.auth();
|
||||
assertThat(httpAuth.type(), is("basic"));
|
||||
|
||||
BasicAuth basicAuth = (BasicAuth) httpAuth;
|
||||
assertThat(basicAuth.getUsername(), is(user));
|
||||
}
|
||||
|
||||
public void testExecutionWithNoDefaults() throws Exception {
|
||||
JiraAction.Simulated result = simulateExecution(singletonMap("key", "value"), emptyMap());
|
||||
assertEquals(result.getFields().size(), 1);
|
||||
assertThat(result.getFields(), hasEntry("KEY", "VALUE"));
|
||||
}
|
||||
|
||||
public void testExecutionNoFieldsWithDefaults() throws Exception {
|
||||
Map<String, String> defaults = new HashMap<>();
|
||||
defaults.put("k0", "v0");
|
||||
|
||||
JiraAction.Simulated result = simulateExecution(new HashMap<>(), defaults);
|
||||
assertEquals(result.getFields().size(), 1);
|
||||
assertThat(result.getFields(), hasEntry("K0", "V0"));
|
||||
|
||||
defaults.put("k1", "v1");
|
||||
|
||||
result = simulateExecution(new HashMap<>(), defaults);
|
||||
assertEquals(result.getFields().size(), 2);
|
||||
assertThat(result.getFields(), allOf(hasEntry("K0", "V0"), hasEntry("K1", "V1")));
|
||||
}
|
||||
|
||||
public void testExecutionFields() throws Exception {
|
||||
Map<String, String> defaults = new HashMap<>();
|
||||
defaults.put("k0", "v0");
|
||||
defaults.put("k1", "v1");
|
||||
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
fields.put("k1", "new_v1"); // overridden
|
||||
fields.put("k2", "v2");
|
||||
fields.put("k3", "v3");
|
||||
|
||||
JiraAction.Simulated result = simulateExecution(fields, defaults);
|
||||
assertEquals(result.getFields().size(), 4);
|
||||
assertThat(result.getFields(), allOf(hasEntry("K0", "V0"), hasEntry("K1", "NEW_V1"), hasEntry("K2", "V2"), hasEntry("K3", "V3")));
|
||||
}
|
||||
|
||||
public void testExecutionFieldsMaps() throws Exception {
|
||||
Map<String, String> defaults = new HashMap<>();
|
||||
defaults.put("k0.a", "b");
|
||||
defaults.put("k1.c", "d");
|
||||
defaults.put("k1.e", "f");
|
||||
defaults.put("k1.g.a", "b");
|
||||
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
fields.put("k2", "v2");
|
||||
fields.put("k3", "v3");
|
||||
|
||||
JiraAction.Simulated result = simulateExecution(fields, defaults);
|
||||
|
||||
final Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("K0", singletonMap("A", "B"));
|
||||
expected.put("K2", "V2");
|
||||
expected.put("K3", "V3");
|
||||
|
||||
final Map<String, Object> expectedK1 = new HashMap<>();
|
||||
expectedK1.put("C", "D");
|
||||
expectedK1.put("E", "F");
|
||||
expectedK1.put("G", singletonMap("A", "B"));
|
||||
expected.put("K1", expectedK1);
|
||||
|
||||
assertThat(result.getFields(), equalTo(expected));
|
||||
}
|
||||
|
||||
public void testExecutionFieldsMapsAreOverridden() throws Exception {
|
||||
Map<String, String> defaults = new HashMap<>();
|
||||
defaults.put("k0", "v0");
|
||||
defaults.put("k1.a", "b");
|
||||
defaults.put("k1.c", "d");
|
||||
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
fields.put("k1", singletonMap("c", "e")); // will overrides the defaults
|
||||
fields.put("k2", "v2");
|
||||
|
||||
JiraAction.Simulated result = simulateExecution(fields, defaults);
|
||||
|
||||
final Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("K0", "V0");
|
||||
expected.put("K1", singletonMap("C", "E"));
|
||||
expected.put("K2", "V2");
|
||||
|
||||
assertThat(result.getFields(), equalTo(expected));
|
||||
}
|
||||
|
||||
public void testExecutionFieldsLists() throws Exception {
|
||||
Map<String, String> defaults = new HashMap<>();
|
||||
defaults.put("k0.0", "a");
|
||||
defaults.put("k0.1", "b");
|
||||
defaults.put("k0.2", "c");
|
||||
defaults.put("k1", "v1");
|
||||
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
fields.put("k2", "v2");
|
||||
fields.put("k3", Arrays.asList("d", "e", "f"));
|
||||
|
||||
JiraAction.Simulated result = simulateExecution(fields, defaults);
|
||||
|
||||
final Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("K0", Arrays.asList("A", "B", "C"));
|
||||
expected.put("K1", "V1");
|
||||
expected.put("K2", "V2");
|
||||
expected.put("K3", Arrays.asList("D", "E", "F"));
|
||||
|
||||
assertThat(result.getFields(), equalTo(expected));
|
||||
}
|
||||
|
||||
public void testExecutionFieldsListsNotOverridden() throws Exception {
|
||||
Map<String, String> defaults = new HashMap<>();
|
||||
defaults.put("k0.0", "a");
|
||||
defaults.put("k0.1", "b");
|
||||
defaults.put("k0.2", "c");
|
||||
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
fields.put("k1", "v1");
|
||||
fields.put("k0", Arrays.asList("d", "e", "f")); // should not be overridden byt the defaults
|
||||
|
||||
JiraAction.Simulated result = simulateExecution(fields, defaults);
|
||||
|
||||
final Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("K0", Arrays.asList("D", "E", "F"));
|
||||
expected.put("K1", "V1");
|
||||
|
||||
assertThat(result.getFields(), equalTo(expected));
|
||||
}
|
||||
|
||||
public void testExecutionFieldsStringArrays() throws Exception {
|
||||
Map<String, String> defaults = Settings.builder()
|
||||
.putArray("k0", "a", "b", "c")
|
||||
.put("k1", "v1")
|
||||
.build()
|
||||
.getAsMap();
|
||||
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
fields.put("k2", "v2");
|
||||
fields.put("k3", new String[]{"d", "e", "f"});
|
||||
|
||||
JiraAction.Simulated result = simulateExecution(fields, defaults);
|
||||
|
||||
assertThat(result.getFields().get("K1"), equalTo("V1"));
|
||||
assertThat(result.getFields().get("K2"), equalTo("V2"));
|
||||
assertArrayEquals((Object[]) result.getFields().get("K3"), new Object[]{"D", "E", "F"});
|
||||
}
|
||||
|
||||
public void testExecutionFieldsStringArraysNotOverridden() throws Exception {
|
||||
Map<String, String> defaults = Settings.builder()
|
||||
.putArray("k0", "a", "b", "c")
|
||||
.build()
|
||||
.getAsMap();
|
||||
|
||||
Map<String, Object> fields = new HashMap<>();
|
||||
fields.put("k1", "v1");
|
||||
fields.put("k0", new String[]{"d", "e", "f"}); // should not be overridden byt the defaults
|
||||
|
||||
JiraAction.Simulated result = simulateExecution(fields, defaults);
|
||||
|
||||
final Map<String, Object> expected = new HashMap<>();
|
||||
expected.put("K0", new String[]{"D", "E", "F"});
|
||||
expected.put("K1", "V1");
|
||||
|
||||
assertArrayEquals((Object[]) result.getFields().get("K0"), new Object[]{"D", "E", "F"});
|
||||
assertThat(result.getFields().get("K1"), equalTo("V1"));
|
||||
}
|
||||
|
||||
private JiraAction.Simulated simulateExecution(Map<String, Object> actionFields, Map<String, String> accountFields) throws Exception {
|
||||
Settings.Builder settings = Settings.builder()
|
||||
.put("url", "https://internal-jira.elastic.co:443")
|
||||
.put("user", "elastic")
|
||||
.put("password", "secret")
|
||||
.putProperties(accountFields, s -> true, s -> "issue_defaults." + s);
|
||||
|
||||
JiraAccount account = new JiraAccount("account", settings.build(), mock(HttpClient.class));
|
||||
|
||||
JiraService service = mock(JiraService.class);
|
||||
when(service.getAccount(eq("account"))).thenReturn(account);
|
||||
|
||||
JiraAction action = new JiraAction("account", actionFields, null);
|
||||
ExecutableJiraAction executable = new ExecutableJiraAction(action, null, service, new UpperCaseTextTemplateEngine());
|
||||
|
||||
WatchExecutionContext context = createWatchExecutionContext();
|
||||
when(context.simulateAction("test")).thenReturn(true);
|
||||
|
||||
Action.Result result = executable.execute("test", context, new Payload.Simple());
|
||||
assertThat(result, instanceOf(JiraAction.Result.class));
|
||||
assertThat(result, instanceOf(JiraAction.Simulated.class));
|
||||
return (JiraAction.Simulated) result;
|
||||
}
|
||||
|
||||
private WatchExecutionContext createWatchExecutionContext() {
|
||||
DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
Wid wid = new Wid(randomAsciiOfLength(5), randomLong(), now);
|
||||
Map<String, Object> metadata = MapBuilder.<String, Object>newMapBuilder().put("_key", "_val").map();
|
||||
return mockExecutionContextBuilder("watch1")
|
||||
.wid(wid)
|
||||
.payload(new Payload.Simple())
|
||||
.time("watch1", now)
|
||||
.metadata(metadata)
|
||||
.buildMock();
|
||||
}
|
||||
|
||||
/**
|
||||
* TextTemplateEngine that convert templates to uppercase
|
||||
*/
|
||||
class UpperCaseTextTemplateEngine extends TextTemplateEngine {
|
||||
|
||||
public UpperCaseTextTemplateEngine() {
|
||||
super(Settings.EMPTY, mock(ScriptService.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String render(TextTemplate textTemplate, Map<String, Object> model) {
|
||||
return textTemplate.getTemplate().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.watcher.actions.jira;
|
||||
|
||||
import org.elasticsearch.common.settings.ClusterSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraAccount;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraService;
|
||||
import org.junit.Before;
|
||||
|
||||
import static java.util.Collections.singleton;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.xpack.notification.jira.JiraAccountTests.randomIssueDefaults;
|
||||
import static org.elasticsearch.xpack.watcher.actions.ActionBuilders.jiraAction;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JiraActionFactoryTests extends ESTestCase {
|
||||
|
||||
private JiraActionFactory factory;
|
||||
private JiraService service;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
service = mock(JiraService.class);
|
||||
factory = new JiraActionFactory(Settings.EMPTY, mock(TextTemplateEngine.class), service);
|
||||
}
|
||||
|
||||
public void testParseAction() throws Exception {
|
||||
JiraAccount account = mock(JiraAccount.class);
|
||||
when(service.getAccount("_account1")).thenReturn(account);
|
||||
|
||||
JiraAction action = jiraAction("_account1", randomIssueDefaults()).build();
|
||||
XContentBuilder jsonBuilder = jsonBuilder().value(action);
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(jsonBuilder.bytes());
|
||||
parser.nextToken();
|
||||
|
||||
JiraAction parsedAction = JiraAction.parse("_w1", "_a1", parser);
|
||||
assertThat(parsedAction, equalTo(action));
|
||||
}
|
||||
|
||||
public void testParseActionUnknownAccount() throws Exception {
|
||||
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, singleton(JiraService.JIRA_ACCOUNT_SETTING));
|
||||
JiraService service = new JiraService(Settings.EMPTY, null, clusterSettings);
|
||||
factory = new JiraActionFactory(Settings.EMPTY, mock(TextTemplateEngine.class), service);
|
||||
|
||||
JiraAction action = jiraAction("_unknown", randomIssueDefaults()).build();
|
||||
XContentBuilder jsonBuilder = jsonBuilder().value(action);
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(jsonBuilder.bytes());
|
||||
parser.nextToken();
|
||||
|
||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> factory.parseExecutable("_w1", "_a1", parser));
|
||||
assertThat(e.getMessage(), containsString("no account found for name: [_unknown]"));
|
||||
}
|
||||
}
|
@ -0,0 +1,311 @@
|
||||
/*
|
||||
* 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.xpack.watcher.actions.jira;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||
import org.elasticsearch.xpack.common.http.HttpProxy;
|
||||
import org.elasticsearch.xpack.common.http.HttpRequest;
|
||||
import org.elasticsearch.xpack.common.http.HttpResponse;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplate;
|
||||
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraAccount;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraAccountTests;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraIssue;
|
||||
import org.elasticsearch.xpack.notification.jira.JiraService;
|
||||
import org.elasticsearch.xpack.watcher.actions.Action;
|
||||
import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext;
|
||||
import org.elasticsearch.xpack.watcher.execution.Wid;
|
||||
import org.elasticsearch.xpack.watcher.support.Variables;
|
||||
import org.elasticsearch.xpack.watcher.watch.Payload;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.cborBuilder;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.smileBuilder;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.yamlBuilder;
|
||||
import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.mockExecutionContextBuilder;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JiraActionTests extends ESTestCase {
|
||||
|
||||
private JiraService service;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception {
|
||||
service = mock(JiraService.class);
|
||||
}
|
||||
|
||||
public void testParser() throws Exception {
|
||||
final String accountName = randomAsciiOfLength(10);
|
||||
final Map<String, Object> issueDefaults = JiraAccountTests.randomIssueDefaults();
|
||||
|
||||
XContentBuilder builder = jsonBuilder().startObject()
|
||||
.field("account", accountName)
|
||||
.field("fields", issueDefaults)
|
||||
.endObject();
|
||||
|
||||
BytesReference bytes = builder.bytes();
|
||||
logger.info("jira action json [{}]", bytes.utf8ToString());
|
||||
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
|
||||
parser.nextToken();
|
||||
|
||||
JiraAction action = JiraAction.parse("_watch", "_action", parser);
|
||||
|
||||
assertThat(action, notNullValue());
|
||||
assertThat(action.account, is(accountName));
|
||||
assertThat(action.fields, notNullValue());
|
||||
assertThat(action.fields, is(issueDefaults));
|
||||
}
|
||||
|
||||
public void testParserSelfGenerated() throws Exception {
|
||||
final JiraAction action = randomJiraAction();
|
||||
|
||||
XContentBuilder builder = jsonBuilder();
|
||||
action.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
BytesReference bytes = builder.bytes();
|
||||
logger.info("{}", bytes.utf8ToString());
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(bytes);
|
||||
parser.nextToken();
|
||||
|
||||
JiraAction parsedAction = JiraAction.parse("_watch", "_action", parser);
|
||||
|
||||
assertThat(parsedAction, notNullValue());
|
||||
assertThat(parsedAction.proxy, equalTo(action.proxy));
|
||||
assertThat(parsedAction.fields, equalTo(action.fields));
|
||||
assertThat(parsedAction.account, equalTo(action.account));
|
||||
assertThat(parsedAction, is(action));
|
||||
}
|
||||
|
||||
public void testParserInvalid() throws Exception {
|
||||
XContentBuilder builder = jsonBuilder().startObject().field("unknown_field", "value").endObject();
|
||||
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
|
||||
parser.nextToken();
|
||||
|
||||
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> JiraAction.parse("_w", "_a", parser));
|
||||
assertThat(e.getMessage(), is("failed to parse [jira] action [_w/_a]. unexpected token [VALUE_STRING/unknown_field]"));
|
||||
}
|
||||
|
||||
public void testToXContent() throws Exception {
|
||||
final JiraAction action = randomJiraAction();
|
||||
|
||||
BytesReference bytes = null;
|
||||
try (XContentBuilder builder = randomFrom(jsonBuilder(), smileBuilder(), yamlBuilder(), cborBuilder())) {
|
||||
action.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
bytes = builder.bytes();
|
||||
}
|
||||
|
||||
String parsedAccount = null;
|
||||
HttpProxy parsedProxy = null;
|
||||
Map<String, Object> parsedFields = null;
|
||||
|
||||
try (XContentParser parser = XContentHelper.createParser(bytes)) {
|
||||
assertNull(parser.currentToken());
|
||||
parser.nextToken();
|
||||
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
assertThat(token, is(XContentParser.Token.START_OBJECT));
|
||||
|
||||
String currentFieldName = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if ("account".equals(currentFieldName)) {
|
||||
parsedAccount = parser.text();
|
||||
} else if ("proxy".equals(currentFieldName)) {
|
||||
parsedProxy = HttpProxy.parse(parser);
|
||||
} else if ("fields".equals(currentFieldName)) {
|
||||
parsedFields = parser.map();
|
||||
} else {
|
||||
fail("unknown field [" + currentFieldName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(parsedAccount, equalTo(action.getAccount()));
|
||||
assertThat(parsedProxy, equalTo(action.proxy));
|
||||
assertThat(parsedFields, equalTo(action.fields));
|
||||
}
|
||||
|
||||
public void testEquals() throws Exception {
|
||||
final JiraAction action1 = randomJiraAction();
|
||||
|
||||
String account = action1.account;
|
||||
Map<String, Object> fields = action1.fields;
|
||||
HttpProxy proxy = action1.proxy;
|
||||
|
||||
boolean equals = randomBoolean();
|
||||
if (!equals) {
|
||||
equals = true;
|
||||
if (rarely()) {
|
||||
equals = false;
|
||||
account = "another account";
|
||||
}
|
||||
if (rarely()) {
|
||||
equals = false;
|
||||
fields = JiraAccountTests.randomIssueDefaults();
|
||||
}
|
||||
if (rarely()) {
|
||||
equals = false;
|
||||
proxy = randomHttpProxy();
|
||||
}
|
||||
}
|
||||
|
||||
JiraAction action2 = new JiraAction(account, fields, proxy);
|
||||
assertThat(action1.equals(action2), is(equals));
|
||||
}
|
||||
|
||||
public void testExecute() throws Exception {
|
||||
final Map<String, Object> model = new HashMap<>();
|
||||
final MapBuilder<String, Object> actionFields = MapBuilder.newMapBuilder();
|
||||
|
||||
String summary = randomAsciiOfLength(15);
|
||||
actionFields.put("summary", "{{ctx.summary}}");
|
||||
model.put("{{ctx.summary}}", summary);
|
||||
|
||||
String projectId = randomAsciiOfLength(10);
|
||||
actionFields.put("project", singletonMap("id", "{{ctx.project_id}}"));
|
||||
model.put("{{ctx.project_id}}", projectId);
|
||||
|
||||
String description = null;
|
||||
if (randomBoolean()) {
|
||||
description = randomAsciiOfLength(50);
|
||||
actionFields.put("description", description);
|
||||
}
|
||||
|
||||
String issueType = null;
|
||||
if (randomBoolean()) {
|
||||
issueType = randomFrom("Bug", "Test", "Task", "Epic");
|
||||
actionFields.put("issuetype", singletonMap("name", issueType));
|
||||
}
|
||||
|
||||
String watchId = null;
|
||||
if (randomBoolean()) {
|
||||
watchId = "jira_watch_" + randomInt();
|
||||
model.put("{{" + Variables.WATCH_ID + "}}", watchId);
|
||||
actionFields.put("customfield_0", "{{watch_id}}");
|
||||
}
|
||||
|
||||
HttpClient httpClient = mock(HttpClient.class);
|
||||
when(httpClient.execute(any(HttpRequest.class))).thenReturn(new HttpResponse(HttpStatus.SC_CREATED));
|
||||
|
||||
Settings.Builder settings = Settings.builder()
|
||||
.put("url", "https://internal-jira.elastic.co:443")
|
||||
.put("user", "elastic")
|
||||
.put("password", "secret")
|
||||
.put("issue_defaults.customfield_000", "foo")
|
||||
.put("issue_defaults.customfield_001", "bar");
|
||||
|
||||
JiraAccount account = new JiraAccount("account", settings.build(), httpClient);
|
||||
|
||||
JiraService service = mock(JiraService.class);
|
||||
when(service.getAccount(eq("account"))).thenReturn(account);
|
||||
|
||||
JiraAction action = new JiraAction("account", actionFields.immutableMap(), null);
|
||||
ExecutableJiraAction executable = new ExecutableJiraAction(action, logger, service, new ModelTextTemplateEngine(model));
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
Payload payload = new Payload.Simple(data);
|
||||
|
||||
DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
|
||||
Wid wid = new Wid(randomAsciiOfLength(5), randomLong(), now);
|
||||
WatchExecutionContext context = mockExecutionContextBuilder(wid.watchId())
|
||||
.wid(wid)
|
||||
.payload(payload)
|
||||
.time(wid.watchId(), now)
|
||||
.buildMock();
|
||||
when(context.simulateAction("test")).thenReturn(false);
|
||||
|
||||
Action.Result result = executable.execute("test", context, new Payload.Simple());
|
||||
assertThat(result, instanceOf(JiraAction.Result.class));
|
||||
assertThat(result, instanceOf(JiraAction.Executed.class));
|
||||
|
||||
JiraIssue issue = ((JiraAction.Executed) result).getResult();
|
||||
assertThat(issue.getFields().get("summary"), equalTo(summary));
|
||||
assertThat(issue.getFields().get("customfield_000"), equalTo("foo"));
|
||||
assertThat(issue.getFields().get("customfield_001"), equalTo("bar"));
|
||||
assertThat(((Map) issue.getFields().get("project")).get("id"), equalTo(projectId));
|
||||
if (issueType != null) {
|
||||
assertThat(((Map) issue.getFields().get("issuetype")).get("name"), equalTo(issueType));
|
||||
}
|
||||
if (description != null) {
|
||||
assertThat(issue.getFields().get("description"), equalTo(description));
|
||||
}
|
||||
if (watchId != null) {
|
||||
assertThat(issue.getFields().get("customfield_0"), equalTo(watchId));
|
||||
}
|
||||
}
|
||||
|
||||
private static JiraAction randomJiraAction() {
|
||||
String account = null;
|
||||
if (randomBoolean()) {
|
||||
account = randomAsciiOfLength(randomIntBetween(5, 10));
|
||||
}
|
||||
Map<String, Object> fields = emptyMap();
|
||||
if (frequently()) {
|
||||
fields = JiraAccountTests.randomIssueDefaults();
|
||||
}
|
||||
HttpProxy proxy = null;
|
||||
if (randomBoolean()) {
|
||||
proxy = randomHttpProxy();
|
||||
}
|
||||
return new JiraAction(account, fields, proxy);
|
||||
}
|
||||
|
||||
private static HttpProxy randomHttpProxy() {
|
||||
return new HttpProxy(randomFrom("localhost", "www.elastic.co", "198.18.0.0"), randomIntBetween(8000, 10000));
|
||||
}
|
||||
|
||||
/**
|
||||
* TextTemplateEngine that picks up templates from the model if exist,
|
||||
* otherwise returns the template as it is.
|
||||
*/
|
||||
class ModelTextTemplateEngine extends TextTemplateEngine {
|
||||
|
||||
private final Map<String, Object> model;
|
||||
|
||||
public ModelTextTemplateEngine(Map<String, Object> model) {
|
||||
super(Settings.EMPTY, mock(ScriptService.class));
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String render(TextTemplate textTemplate, Map<String, Object> ignoredModel) {
|
||||
String template = textTemplate.getTemplate();
|
||||
if (model.containsKey(template)) {
|
||||
return (String) model.get(template);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,10 @@ dependencies {
|
||||
}
|
||||
|
||||
integTest {
|
||||
// JIRA integration tests are ignored until a JIRA server is available for testing
|
||||
// see https://github.com/elastic/infra/issues/1498
|
||||
systemProperty 'tests.rest.blacklist', 'actions/20_jira/*'
|
||||
|
||||
cluster {
|
||||
plugin ':x-plugins:elasticsearch'
|
||||
setting 'xpack.security.enabled', 'false'
|
||||
@ -13,5 +17,16 @@ integTest {
|
||||
setting 'http.port', '9400'
|
||||
setting 'script.inline', 'true'
|
||||
setting 'script.stored', 'true'
|
||||
// Need to allow more compilations per minute because of the integration tests
|
||||
setting 'script.max_compilations_per_minute', '100'
|
||||
|
||||
// JIRA integration test settings
|
||||
setting 'xpack.notification.jira.account.test.url', 'http://localhost:8080'
|
||||
setting 'xpack.notification.jira.account.test.allow_http', 'true'
|
||||
setting 'xpack.notification.jira.account.test.user', 'jira_user'
|
||||
setting 'xpack.notification.jira.account.test.password', 'secret'
|
||||
setting 'xpack.notification.jira.account.test.issue_defaults.project.key', 'BAS'
|
||||
setting 'xpack.notification.jira.account.test.issue_defaults.labels', ['integration-tests']
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,159 @@
|
||||
---
|
||||
"Test Jira Action":
|
||||
- do:
|
||||
cluster.health:
|
||||
wait_for_status: yellow
|
||||
|
||||
- do:
|
||||
xpack.watcher.put_watch:
|
||||
id: "jira_watch"
|
||||
body: >
|
||||
{
|
||||
"metadata": {
|
||||
"custom_title": "Hello from"
|
||||
},
|
||||
"trigger": {
|
||||
"schedule": {
|
||||
"interval": "1s"
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
"simple": {
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"always": {}
|
||||
},
|
||||
"actions": {
|
||||
"create_jira_issue": {
|
||||
"jira": {
|
||||
"account": "test",
|
||||
"fields": {
|
||||
"summary": "{{ctx.metadata.custom_title}} {{ctx.watch_id}}",
|
||||
"description": "Issue created by the REST integration test [/watcher/actions/10_jira.yaml]",
|
||||
"issuetype" : {
|
||||
"name": "Bug"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
- match: { _id: "jira_watch" }
|
||||
- match: { created: true }
|
||||
|
||||
- do:
|
||||
xpack.watcher.execute_watch:
|
||||
id: "jira_watch"
|
||||
body: >
|
||||
{
|
||||
"trigger_data" : {
|
||||
"triggered_time" : "2012-12-12T12:12:12.120Z",
|
||||
"scheduled_time" : "2000-12-12T12:12:12.120Z"
|
||||
},
|
||||
"record_execution": true
|
||||
}
|
||||
|
||||
- match: { watch_record.watch_id: "jira_watch" }
|
||||
- match: { watch_record.trigger_event.type: "manual" }
|
||||
- match: { watch_record.trigger_event.triggered_time: "2012-12-12T12:12:12.120Z" }
|
||||
- match: { watch_record.trigger_event.manual.schedule.scheduled_time: "2000-12-12T12:12:12.120Z" }
|
||||
- match: { watch_record.state: "executed" }
|
||||
|
||||
# Waits for the watcher history index to be available
|
||||
- do:
|
||||
cluster.health:
|
||||
index: ".watcher-history-*"
|
||||
wait_for_no_relocating_shards: true
|
||||
timeout: 60s
|
||||
|
||||
- do:
|
||||
indices.refresh: {}
|
||||
|
||||
- do:
|
||||
search:
|
||||
index: ".watcher-history-*"
|
||||
- match: { hits.total: 1 }
|
||||
|
||||
- match: { hits.hits.0._type: "watch_record" }
|
||||
- match: { hits.hits.0._source.watch_id: "jira_watch" }
|
||||
- match: { hits.hits.0._source.state: "executed" }
|
||||
|
||||
- match: { hits.hits.0._source.result.actions.0.id: "create_jira_issue" }
|
||||
- match: { hits.hits.0._source.result.actions.0.type: "jira" }
|
||||
- match: { hits.hits.0._source.result.actions.0.status: "success" }
|
||||
- match: { hits.hits.0._source.result.actions.0.jira.fields.summary: "Hello from jira_watch" }
|
||||
- match: { hits.hits.0._source.result.actions.0.jira.fields.issuetype.name: "Bug" }
|
||||
- match: { hits.hits.0._source.result.actions.0.jira.fields.project.key: "BAS" }
|
||||
- match: { hits.hits.0._source.result.actions.0.jira.fields.labels.0: "integration-tests" }
|
||||
- match: { hits.hits.0._source.result.actions.0.jira.result.id: /\d+/ }
|
||||
- match: { hits.hits.0._source.result.actions.0.jira.result.key: /BAS-\d+/ }
|
||||
---
|
||||
"Test Jira Action with Error":
|
||||
- do:
|
||||
cluster.health:
|
||||
wait_for_status: yellow
|
||||
|
||||
- do:
|
||||
xpack.watcher.put_watch:
|
||||
id: "wrong_jira_watch"
|
||||
body: >
|
||||
{
|
||||
"trigger": {
|
||||
"schedule": {
|
||||
"interval": "1d"
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
"simple": {
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"always": {}
|
||||
},
|
||||
"actions": {
|
||||
"fail_to_create_jira_issue": {
|
||||
"jira": {
|
||||
"account": "test",
|
||||
"fields": {
|
||||
"summary": "Hello from {{ctx.watch_id}}",
|
||||
"description": "This Jira issue does not have a type (see below) so it won't be created at all",
|
||||
"issuetype" : {
|
||||
"name": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
- match: { _id: "wrong_jira_watch" }
|
||||
- match: { created: true }
|
||||
|
||||
- do:
|
||||
xpack.watcher.execute_watch:
|
||||
id: "wrong_jira_watch"
|
||||
body: >
|
||||
{
|
||||
"trigger_data" : {
|
||||
"triggered_time" : "2012-12-12T12:12:12.120Z",
|
||||
"scheduled_time" : "2000-12-12T12:12:12.120Z"
|
||||
}
|
||||
}
|
||||
|
||||
- match: { watch_record.watch_id: "wrong_jira_watch" }
|
||||
- match: { watch_record.trigger_event.type: "manual" }
|
||||
- match: { watch_record.trigger_event.triggered_time: "2012-12-12T12:12:12.120Z" }
|
||||
- match: { watch_record.trigger_event.manual.schedule.scheduled_time: "2000-12-12T12:12:12.120Z" }
|
||||
- match: { watch_record.state: "executed" }
|
||||
|
||||
- match: { watch_record.result.actions.0.id: "fail_to_create_jira_issue" }
|
||||
- match: { watch_record.result.actions.0.type: "jira" }
|
||||
- match: { watch_record.result.actions.0.status: "failure" }
|
||||
- match: { watch_record.result.actions.0.jira.fields.summary: "Hello from wrong_jira_watch" }
|
||||
- is_false: watch_record.result.actions.0.jira.fields.issuetype.name
|
||||
- match: { watch_record.result.actions.0.jira.fields.project.key: "BAS" }
|
||||
- match: { watch_record.result.actions.0.jira.fields.labels.0: "integration-tests" }
|
||||
- match: { watch_record.result.actions.0.jira.reason: "Bad Request - Field [issuetype] has error [issue type is required]\n" }
|
||||
- match: { watch_record.result.actions.0.jira.request.method: "post" }
|
||||
- match: { watch_record.result.actions.0.jira.request.path: "/rest/api/2/issue" }
|
||||
- match: { watch_record.result.actions.0.jira.response.body: "{\"errorMessages\":[],\"errors\":{\"issuetype\":\"issue type is required\"}}" }
|
Loading…
x
Reference in New Issue
Block a user