Add mappings for Jira action (elastic/elasticsearch#4155)

This commit updates the watch_history.json file so that it includes mappings for the new Jira action. It also update the JiraIssue format so that it now includes the name of the account used to create the Jira issue. It also update the REST tests to check that Jira action result are searchable and hide the user's password.

Original commit: elastic/x-pack-elasticsearch@75888f7748
This commit is contained in:
Tanguy Leroux 2016-11-23 11:53:06 +01:00 committed by GitHub
parent 5d042fc1b8
commit a32f2096a6
5 changed files with 308 additions and 26 deletions

View File

@ -90,7 +90,7 @@ public class JiraAccount {
.build(); .build();
HttpResponse response = httpClient.execute(request); HttpResponse response = httpClient.execute(request);
return JiraIssue.responded(fields, request, response); return JiraIssue.responded(name, fields, request, response);
} }
private static SettingsException requiredSettingException(String account, String setting) { private static SettingsException requiredSettingException(String account, String setting) {

View File

@ -17,6 +17,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.common.http.HttpRequest; import org.elasticsearch.xpack.common.http.HttpRequest;
import org.elasticsearch.xpack.common.http.HttpResponse; import org.elasticsearch.xpack.common.http.HttpResponse;
import org.elasticsearch.xpack.watcher.actions.jira.JiraAction; import org.elasticsearch.xpack.watcher.actions.jira.JiraAction;
import org.elasticsearch.xpack.watcher.support.xcontent.WatcherParams;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -26,16 +27,18 @@ import java.util.Objects;
public class JiraIssue implements ToXContent { public class JiraIssue implements ToXContent {
@Nullable final String account;
private final Map<String, Object> fields; private final Map<String, Object> fields;
@Nullable private final HttpRequest request; @Nullable private final HttpRequest request;
@Nullable private final HttpResponse response; @Nullable private final HttpResponse response;
@Nullable private final String failureReason; @Nullable private final String failureReason;
public static JiraIssue responded(Map<String, Object> fields, HttpRequest request, HttpResponse response) { public static JiraIssue responded(String account, Map<String, Object> fields, HttpRequest request, HttpResponse response) {
return new JiraIssue(fields, request, response, resolveFailureReason(response)); return new JiraIssue(account, fields, request, response, resolveFailureReason(response));
} }
JiraIssue(Map<String, Object> fields, HttpRequest request, HttpResponse response, String failureReason) { JiraIssue(String account, Map<String, Object> fields, HttpRequest request, HttpResponse response, String failureReason) {
this.account = account;
this.fields = fields; this.fields = fields;
this.request = request; this.request = request;
this.response = response; this.response = response;
@ -46,6 +49,10 @@ public class JiraIssue implements ToXContent {
return failureReason == null; return failureReason == null;
} }
public String getAccount() {
return account;
}
public HttpRequest getRequest() { public HttpRequest getRequest() {
return request; return request;
} }
@ -67,28 +74,30 @@ public class JiraIssue implements ToXContent {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
JiraIssue sentEvent = (JiraIssue) o; JiraIssue issue = (JiraIssue) o;
return Objects.equals(fields, sentEvent.fields) && return Objects.equals(account, issue.account) &&
Objects.equals(request, sentEvent.request) && Objects.equals(fields, issue.fields) &&
Objects.equals(response, sentEvent.response) && Objects.equals(request, issue.request) &&
Objects.equals(failureReason, sentEvent.failureReason); Objects.equals(response, issue.response) &&
Objects.equals(failureReason, issue.failureReason);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(fields, request, response, failureReason); return Objects.hash(account, fields, request, response, failureReason);
} }
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Field.ACCOUNT.getPreferredName(), account);
if (fields != null) { if (fields != null) {
builder.field(Field.FIELDS.getPreferredName(), fields); builder.field(Field.FIELDS.getPreferredName(), fields);
} }
if (successful() == false) { if (successful() == false) {
builder.field(Field.REASON.getPreferredName(), failureReason); builder.field(Field.REASON.getPreferredName(), failureReason);
if (request != null) { if (request != null) {
builder.field(Field.REQUEST.getPreferredName(), request, params); builder.field(Field.REQUEST.getPreferredName(), request, WatcherParams.builder().hideSecrets(true).build());
} }
if (response != null) { if (response != null) {
builder.field(Field.RESPONSE.getPreferredName(), response, params); builder.field(Field.RESPONSE.getPreferredName(), response, params);
@ -181,6 +190,7 @@ public class JiraIssue implements ToXContent {
private interface Field { private interface Field {
ParseField FIELDS = JiraAction.Field.FIELDS; ParseField FIELDS = JiraAction.Field.FIELDS;
ParseField ACCOUNT = new ParseField("account");
ParseField REASON = new ParseField("reason"); ParseField REASON = new ParseField("reason");
ParseField REQUEST = new ParseField("request"); ParseField REQUEST = new ParseField("request");
ParseField RESPONSE = new ParseField("response"); ParseField RESPONSE = new ParseField("response");

View File

@ -28,6 +28,15 @@
"enabled": false "enabled": false
} }
} }
},
{
"disabled_jira_custom_fields": {
"path_match": "result.actions.jira.fields.customfield_*",
"mapping": {
"type": "object",
"enabled": false
}
}
} }
], ],
"dynamic": false, "dynamic": false,
@ -358,6 +367,80 @@
} }
} }
}, },
"jira" : {
"type": "object",
"dynamic": true,
"properties": {
"account": {
"type": "keyword"
},
"reason": {
"type": "text"
},
"request" : {
"type" : "object",
"enabled" : false
},
"response" : {
"type" : "object",
"enabled" : false
},
"fields": {
"type": "object",
"dynamic": true,
"properties": {
"summary": {
"type": "text"
},
"description": {
"type": "text"
},
"labels" : {
"type": "text"
},
"project" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"key" : {
"type" : "keyword"
},
"id" : {
"type" : "keyword"
}
}
},
"issuetype" : {
"type" : "object",
"dynamic" : true,
"properties" : {
"name" : {
"type": "keyword"
},
"id" : {
"type" : "keyword"
}
}
}
}
},
"result": {
"type": "object",
"dynamic": true,
"properties" : {
"id" : {
"type" : "keyword"
},
"key" : {
"type" : "keyword"
},
"self" : {
"type" : "keyword"
}
}
}
}
},
"slack" : { "slack" : {
"type": "object", "type": "object",
"dynamic": true, "dynamic": true,

View File

@ -52,6 +52,7 @@ public class JiraIssueTests extends ESTestCase {
HttpRequest parsedRequest = null; HttpRequest parsedRequest = null;
HttpResponse parsedResponse = null; HttpResponse parsedResponse = null;
String parsedAccount = null;
String parsedReason = null; String parsedReason = null;
try (XContentParser parser = XContentHelper.createParser(bytes)) { try (XContentParser parser = XContentHelper.createParser(bytes)) {
@ -65,6 +66,8 @@ public class JiraIssueTests extends ESTestCase {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if ("account".equals(currentFieldName)) {
parsedAccount = parser.text();
} else if ("result".equals(currentFieldName)) { } else if ("result".equals(currentFieldName)) {
parsedResult = parser.map(); parsedResult = parser.map();
} else if ("request".equals(currentFieldName)) { } else if ("request".equals(currentFieldName)) {
@ -83,6 +86,7 @@ public class JiraIssueTests extends ESTestCase {
} }
} }
assertThat(parsedAccount, equalTo(issue.getAccount()));
assertThat(parsedFields, equalTo(issue.getFields())); assertThat(parsedFields, equalTo(issue.getFields()));
if (issue.successful()) { if (issue.successful()) {
assertThat(parsedResult, hasEntry("key", "TEST")); assertThat(parsedResult, hasEntry("key", "TEST"));
@ -108,11 +112,12 @@ public class JiraIssueTests extends ESTestCase {
} }
} }
JiraIssue issue2 = new JiraIssue(fields, issue1.getRequest(), issue1.getResponse(), issue1.getFailureReason()); JiraIssue issue2 = new JiraIssue(issue1.getAccount(), fields, issue1.getRequest(), issue1.getResponse(), issue1.getFailureReason());
assertThat(issue1.equals(issue2), is(equals)); assertThat(issue1.equals(issue2), is(equals));
} }
private static JiraIssue randomJiraIssue() throws IOException { private static JiraIssue randomJiraIssue() throws IOException {
String account = "account_" + randomIntBetween(0, 100);
Map<String, Object> fields = randomIssueDefaults(); Map<String, Object> fields = randomIssueDefaults();
HttpRequest request = HttpRequest.builder(randomFrom("localhost", "internal-jira.elastic.co"), randomFrom(80, 443)) HttpRequest request = HttpRequest.builder(randomFrom("localhost", "internal-jira.elastic.co"), randomFrom(80, 443))
.method(HttpMethod.POST) .method(HttpMethod.POST)
@ -121,8 +126,8 @@ public class JiraIssueTests extends ESTestCase {
.build(); .build();
if (rarely()) { if (rarely()) {
Tuple<Integer, String> error = randomHttpError(); Tuple<Integer, String> error = randomHttpError();
return JiraIssue.responded(fields, request, new HttpResponse(error.v1(), "{\"error\": \"" + error.v2() + "\"}")); return JiraIssue.responded(account, fields, request, new HttpResponse(error.v1(), "{\"error\": \"" + error.v2() + "\"}"));
} }
return JiraIssue.responded(fields, request, new HttpResponse(HttpStatus.SC_CREATED, "{\"key\": \"TEST\"}")); return JiraIssue.responded(account, fields, request, new HttpResponse(HttpStatus.SC_CREATED, "{\"key\": \"TEST\"}"));
} }
} }

View File

@ -82,12 +82,40 @@
- match: { hits.hits.0._source.result.actions.0.id: "create_jira_issue" } - 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.type: "jira" }
- match: { hits.hits.0._source.result.actions.0.status: "success" } - match: { hits.hits.0._source.result.actions.0.status: "success" }
- match: { hits.hits.0._source.result.actions.0.jira.account: "test" }
- 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.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.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.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.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.id: /\d+/ }
- match: { hits.hits.0._source.result.actions.0.jira.result.key: /BAS-\d+/ } - match: { hits.hits.0._source.result.actions.0.jira.result.key: /BAS-\d+/ }
- match: { hits.hits.0._source.result.actions.0.jira.result.self: /http(.)*/ }
- set: { hits.hits.0._id: id }
- set: { hits.hits.0._source.result.actions.0.jira.result.self: self }
- do:
search:
index: ".watcher-history-*"
body:
query:
match:
result.actions.jira.fields.project.key: "BAS"
- match: { hits.total: 1 }
- match: { hits.hits.0._id: $id }
- match: { hits.hits.0._source.result.actions.0.jira.result.self: $self }
- do:
search:
index: ".watcher-history-*"
body:
query:
match:
result.actions.jira.fields.summary: "hello jira_watch"
- match: { hits.total: 1 }
- match: { hits.hits.0._id: $id }
- match: { hits.hits.0._source.result.actions.0.jira.result.self: $self }
--- ---
"Test Jira Action with Error": "Test Jira Action with Error":
- do: - do:
@ -137,7 +165,8 @@
"trigger_data" : { "trigger_data" : {
"triggered_time" : "2012-12-12T12:12:12.120Z", "triggered_time" : "2012-12-12T12:12:12.120Z",
"scheduled_time" : "2000-12-12T12:12:12.120Z" "scheduled_time" : "2000-12-12T12:12:12.120Z"
} },
"record_execution": true
} }
- match: { watch_record.watch_id: "wrong_jira_watch" } - match: { watch_record.watch_id: "wrong_jira_watch" }
@ -146,14 +175,169 @@
- match: { watch_record.trigger_event.manual.schedule.scheduled_time: "2000-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.state: "executed" }
- match: { watch_record.result.actions.0.id: "fail_to_create_jira_issue" } # Waits for the watcher history index to be available
- match: { watch_record.result.actions.0.type: "jira" } - do:
- match: { watch_record.result.actions.0.status: "failure" } cluster.health:
- match: { watch_record.result.actions.0.jira.fields.summary: "Hello from wrong_jira_watch" } index: ".watcher-history-*"
- is_false: watch_record.result.actions.0.jira.fields.issuetype.name wait_for_no_relocating_shards: true
- match: { watch_record.result.actions.0.jira.fields.project.key: "BAS" } timeout: 60s
- 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" } - do:
- match: { watch_record.result.actions.0.jira.request.method: "post" } indices.refresh: {}
- 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\"}}" } - do:
search:
index: ".watcher-history-*"
body:
query:
match:
result.actions.status: "failure"
- match: { hits.total: 1 }
- match: { hits.hits.0._type: "watch_record" }
- match: { hits.hits.0._source.watch_id: "wrong_jira_watch" }
- match: { hits.hits.0._source.state: "executed" }
- match: { hits.hits.0._source.result.actions.0.id: "fail_to_create_jira_issue" }
- match: { hits.hits.0._source.result.actions.0.type: "jira" }
- match: { hits.hits.0._source.result.actions.0.status: "failure" }
- match: { hits.hits.0._source.result.actions.0.jira.account: "test" }
- match: { hits.hits.0._source.result.actions.0.jira.fields.summary: "Hello from wrong_jira_watch" }
- is_false: hits.hits.0._source.result.actions.0.jira.fields.issuetype.name
- 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.reason: "Bad Request - Field [issuetype] has error [issue type is required]\n" }
- match: { hits.hits.0._source.result.actions.0.jira.request.method: "post" }
- match: { hits.hits.0._source.result.actions.0.jira.request.path: "/rest/api/2/issue" }
- match: { hits.hits.0._source.result.actions.0.jira.request.auth.basic.username: "jira_user" }
- is_false: hits.hits.0._source.result.actions.0.jira.request.auth.basic.password
- match: { hits.hits.0._source.result.actions.0.jira.response.body: "{\"errorMessages\":[],\"errors\":{\"issuetype\":\"issue type is required\"}}" }
---
"Test Jira action with custom fields of different types":
- do:
cluster.health:
wait_for_status: yellow
- do:
xpack.watcher.put_watch:
id: "jira_watch_with_custom_field_one"
body: >
{
"trigger": {
"schedule": {
"interval": "1s"
}
},
"input": {
"simple": {
}
},
"condition": {
"always": {}
},
"actions": {
"create_jira_issue": {
"jira": {
"account": "test",
"fields": {
"summary": "Jira watch with custom field of string type",
"description": "Issue created by the REST integration test [/watcher/actions/10_jira.yaml]",
"issuetype" : {
"name": "Bug"
},
"customfield_70000": "jira-software-users"
}
}
}
}
}
- match: { _id: "jira_watch_with_custom_field_one" }
- match: { created: true }
- do:
xpack.watcher.execute_watch:
id: "jira_watch_with_custom_field_one"
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_with_custom_field_one" }
- match: { watch_record.state: "executed" }
- do:
xpack.watcher.put_watch:
id: "jira_watch_with_custom_field_two"
body: >
{
"trigger": {
"schedule": {
"interval": "1s"
}
},
"input": {
"simple": {
}
},
"condition": {
"always": {}
},
"actions": {
"create_jira_issue": {
"jira": {
"account": "test",
"fields": {
"summary": "Jira watch with custom field of object (Jira's CascadingSelectField) type",
"description": "Issue created by the REST integration test [/watcher/actions/10_jira.yaml]",
"issuetype" : {
"name": "Bug"
},
"customfield_70000": {
"value": "green",
"child": {
"value":"blue"
}
}
}
}
}
}
}
- match: { _id: "jira_watch_with_custom_field_two" }
- match: { created: true }
- do:
xpack.watcher.execute_watch:
id: "jira_watch_with_custom_field_two"
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_with_custom_field_two" }
- match: { watch_record.state: "executed" }
- do:
indices.refresh:
index: ".watcher-history-*"
- do:
search:
index: ".watcher-history-*"
body:
query:
match:
result.actions.status: "failure"
- match: { hits.total: 2 }