From 970570545dbbb703dcd508e246ade7bbdc21f1f9 Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Mon, 23 Jul 2018 10:28:16 -0500 Subject: [PATCH 01/10] Watcher: migrate PagerDuty v1 events API to v2 API The PagerDuty v1 API is EOL and will stop accepting new accounts shortly. This commit swaps out the watcher use of the v1 API with the new v2 API. It does not change anything about the existing watcher API. Closes #32243 --- .../notification/pagerduty/IncidentEvent.java | 78 +++++++++++++++---- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java index 0fb1a52d28633..62796b7fc73d0 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java @@ -39,7 +39,8 @@ public class IncidentEvent implements ToXContentObject { static final String HOST = "events.pagerduty.com"; - static final String PATH = "/generic/2010-04-15/create_event.json"; + static final String PATH = "/v2/enqueue"; + static final String ACCEPT_HEADER = "application/vnd.pagerduty+json;version=2"; final String description; @Nullable final HttpProxy proxy; @@ -99,40 +100,85 @@ public HttpRequest createRequest(final String serviceKey, final Payload payload) .scheme(Scheme.HTTPS) .path(PATH) .proxy(proxy) + .setHeader("Accept", ACCEPT_HEADER) .jsonBody(new ToXContent() { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.field(Fields.SERVICE_KEY.getPreferredName(), serviceKey); - builder.field(Fields.EVENT_TYPE.getPreferredName(), eventType); - builder.field(Fields.DESCRIPTION.getPreferredName(), description); + builder.field("routing_key", serviceKey); + builder.field("event_action", eventType); if (incidentKey != null) { - builder.field(Fields.INCIDENT_KEY.getPreferredName(), incidentKey); + builder.field("dedup_key", incidentKey); } + + builder.startObject("payload"); + { + builder.field("summary", description); + + if (attachPayload) { + builder.startObject("custom_details"); + builder.field(Fields.PAYLOAD.getPreferredName()); + payload.toXContent(builder, params); + builder.endObject(); + } + + // TODO externalize this into something user editable + builder.field("source", "watcher"); + builder.field("severity", "critical"); + } + builder.endObject(); + if (client != null) { builder.field(Fields.CLIENT.getPreferredName(), client); } if (clientUrl != null) { builder.field(Fields.CLIENT_URL.getPreferredName(), clientUrl); } - if (attachPayload) { - builder.startObject(Fields.DETAILS.getPreferredName()); - builder.field(Fields.PAYLOAD.getPreferredName()); - payload.toXContent(builder, params); - builder.endObject(); - } + if (contexts != null && contexts.length > 0) { - builder.startArray(Fields.CONTEXTS.getPreferredName()); - for (IncidentEventContext context : contexts) { - context.toXContent(builder, params); - } - builder.endArray(); + toXContentV2Contexts(builder, params, contexts); } + return builder; } }) .build(); } + /** + * Turns the V1 API contexts into 2 distinct lists, images and links. The V2 API has separated these out into 2 top level fields. + */ + private void toXContentV2Contexts(XContentBuilder builder, ToXContent.Params params, + IncidentEventContext[] contexts) throws IOException { + // contexts can be either links or images, and the v2 api needs them separate + List links = new ArrayList<>(); + List images = new ArrayList<>(); + for (IncidentEventContext context: contexts) { + if (context.type == IncidentEventContext.Type.LINK) { + links.add(context); + } else if (context.type == IncidentEventContext.Type.IMAGE) { + images.add(context); + } + } + if (links.isEmpty() == false) { + builder.startArray("links"); + { + for (IncidentEventContext link: links) { + link.toXContent(builder, params); + } + } + builder.endArray(); + } + if (images.isEmpty() == false) { + builder.startArray("images"); + { + for (IncidentEventContext image: images) { + image.toXContent(builder, params); + } + } + builder.endArray(); + } + } + @Override public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { builder.startObject(); From 426c81dccf99d4727ee0fc09ab35acd97e976835 Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Tue, 24 Jul 2018 19:56:43 -0500 Subject: [PATCH 02/10] Add watchID to source --- .../actions/pagerduty/ExecutablePagerDutyAction.java | 2 +- .../watcher/notification/pagerduty/IncidentEvent.java | 8 ++++++-- .../watcher/notification/pagerduty/PagerDutyAccount.java | 4 ++-- .../watcher/actions/pagerduty/PagerDutyActionTests.java | 2 +- .../notification/pagerduty/PagerDutyAccountsTests.java | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/pagerduty/ExecutablePagerDutyAction.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/pagerduty/ExecutablePagerDutyAction.java index 224e72e1a3da5..59381dc33362c 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/pagerduty/ExecutablePagerDutyAction.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/actions/pagerduty/ExecutablePagerDutyAction.java @@ -47,7 +47,7 @@ public Action.Result execute(final String actionId, WatchExecutionContext ctx, P return new PagerDutyAction.Result.Simulated(event); } - SentEvent sentEvent = account.send(event, payload); + SentEvent sentEvent = account.send(event, payload, ctx.id().watchId()); return new PagerDutyAction.Result.Executed(account.getName(), sentEvent); } diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java index 62796b7fc73d0..be48a2d0b9799 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java @@ -94,7 +94,7 @@ public int hashCode() { return result; } - public HttpRequest createRequest(final String serviceKey, final Payload payload) throws IOException { + public HttpRequest createRequest(final String serviceKey, final Payload payload, final String watchId) throws IOException { return HttpRequest.builder(HOST, -1) .method(HttpMethod.POST) .scheme(Scheme.HTTPS) @@ -122,7 +122,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } // TODO externalize this into something user editable - builder.field("source", "watcher"); + if (watchId != null) { + builder.field("source", watchId); + } else { + builder.field("source", "watcher"); + } builder.field("severity", "critical"); } builder.endObject(); diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccount.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccount.java index 5cf1a77f9711a..b2498a749d7b2 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccount.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccount.java @@ -48,8 +48,8 @@ public IncidentEventDefaults getDefaults() { return eventDefaults; } - public SentEvent send(IncidentEvent event, Payload payload) throws IOException { - HttpRequest request = event.createRequest(serviceKey, payload); + public SentEvent send(IncidentEvent event, Payload payload, String watchId) throws IOException { + HttpRequest request = event.createRequest(serviceKey, payload, watchId); HttpResponse response = httpClient.execute(request); return SentEvent.responded(event, request, response); } diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/pagerduty/PagerDutyActionTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/pagerduty/PagerDutyActionTests.java index 6f57ccd82d930..07a55c628ec1c 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/pagerduty/PagerDutyActionTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/pagerduty/PagerDutyActionTests.java @@ -111,7 +111,7 @@ public void testExecute() throws Exception { when(response.status()).thenReturn(200); HttpRequest request = mock(HttpRequest.class); SentEvent sentEvent = SentEvent.responded(event, request, response); - when(account.send(event, payload)).thenReturn(sentEvent); + when(account.send(event, payload, wid.watchId())).thenReturn(sentEvent); when(service.getAccount(accountName)).thenReturn(account); Action.Result result = executable.execute("_id", ctx, payload); diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccountsTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccountsTests.java index d70badc4bec22..6ee930cfee2ec 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccountsTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccountsTests.java @@ -48,7 +48,7 @@ public void testProxy() throws Exception { HttpProxy proxy = new HttpProxy("localhost", 8080); IncidentEvent event = new IncidentEvent("foo", null, null, null, null, account.getName(), true, null, proxy); - account.send(event, Payload.EMPTY); + account.send(event, Payload.EMPTY, null); HttpRequest request = argumentCaptor.getValue(); assertThat(request.proxy(), is(proxy)); @@ -72,7 +72,7 @@ public void testContextIsSentCorrect() throws Exception { "https://www.elastic.co/products/x-pack/alerting", "X-Pack-Alerting website link with log") }; IncidentEvent event = new IncidentEvent("foo", null, null, null, null, account.getName(), true, contexts, HttpProxy.NO_PROXY); - account.send(event, Payload.EMPTY); + account.send(event, Payload.EMPTY, null); HttpRequest request = argumentCaptor.getValue(); ObjectPath source = ObjectPath.createFromXContent(JsonXContent.jsonXContent, new BytesArray(request.body())); From 003896a6675d33363c24e36c1337a31a1a4f4fbc Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Tue, 24 Jul 2018 19:59:08 -0500 Subject: [PATCH 03/10] making the payload check better --- .../xpack/watcher/notification/pagerduty/IncidentEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java index be48a2d0b9799..9d1f3b13e9cf8 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java @@ -114,7 +114,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws { builder.field("summary", description); - if (attachPayload) { + if (attachPayload && payload != null) { builder.startObject("custom_details"); builder.field(Fields.PAYLOAD.getPreferredName()); payload.toXContent(builder, params); From d3e59219de6fdbb20bb0afa41db495f2ff919c7a Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Tue, 24 Jul 2018 20:04:26 -0500 Subject: [PATCH 04/10] Cleanup xcontent --- .../watcher/notification/pagerduty/IncidentEvent.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java index 9d1f3b13e9cf8..4fc725e4ea85d 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java @@ -116,17 +116,18 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (attachPayload && payload != null) { builder.startObject("custom_details"); - builder.field(Fields.PAYLOAD.getPreferredName()); - payload.toXContent(builder, params); + { + builder.field(Fields.PAYLOAD.getPreferredName(), payload, params); + } builder.endObject(); } - // TODO externalize this into something user editable if (watchId != null) { builder.field("source", watchId); } else { builder.field("source", "watcher"); } + // TODO externalize this into something user editable builder.field("severity", "critical"); } builder.endObject(); From 6644f2ba404729efd43f3e02d57c3a9a82eed34e Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Mon, 30 Jul 2018 11:13:42 -0500 Subject: [PATCH 05/10] Fix test changing due to contexts becoming links/images --- .../notification/pagerduty/PagerDutyAccountsTests.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccountsTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccountsTests.java index 6ee930cfee2ec..1e88c69614270 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccountsTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/PagerDutyAccountsTests.java @@ -24,6 +24,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -76,7 +77,9 @@ public void testContextIsSentCorrect() throws Exception { HttpRequest request = argumentCaptor.getValue(); ObjectPath source = ObjectPath.createFromXContent(JsonXContent.jsonXContent, new BytesArray(request.body())); - assertThat(source.evaluate("contexts"), notNullValue()); + assertThat(source.evaluate("contexts"), nullValue()); + assertThat(source.evaluate("links"), notNullValue()); + assertThat(source.evaluate("images"), notNullValue()); } private void addAccountSettings(String name, Settings.Builder builder) { From 71629ae194a8eeecaeef465a619eb4547c0e1db1 Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Wed, 1 Aug 2018 11:52:40 -0500 Subject: [PATCH 06/10] Move payload generation for PD API to a helper method and test that it emits valid json --- .../notification/pagerduty/IncidentEvent.java | 124 +++++++++----- .../pagerduty/IncidentEventContext.java | 79 +++++++++ .../pagerduty/IncidentEventTests.java | 157 ++++++++++++++++++ 3 files changed, 316 insertions(+), 44 deletions(-) create mode 100644 x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java index 4fc725e4ea85d..d770e690ae380 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java @@ -101,52 +101,52 @@ public HttpRequest createRequest(final String serviceKey, final Payload payload, .path(PATH) .proxy(proxy) .setHeader("Accept", ACCEPT_HEADER) - .jsonBody(new ToXContent() { - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.field("routing_key", serviceKey); - builder.field("event_action", eventType); - if (incidentKey != null) { - builder.field("dedup_key", incidentKey); - } + .jsonBody((b, p) -> buildAPIXContent(b, p, serviceKey, payload, watchId)) + .build(); + } - builder.startObject("payload"); - { - builder.field("summary", description); + public XContentBuilder buildAPIXContent(XContentBuilder builder, Params params, String serviceKey, + Payload payload, String watchId) throws IOException { + builder.field(Fields.ROUTING_KEY.getPreferredName(), serviceKey); + builder.field(Fields.EVENT_ACTION.getPreferredName(), eventType); + if (incidentKey != null) { + builder.field(Fields.DEDUP_KEY.getPreferredName(), incidentKey); + } - if (attachPayload && payload != null) { - builder.startObject("custom_details"); - { - builder.field(Fields.PAYLOAD.getPreferredName(), payload, params); - } - builder.endObject(); - } + builder.startObject(Fields.PAYLOAD.getPreferredName()); + { + builder.field(Fields.SUMMARY.getPreferredName(), description); - if (watchId != null) { - builder.field("source", watchId); - } else { - builder.field("source", "watcher"); - } - // TODO externalize this into something user editable - builder.field("severity", "critical"); - } - builder.endObject(); + if (attachPayload && payload != null) { + builder.startObject(Fields.CUSTOM_DETAILS.getPreferredName()); + { + builder.field(Fields.PAYLOAD.getPreferredName(), payload, params); + } + builder.endObject(); + } - if (client != null) { - builder.field(Fields.CLIENT.getPreferredName(), client); - } - if (clientUrl != null) { - builder.field(Fields.CLIENT_URL.getPreferredName(), clientUrl); - } + if (watchId != null) { + builder.field(Fields.SOURCE.getPreferredName(), watchId); + } else { + builder.field(Fields.SOURCE.getPreferredName(), "watcher"); + } + // TODO externalize this into something user editable + builder.field(Fields.SEVERITY.getPreferredName(), "critical"); + } + builder.endObject(); - if (contexts != null && contexts.length > 0) { - toXContentV2Contexts(builder, params, contexts); - } + if (client != null) { + builder.field(Fields.CLIENT.getPreferredName(), client); + } + if (clientUrl != null) { + builder.field(Fields.CLIENT_URL.getPreferredName(), clientUrl); + } - return builder; - } - }) - .build(); + if (contexts != null && contexts.length > 0) { + toXContentV2Contexts(builder, params, contexts); + } + + return builder; } /** @@ -165,7 +165,7 @@ private void toXContentV2Contexts(XContentBuilder builder, ToXContent.Params par } } if (links.isEmpty() == false) { - builder.startArray("links"); + builder.startArray(Fields.LINKS.getPreferredName()); { for (IncidentEventContext link: links) { link.toXContent(builder, params); @@ -174,7 +174,7 @@ private void toXContentV2Contexts(XContentBuilder builder, ToXContent.Params par builder.endArray(); } if (images.isEmpty() == false) { - builder.startArray("images"); + builder.startArray(Fields.IMAGES.getPreferredName()); { for (IncidentEventContext image: images) { image.toXContent(builder, params); @@ -496,8 +496,44 @@ interface Fields { // we need to keep this for BWC ParseField CONTEXT_DEPRECATED = new ParseField("context"); - ParseField SERVICE_KEY = new ParseField("service_key"); ParseField PAYLOAD = new ParseField("payload"); - ParseField DETAILS = new ParseField("details"); + ParseField ROUTING_KEY = new ParseField("routing_key"); + ParseField EVENT_ACTION = new ParseField("event_action"); + ParseField DEDUP_KEY = new ParseField("dedup_key"); + ParseField SUMMARY = new ParseField("summary"); + ParseField SOURCE = new ParseField("source"); + ParseField SEVERITY = new ParseField("severity"); + ParseField LINKS = new ParseField("links"); + ParseField IMAGES = new ParseField("images"); + ParseField CUSTOM_DETAILS = new ParseField("custom_details"); + + /* + if (incidentKey != null) { + builder.field("dedup_key", incidentKey); + } + + builder.startObject(Fields.PAYLOAD.getPreferredName()); + { + builder.field("summary", description); + + if (attachPayload && payload != null) { + builder.startObject("custom_details"); + { + builder.field(Fields.PAYLOAD.getPreferredName(), payload, params); + } + builder.endObject(); + } + + if (watchId != null) { + builder.field("source", watchId); + } else { + builder.field("source", "watcher"); + } + // TODO externalize this into something user editable + builder.field("severity", "critical"); + } + builder.endObject(); + */ + } } diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventContext.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventContext.java index d43829346b626..cd9924ae9dca7 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventContext.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventContext.java @@ -92,6 +92,85 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder.endObject(); } + public static IncidentEventContext parse(XContentParser parser) throws IOException { + Type type = null; + String href = null; + String text = null; + String src = null; + String alt = null; + + String currentFieldName = null; + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (Strings.hasLength(currentFieldName)) { + if (XField.TYPE.match(currentFieldName, parser.getDeprecationHandler())) { + try { + type = Type.valueOf(parser.text().toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + String msg = "could not parse trigger incident event context. unknown context type [{}]"; + throw new ElasticsearchParseException(msg, parser.text()); + } + } else { + if (XField.HREF.match(currentFieldName, parser.getDeprecationHandler())) { + href = parser.text(); + } else if (XField.TEXT.match(currentFieldName, parser.getDeprecationHandler())) { + text = parser.text(); + } else if (XField.SRC.match(currentFieldName, parser.getDeprecationHandler())) { + src = parser.text(); + } else if (XField.ALT.match(currentFieldName, parser.getDeprecationHandler())) { + alt = parser.text(); + } else { + String msg = "could not parse trigger incident event context. unknown field [{}]"; + throw new ElasticsearchParseException(msg, currentFieldName); + } + } + } + } + + return createAndValidateTemplate(type, href, src, alt, text); + } + + private static IncidentEventContext createAndValidateTemplate(Type type, String href, String src, String alt, + String text) { + if (type == null) { + throw new ElasticsearchParseException("could not parse trigger incident event context. missing required field [{}]", + XField.TYPE.getPreferredName()); + } + + switch (type) { + case LINK: + if (href == null) { + throw new ElasticsearchParseException("could not parse trigger incident event context. missing required field " + + "[{}] for [{}] context", XField.HREF.getPreferredName(), Type.LINK.name().toLowerCase(Locale.ROOT)); + } + if (src != null) { + throw new ElasticsearchParseException("could not parse trigger incident event context. unexpected field [{}] for " + + "[{}] context", XField.SRC.getPreferredName(), Type.LINK.name().toLowerCase(Locale.ROOT)); + } + if (alt != null) { + throw new ElasticsearchParseException("could not parse trigger incident event context. unexpected field [{}] for " + + "[{}] context", XField.ALT.getPreferredName(), Type.LINK.name().toLowerCase(Locale.ROOT)); + } + return link(href, text); + case IMAGE: + if (src == null) { + throw new ElasticsearchParseException("could not parse trigger incident event context. missing required field " + + "[{}] for [{}] context", XField.SRC.getPreferredName(), Type.IMAGE.name().toLowerCase(Locale.ROOT)); + } + if (text != null) { + throw new ElasticsearchParseException("could not parse trigger incident event context. unexpected field [{}] for " + + "[{}] context", XField.TEXT.getPreferredName(), Type.IMAGE.name().toLowerCase(Locale.ROOT)); + } + return image(src, href, alt); + default: + throw new ElasticsearchParseException("could not parse trigger incident event context. unknown context type [{}]", + type); + } + } + + public static class Template implements ToXContentObject { final Type type; diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java new file mode 100644 index 0000000000000..62c23a6092688 --- /dev/null +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java @@ -0,0 +1,157 @@ +package org.elasticsearch.xpack.watcher.notification.pagerduty; + +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.watcher.watch.Payload; +import org.elasticsearch.xpack.watcher.common.http.HttpProxy; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.equalTo; + +public class IncidentEventTests extends ESTestCase { + + public void testPagerDutyXContent() throws IOException { + + String serviceKey = randomAlphaOfLength(3); + boolean attachPayload = randomBoolean(); + Payload payload = null; + if (attachPayload) { + payload = new Payload.Simple(Collections.singletonMap(randomAlphaOfLength(3), randomAlphaOfLength(3))); + } + String watchId = randomAlphaOfLength(3); + String description = randomAlphaOfLength(3); + String eventType = randomAlphaOfLength(3); + String incidentKey = rarely() ? null : randomAlphaOfLength(3); + String client = rarely() ? null : randomAlphaOfLength(3); + String clientUrl = rarely() ? null : randomAlphaOfLength(3); + String account = rarely() ? null : randomAlphaOfLength(3); + + IncidentEventContext[] contexts = null; + List links = new ArrayList<>(); + List images = new ArrayList<>(); + + if (randomBoolean()) { + int numContexts = randomIntBetween(0, 3); + contexts = new IncidentEventContext[numContexts]; + for (int i = 0; i < numContexts; i++) { + if (randomBoolean()) { + contexts[i] = IncidentEventContext.link("href", "text"); + links.add(contexts[i]); + } else { + contexts[i] = IncidentEventContext.image("src", "href", "alt"); + images.add(contexts[i]); + } + } + } + + HttpProxy proxy = rarely() ? null : HttpProxy.NO_PROXY; + + IncidentEvent event = new IncidentEvent(description, eventType, incidentKey, client, clientUrl, account, + attachPayload, contexts, proxy); + + XContentBuilder jsonBuilder = jsonBuilder(); + jsonBuilder.startObject(); // since its a snippet + event.buildAPIXContent(jsonBuilder, ToXContent.EMPTY_PARAMS, serviceKey, payload, watchId); + jsonBuilder.endObject(); + XContentParser parser = createParser(jsonBuilder); + parser.nextToken(); + + String actualServiceKey = null; + String actualWatchId = "watcher"; // hardcoded if the SOURCE is null + String actualDescription = null; + String actualEventType = null; + String actualIncidentKey = null; + String actualClient = null; + String actualClientUrl = null; + String actualSeverity = null; + List actualLinks = new ArrayList<>(); + List actualImages = new ArrayList<>(); + Payload actualPayload = 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 (IncidentEvent.Fields.ROUTING_KEY.match(currentFieldName, parser.getDeprecationHandler())) { + actualServiceKey = parser.text(); + } else if (IncidentEvent.Fields.EVENT_ACTION.match(currentFieldName, parser.getDeprecationHandler())) { + actualEventType = parser.text(); + } else if (IncidentEvent.Fields.DEDUP_KEY.match(currentFieldName, parser.getDeprecationHandler())) { + actualIncidentKey = parser.text(); + } else if (IncidentEvent.Fields.CLIENT.match(currentFieldName, parser.getDeprecationHandler())) { + actualClient = parser.text(); + } else if (IncidentEvent.Fields.CLIENT_URL.match(currentFieldName, parser.getDeprecationHandler())) { + actualClientUrl = parser.text(); + } else if (IncidentEvent.Fields.LINKS.match(currentFieldName, parser.getDeprecationHandler())) { + // this is an array + if (token != XContentParser.Token.START_ARRAY) { + fail("Links was not an array"); + } + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + actualLinks.add(IncidentEventContext.parse(parser)); + } + } else if (IncidentEvent.Fields.IMAGES.match(currentFieldName, parser.getDeprecationHandler())) { + // this is an array + if (token != XContentParser.Token.START_ARRAY) { + fail("Images was not an array"); + } + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + actualImages.add(IncidentEventContext.parse(parser)); + } + } else if (IncidentEvent.Fields.PAYLOAD.match(currentFieldName, parser.getDeprecationHandler())) { + // this is a nested object containing a few interesting bits + //actualPayload = new Payload.Simple(parser.map()); + if (token != XContentParser.Token.START_OBJECT) { + fail("payload was not an object"); + } + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (IncidentEvent.Fields.SUMMARY.match(currentFieldName, parser.getDeprecationHandler())) { + actualDescription = parser.text(); + } else if (IncidentEvent.Fields.SOURCE.match(currentFieldName, parser.getDeprecationHandler())) { + actualWatchId = parser.text(); + } else if (IncidentEvent.Fields.SEVERITY.match(currentFieldName, parser.getDeprecationHandler())) { + actualSeverity = parser.text(); + } else if (IncidentEvent.Fields.CUSTOM_DETAILS.match(currentFieldName, parser.getDeprecationHandler())) { + // nested payload object is in here + if (token != XContentParser.Token.START_OBJECT) { + fail("custom_details was not an object"); + } + parser.nextToken(); + Map mapped = parser.map(); + // the first entry is the payload + actualPayload = new Payload.Simple((Map) mapped.get("payload")); + } else { + fail("an unexpected field was encountered inside payload: " + currentFieldName); + } + } + } else { + // this case should not happen + fail("an unexpected field was encountered: " + currentFieldName); + } + } + + // assert the actuals were the same as expected + assertThat(serviceKey, equalTo(actualServiceKey)); + assertThat(eventType, equalTo(actualEventType)); + assertThat(incidentKey, equalTo(actualIncidentKey)); + assertThat(description, equalTo(actualDescription)); + assertThat(watchId, equalTo(actualWatchId)); + assertThat("critical", equalTo(actualSeverity)); + assertThat(client, equalTo(actualClient)); + assertThat(clientUrl, equalTo(actualClientUrl)); + assertThat(links, equalTo(actualLinks)); + assertThat(images, equalTo(actualImages)); + assertThat(payload, equalTo(actualPayload)); + } +} From fa1fc0777cc2c9c4e78bb199006934f18257214d Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Wed, 1 Aug 2018 12:09:01 -0500 Subject: [PATCH 07/10] Precommit header fix --- .../watcher/notification/pagerduty/IncidentEventTests.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java index 62c23a6092688..b1fa3469873e0 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java @@ -1,3 +1,8 @@ +/* + * 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.notification.pagerduty; import org.elasticsearch.common.xcontent.ToXContent; From dc7edb5c674f227799c1634292e55681736a694e Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Wed, 1 Aug 2018 12:10:00 -0500 Subject: [PATCH 08/10] Removing commented code --- .../notification/pagerduty/IncidentEvent.java | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java index d770e690ae380..515dcaa43240b 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java @@ -506,34 +506,5 @@ interface Fields { ParseField LINKS = new ParseField("links"); ParseField IMAGES = new ParseField("images"); ParseField CUSTOM_DETAILS = new ParseField("custom_details"); - - /* - if (incidentKey != null) { - builder.field("dedup_key", incidentKey); - } - - builder.startObject(Fields.PAYLOAD.getPreferredName()); - { - builder.field("summary", description); - - if (attachPayload && payload != null) { - builder.startObject("custom_details"); - { - builder.field(Fields.PAYLOAD.getPreferredName(), payload, params); - } - builder.endObject(); - } - - if (watchId != null) { - builder.field("source", watchId); - } else { - builder.field("source", "watcher"); - } - // TODO externalize this into something user editable - builder.field("severity", "critical"); - } - builder.endObject(); - */ - } } From a0e68bb67bbbfb6876d9cc262bf71305ed16642f Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Wed, 1 Aug 2018 12:12:17 -0500 Subject: [PATCH 09/10] Remove commented code --- .../xpack/watcher/notification/pagerduty/IncidentEventTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java index b1fa3469873e0..8c2f9f9b00ab3 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java @@ -114,7 +114,6 @@ public void testPagerDutyXContent() throws IOException { } } else if (IncidentEvent.Fields.PAYLOAD.match(currentFieldName, parser.getDeprecationHandler())) { // this is a nested object containing a few interesting bits - //actualPayload = new Payload.Simple(parser.map()); if (token != XContentParser.Token.START_OBJECT) { fail("payload was not an object"); } From 6a23d317d590a3025807f5faaa924a14b1f3c49f Mon Sep 17 00:00:00 2001 From: Michael Basnight Date: Thu, 2 Aug 2018 11:34:57 -0500 Subject: [PATCH 10/10] Cleanup test and use streams in one method --- .../notification/pagerduty/IncidentEvent.java | 42 +++---- .../pagerduty/IncidentEventTests.java | 107 ++++++------------ 2 files changed, 49 insertions(+), 100 deletions(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java index 515dcaa43240b..c44fbf36e0b18 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEvent.java @@ -24,17 +24,16 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; /** * Official documentation for this can be found at * - * https://developer.pagerduty.com/documentation/howto/manually-trigger-an-incident/ - * https://developer.pagerduty.com/documentation/integration/events/trigger - * https://developer.pagerduty.com/documentation/integration/events/acknowledge - * https://developer.pagerduty.com/documentation/integration/events/resolve + * https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 */ public class IncidentEvent implements ToXContentObject { @@ -94,7 +93,7 @@ public int hashCode() { return result; } - public HttpRequest createRequest(final String serviceKey, final Payload payload, final String watchId) throws IOException { + HttpRequest createRequest(final String serviceKey, final Payload payload, final String watchId) throws IOException { return HttpRequest.builder(HOST, -1) .method(HttpMethod.POST) .scheme(Scheme.HTTPS) @@ -105,7 +104,7 @@ public HttpRequest createRequest(final String serviceKey, final Payload payload, .build(); } - public XContentBuilder buildAPIXContent(XContentBuilder builder, Params params, String serviceKey, + XContentBuilder buildAPIXContent(XContentBuilder builder, Params params, String serviceKey, Payload payload, String watchId) throws IOException { builder.field(Fields.ROUTING_KEY.getPreferredName(), serviceKey); builder.field(Fields.EVENT_ACTION.getPreferredName(), eventType); @@ -155,32 +154,17 @@ public XContentBuilder buildAPIXContent(XContentBuilder builder, Params params, private void toXContentV2Contexts(XContentBuilder builder, ToXContent.Params params, IncidentEventContext[] contexts) throws IOException { // contexts can be either links or images, and the v2 api needs them separate - List links = new ArrayList<>(); - List images = new ArrayList<>(); - for (IncidentEventContext context: contexts) { - if (context.type == IncidentEventContext.Type.LINK) { - links.add(context); - } else if (context.type == IncidentEventContext.Type.IMAGE) { - images.add(context); - } - } + Map> groups = Arrays.stream(contexts) + .collect(Collectors.groupingBy(iec -> iec.type)); + + List links = groups.getOrDefault(IncidentEventContext.Type.LINK, Collections.emptyList()); if (links.isEmpty() == false) { - builder.startArray(Fields.LINKS.getPreferredName()); - { - for (IncidentEventContext link: links) { - link.toXContent(builder, params); - } - } - builder.endArray(); + builder.array(Fields.LINKS.getPreferredName(), links.toArray()); } + + List images = groups.getOrDefault(IncidentEventContext.Type.IMAGE, Collections.emptyList()); if (images.isEmpty() == false) { - builder.startArray(Fields.IMAGES.getPreferredName()); - { - for (IncidentEventContext image: images) { - image.toXContent(builder, params); - } - } - builder.endArray(); + builder.array(Fields.IMAGES.getPreferredName(), images.toArray()); } } diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java index 8c2f9f9b00ab3..3638d5f85d929 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/pagerduty/IncidentEventTests.java @@ -5,10 +5,12 @@ */ package org.elasticsearch.xpack.watcher.notification.pagerduty; +import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.rest.yaml.ObjectPath; import org.elasticsearch.xpack.core.watcher.watch.Payload; import org.elasticsearch.xpack.watcher.common.http.HttpProxy; @@ -69,79 +71,42 @@ public void testPagerDutyXContent() throws IOException { XContentParser parser = createParser(jsonBuilder); parser.nextToken(); - String actualServiceKey = null; - String actualWatchId = "watcher"; // hardcoded if the SOURCE is null - String actualDescription = null; - String actualEventType = null; - String actualIncidentKey = null; - String actualClient = null; - String actualClientUrl = null; - String actualSeverity = null; - List actualLinks = new ArrayList<>(); - List actualImages = new ArrayList<>(); + ObjectPath objectPath = ObjectPath.createFromXContent(jsonBuilder.contentType().xContent(), BytesReference.bytes(jsonBuilder)); + + String actualServiceKey = objectPath.evaluate(IncidentEvent.Fields.ROUTING_KEY.getPreferredName()); + String actualWatchId = objectPath.evaluate(IncidentEvent.Fields.PAYLOAD.getPreferredName() + + "." + IncidentEvent.Fields.SOURCE.getPreferredName()); + if (actualWatchId == null) { + actualWatchId = "watcher"; // hardcoded if the SOURCE is null + } + String actualDescription = objectPath.evaluate(IncidentEvent.Fields.PAYLOAD.getPreferredName() + + "." + IncidentEvent.Fields.SUMMARY.getPreferredName()); + String actualEventType = objectPath.evaluate(IncidentEvent.Fields.EVENT_ACTION.getPreferredName()); + String actualIncidentKey = objectPath.evaluate(IncidentEvent.Fields.DEDUP_KEY.getPreferredName()); + String actualClient = objectPath.evaluate(IncidentEvent.Fields.CLIENT.getPreferredName()); + String actualClientUrl = objectPath.evaluate(IncidentEvent.Fields.CLIENT_URL.getPreferredName()); + String actualSeverity = objectPath.evaluate(IncidentEvent.Fields.PAYLOAD.getPreferredName() + + "." + IncidentEvent.Fields.SEVERITY.getPreferredName()); + Map payloadDetails = objectPath.evaluate("payload.custom_details.payload"); Payload actualPayload = 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 (IncidentEvent.Fields.ROUTING_KEY.match(currentFieldName, parser.getDeprecationHandler())) { - actualServiceKey = parser.text(); - } else if (IncidentEvent.Fields.EVENT_ACTION.match(currentFieldName, parser.getDeprecationHandler())) { - actualEventType = parser.text(); - } else if (IncidentEvent.Fields.DEDUP_KEY.match(currentFieldName, parser.getDeprecationHandler())) { - actualIncidentKey = parser.text(); - } else if (IncidentEvent.Fields.CLIENT.match(currentFieldName, parser.getDeprecationHandler())) { - actualClient = parser.text(); - } else if (IncidentEvent.Fields.CLIENT_URL.match(currentFieldName, parser.getDeprecationHandler())) { - actualClientUrl = parser.text(); - } else if (IncidentEvent.Fields.LINKS.match(currentFieldName, parser.getDeprecationHandler())) { - // this is an array - if (token != XContentParser.Token.START_ARRAY) { - fail("Links was not an array"); - } - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - actualLinks.add(IncidentEventContext.parse(parser)); - } - } else if (IncidentEvent.Fields.IMAGES.match(currentFieldName, parser.getDeprecationHandler())) { - // this is an array - if (token != XContentParser.Token.START_ARRAY) { - fail("Images was not an array"); - } - while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - actualImages.add(IncidentEventContext.parse(parser)); - } - } else if (IncidentEvent.Fields.PAYLOAD.match(currentFieldName, parser.getDeprecationHandler())) { - // this is a nested object containing a few interesting bits - if (token != XContentParser.Token.START_OBJECT) { - fail("payload was not an object"); - } - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (IncidentEvent.Fields.SUMMARY.match(currentFieldName, parser.getDeprecationHandler())) { - actualDescription = parser.text(); - } else if (IncidentEvent.Fields.SOURCE.match(currentFieldName, parser.getDeprecationHandler())) { - actualWatchId = parser.text(); - } else if (IncidentEvent.Fields.SEVERITY.match(currentFieldName, parser.getDeprecationHandler())) { - actualSeverity = parser.text(); - } else if (IncidentEvent.Fields.CUSTOM_DETAILS.match(currentFieldName, parser.getDeprecationHandler())) { - // nested payload object is in here - if (token != XContentParser.Token.START_OBJECT) { - fail("custom_details was not an object"); - } - parser.nextToken(); - Map mapped = parser.map(); - // the first entry is the payload - actualPayload = new Payload.Simple((Map) mapped.get("payload")); - } else { - fail("an unexpected field was encountered inside payload: " + currentFieldName); - } - } - } else { - // this case should not happen - fail("an unexpected field was encountered: " + currentFieldName); + if (payloadDetails != null) { + actualPayload = new Payload.Simple(payloadDetails); + } + + List actualLinks = new ArrayList<>(); + List> linkMap = (List>) objectPath.evaluate(IncidentEvent.Fields.LINKS.getPreferredName()); + if (linkMap != null) { + for (Map iecValue : linkMap) { + actualLinks.add(IncidentEventContext.link(iecValue.get("href"), iecValue.get("text"))); + } + } + + List actualImages = new ArrayList<>(); + List> imgMap = (List>) objectPath.evaluate(IncidentEvent.Fields.IMAGES.getPreferredName()); + if (imgMap != null) { + for (Map iecValue : imgMap) { + actualImages.add(IncidentEventContext.image(iecValue.get("src"), iecValue.get("href"), iecValue.get("alt"))); } }