Browse Source

Merge pull request ESQL-1018 from elastic/main

🤖 ESQL: Merge upstream
elasticsearchmachine 2 years ago
parent
commit
c1df76bc36

+ 5 - 0
docs/changelog/95196.yaml

@@ -0,0 +1,5 @@
+pr: 95196
+summary: Add logging for debug to Watcher webhook service
+area: Watcher
+type: bug
+issues: []

+ 2 - 2
x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/Watcher.java

@@ -354,11 +354,11 @@ public class Watcher extends Plugin implements SystemIndexPlugin, ScriptPlugin,
 
         TextTemplateEngine templateEngine = new TextTemplateEngine(scriptService);
         Map<String, EmailAttachmentParser<?>> emailAttachmentParsers = new HashMap<>();
-        emailAttachmentParsers.put(HttpEmailAttachementParser.TYPE, new HttpEmailAttachementParser(httpClient, templateEngine));
+        emailAttachmentParsers.put(HttpEmailAttachementParser.TYPE, new HttpEmailAttachementParser(webhookService, templateEngine));
         emailAttachmentParsers.put(DataAttachmentParser.TYPE, new DataAttachmentParser());
         emailAttachmentParsers.put(
             ReportingAttachmentParser.TYPE,
-            new ReportingAttachmentParser(settings, httpClient, templateEngine, clusterService.getClusterSettings())
+            new ReportingAttachmentParser(settings, webhookService, templateEngine, clusterService.getClusterSettings())
         );
         EmailAttachmentsParser emailAttachmentsParser = new EmailAttachmentsParser(emailAttachmentParsers);
 

+ 61 - 21
x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/WebhookService.java

@@ -15,6 +15,7 @@ import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.SettingsException;
 import org.elasticsearch.common.util.LazyInitializable;
+import org.elasticsearch.core.Tuple;
 import org.elasticsearch.logging.LogManager;
 import org.elasticsearch.logging.Logger;
 import org.elasticsearch.xpack.core.watcher.actions.Action;
@@ -121,34 +122,28 @@ public class WebhookService extends NotificationService<WebhookService.WebhookAc
         Map<String, Object> model = Variables.createCtxParamsMap(ctx, payload);
 
         // Render the original request
-        HttpRequest request = action.getRequest().render(templateEngine, model);
+        HttpRequest originalRequest = action.getRequest().render(templateEngine, model);
 
-        // If applicable, add the extra token to the headers
-        boolean tokenAdded = false;
-        WebhookAccount account = getAccount(NAME);
-        if (this.additionalTokenEnabled && account.hostTokenMap.size() > 0) {
-            // Generate a string like example.com:9200 to match against the list of hosts where the
-            // additional token should be provided. The token will only be added to the headers if
-            // the request matches the list.
-            String reqHostAndPort = request.host() + ":" + request.port();
-            if (Strings.hasText(account.hostTokenMap.get(reqHostAndPort))) {
-                // Add the additional token
-                tokenAdded = true;
-                request = request.copy().setHeader(TOKEN_HEADER_NAME, account.hostTokenMap.get(reqHostAndPort)).build();
-            }
+        if (ctx.simulateAction(actionId)) {
+            HttpRequest request = maybeModifyHttpRequest(originalRequest);
+            // If the request was modified, then the request has had the token added
+            boolean tokenAdded = originalRequest != request;
+            // Skip execution, return only the simulated (and redacted if necessary) response
+            return new WebhookAction.Result.Simulated(
+                tokenAdded ? request.copy().setHeader(TOKEN_HEADER_NAME, WatcherXContentParser.REDACTED_PASSWORD).build() : request
+            );
         }
 
+        final Tuple<HttpRequest, HttpResponse> respTup = modifyAndExecuteHttpRequest(originalRequest);
+        final HttpRequest request = respTup.v1();
+        final HttpResponse response = respTup.v2();
+        // If the request was modified, then the request has had the token added
+        final boolean tokenAdded = originalRequest != request;
+
         final Function<HttpRequest, HttpRequest> redactToken = tokenAdded
             ? req -> req.copy().setHeader(TOKEN_HEADER_NAME, WatcherXContentParser.REDACTED_PASSWORD).build()
             : Function.identity();
 
-        if (ctx.simulateAction(actionId)) {
-            // Skip execution, return only the simulated (and redacted if necessary) response
-            return new WebhookAction.Result.Simulated(redactToken.apply(request));
-        }
-
-        HttpResponse response = httpClient.execute(request);
-
         if (response.status() >= 400) {
             return new WebhookAction.Result.Failure(redactToken.apply(request), response);
         } else {
@@ -156,6 +151,51 @@ public class WebhookService extends NotificationService<WebhookService.WebhookAc
         }
     }
 
+    /**
+     * Makes any additional modifications to the {@link HttpRequest} if necessary.
+     * If no modifications are made the same instance is returned, otherwise a new
+     * HttpRequest is returned.
+     */
+    HttpRequest maybeModifyHttpRequest(HttpRequest request) {
+        WebhookAccount account = getAccount(NAME);
+        if (this.additionalTokenEnabled && account.hostTokenMap.size() > 0) {
+            // Generate a string like example.com:9200 to match against the list of hosts where the
+            // additional token should be provided. The token will only be added to the headers if
+            // the request matches the list.
+            String reqHostAndPort = request.host() + ":" + request.port();
+            if (Strings.hasText(account.hostTokenMap.get(reqHostAndPort))) {
+                // Add the additional token
+                logger.debug(
+                    "additional [{}] header token added to watcher webhook request for {}://{}:{}",
+                    TOKEN_HEADER_NAME,
+                    request.scheme().scheme(),
+                    request.host(),
+                    request.port()
+                );
+                return request.copy().setHeader(TOKEN_HEADER_NAME, account.hostTokenMap.get(reqHostAndPort)).build();
+            }
+        }
+        return request;
+    }
+
+    /**
+     * Executes the given {@link HttpRequest} after any necessary modifications.
+     * A tuple of the modified (or unmodified) {@link HttpRequest} and
+     * {@link HttpResponse} is returned.
+     */
+    public Tuple<HttpRequest, HttpResponse> modifyAndExecuteHttpRequest(HttpRequest request) throws IOException {
+        final HttpRequest modifiedRequest = maybeModifyHttpRequest(request);
+        final HttpResponse response = httpClient.execute(modifiedRequest);
+        logger.debug(
+            "executed watcher webhook request for {}://{}:{}, response code: {}",
+            modifiedRequest.scheme().scheme(),
+            modifiedRequest.host(),
+            modifiedRequest.port(),
+            response.status()
+        );
+        return Tuple.tuple(modifiedRequest, response);
+    }
+
     public static final class WebhookAccount {
         private final Map<String, String> hostTokenMap;
 

+ 5 - 5
x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/email/attachment/HttpEmailAttachementParser.java

@@ -14,11 +14,11 @@ import org.elasticsearch.xcontent.ParseField;
 import org.elasticsearch.xcontent.XContentParser;
 import org.elasticsearch.xpack.core.watcher.execution.WatchExecutionContext;
 import org.elasticsearch.xpack.core.watcher.watch.Payload;
-import org.elasticsearch.xpack.watcher.common.http.HttpClient;
 import org.elasticsearch.xpack.watcher.common.http.HttpRequest;
 import org.elasticsearch.xpack.watcher.common.http.HttpRequestTemplate;
 import org.elasticsearch.xpack.watcher.common.http.HttpResponse;
 import org.elasticsearch.xpack.watcher.common.text.TextTemplateEngine;
+import org.elasticsearch.xpack.watcher.notification.WebhookService;
 import org.elasticsearch.xpack.watcher.notification.email.Attachment;
 import org.elasticsearch.xpack.watcher.support.Variables;
 
@@ -34,11 +34,11 @@ public class HttpEmailAttachementParser implements EmailAttachmentParser<HttpReq
     }
 
     public static final String TYPE = "http";
-    private final HttpClient httpClient;
+    private final WebhookService webhookService;
     private final TextTemplateEngine templateEngine;
 
-    public HttpEmailAttachementParser(HttpClient httpClient, TextTemplateEngine templateEngine) {
-        this.httpClient = httpClient;
+    public HttpEmailAttachementParser(WebhookService webhookService, TextTemplateEngine templateEngine) {
+        this.webhookService = webhookService;
         this.templateEngine = templateEngine;
     }
 
@@ -82,7 +82,7 @@ public class HttpEmailAttachementParser implements EmailAttachmentParser<HttpReq
         Map<String, Object> model = Variables.createCtxParamsMap(context, payload);
         HttpRequest httpRequest = attachment.getRequestTemplate().render(templateEngine, model);
 
-        HttpResponse response = httpClient.execute(httpRequest);
+        HttpResponse response = webhookService.modifyAndExecuteHttpRequest(httpRequest).v2();
         // check for status 200, only then append attachment
         if (response.status() >= 200 && response.status() < 300) {
             if (response.hasContent()) {

+ 6 - 6
x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/notification/email/attachment/ReportingAttachmentParser.java

@@ -26,7 +26,6 @@ import org.elasticsearch.xcontent.json.JsonXContent;
 import org.elasticsearch.xpack.core.watcher.execution.WatchExecutionContext;
 import org.elasticsearch.xpack.core.watcher.watch.Payload;
 import org.elasticsearch.xpack.watcher.common.http.BasicAuth;
-import org.elasticsearch.xpack.watcher.common.http.HttpClient;
 import org.elasticsearch.xpack.watcher.common.http.HttpMethod;
 import org.elasticsearch.xpack.watcher.common.http.HttpProxy;
 import org.elasticsearch.xpack.watcher.common.http.HttpRequest;
@@ -34,6 +33,7 @@ import org.elasticsearch.xpack.watcher.common.http.HttpRequestTemplate;
 import org.elasticsearch.xpack.watcher.common.http.HttpResponse;
 import org.elasticsearch.xpack.watcher.common.text.TextTemplate;
 import org.elasticsearch.xpack.watcher.common.text.TextTemplateEngine;
+import org.elasticsearch.xpack.watcher.notification.WebhookService;
 import org.elasticsearch.xpack.watcher.notification.email.Attachment;
 import org.elasticsearch.xpack.watcher.support.Variables;
 
@@ -120,20 +120,20 @@ public class ReportingAttachmentParser implements EmailAttachmentParser<Reportin
     private final Logger logger;
     private final TimeValue interval;
     private final int retries;
-    private HttpClient httpClient;
+    private final WebhookService webhookService;
     private final TextTemplateEngine templateEngine;
     private boolean warningEnabled = REPORT_WARNING_ENABLED_SETTING.getDefault(Settings.EMPTY);
     private final Map<String, String> customWarnings = new ConcurrentHashMap<>(1);
 
     public ReportingAttachmentParser(
         Settings settings,
-        HttpClient httpClient,
+        WebhookService webhookService,
         TextTemplateEngine templateEngine,
         ClusterSettings clusterSettings
     ) {
         this.interval = INTERVAL_SETTING.get(settings);
         this.retries = RETRIES_SETTING.get(settings);
-        this.httpClient = httpClient;
+        this.webhookService = webhookService;
         this.templateEngine = templateEngine;
         this.logger = LogManager.getLogger(getClass());
         clusterSettings.addSettingsUpdateConsumer(REPORT_WARNING_ENABLED_SETTING, this::setWarningEnabled);
@@ -210,7 +210,7 @@ public class ReportingAttachmentParser implements EmailAttachmentParser<Reportin
             // IMPORTANT NOTE: This is only a temporary solution until we made the execution of watcher more async
             // This still blocks other executions on the thread and we have to get away from that
             sleep(sleepMillis, context, attachment);
-            HttpResponse response = httpClient.execute(pollingRequest);
+            HttpResponse response = webhookService.modifyAndExecuteHttpRequest(pollingRequest).v2();
 
             if (response.status() == 503) {
                 // requires us to interval another run, no action to take, except logging
@@ -322,7 +322,7 @@ public class ReportingAttachmentParser implements EmailAttachmentParser<Reportin
      * Trigger the initial report generation and catch possible exceptions
      */
     private HttpResponse requestReportGeneration(String watchId, String attachmentId, HttpRequest request) throws IOException {
-        HttpResponse response = httpClient.execute(request);
+        HttpResponse response = webhookService.modifyAndExecuteHttpRequest(request).v2();
         if (response.status() != 200) {
             throw new ElasticsearchException(
                 "Watch[{}] reporting[{}] Error response when trying to trigger reporting generation "

+ 20 - 2
x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/actions/email/EmailActionTests.java

@@ -9,6 +9,7 @@ package org.elasticsearch.xpack.watcher.actions.email;
 import io.netty.handler.codec.http.HttpHeaders;
 
 import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.collect.MapBuilder;
@@ -35,6 +36,7 @@ import org.elasticsearch.xpack.watcher.common.http.HttpRequestTemplate;
 import org.elasticsearch.xpack.watcher.common.http.HttpResponse;
 import org.elasticsearch.xpack.watcher.common.text.TextTemplate;
 import org.elasticsearch.xpack.watcher.common.text.TextTemplateEngine;
+import org.elasticsearch.xpack.watcher.notification.WebhookService;
 import org.elasticsearch.xpack.watcher.notification.email.Attachment;
 import org.elasticsearch.xpack.watcher.notification.email.Authentication;
 import org.elasticsearch.xpack.watcher.notification.email.Email;
@@ -62,6 +64,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import static java.util.Collections.emptyMap;
 import static java.util.Collections.singletonMap;
@@ -92,7 +95,10 @@ public class EmailActionTests extends ESTestCase {
         Map<String, EmailAttachmentParser<? extends EmailAttachmentParser.EmailAttachment>> emailAttachmentParsers = new HashMap<>();
         emailAttachmentParsers.put(
             HttpEmailAttachementParser.TYPE,
-            new HttpEmailAttachementParser(httpClient, new MockTextTemplateEngine())
+            new HttpEmailAttachementParser(
+                new WebhookService(Settings.EMPTY, httpClient, mockClusterService().getClusterSettings()),
+                new MockTextTemplateEngine()
+            )
         );
         emailAttachmentParsers.put(DataAttachmentParser.TYPE, new DataAttachmentParser());
         emailAttachmentParser = new EmailAttachmentsParser(emailAttachmentParsers);
@@ -547,7 +553,13 @@ public class EmailActionTests extends ESTestCase {
 
         // setup email attachment parsers
         Map<String, EmailAttachmentParser<? extends EmailAttachmentParser.EmailAttachment>> attachmentParsers = new HashMap<>();
-        attachmentParsers.put(HttpEmailAttachementParser.TYPE, new HttpEmailAttachementParser(httpClient, engine));
+        attachmentParsers.put(
+            HttpEmailAttachementParser.TYPE,
+            new HttpEmailAttachementParser(
+                new WebhookService(Settings.EMPTY, httpClient, mockClusterService().getClusterSettings()),
+                engine
+            )
+        );
         EmailAttachmentsParser emailAttachmentsParser = new EmailAttachmentsParser(attachmentParsers);
 
         XContentBuilder builder = jsonBuilder().startObject()
@@ -666,4 +678,10 @@ public class EmailActionTests extends ESTestCase {
         }
     }
 
+    private ClusterService mockClusterService() {
+        ClusterService clusterService = mock(ClusterService.class);
+        ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, Set.of());
+        when(clusterService.getClusterSettings()).thenReturn(clusterSettings);
+        return clusterService;
+    }
 }

+ 118 - 0
x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/WebhookServiceTests.java

@@ -0,0 +1,118 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.watcher.notification;
+
+import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.MockSecureSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.watcher.common.http.HttpClient;
+import org.elasticsearch.xpack.watcher.common.http.HttpMethod;
+import org.elasticsearch.xpack.watcher.common.http.HttpRequest;
+import org.elasticsearch.xpack.watcher.common.http.HttpResponse;
+import org.elasticsearch.xpack.watcher.common.http.Scheme;
+
+import java.io.IOException;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class WebhookServiceTests extends ESTestCase {
+    public void testModifyRequest() throws IOException {
+        Settings.Builder builder = Settings.builder();
+        builder.put(WebhookService.SETTING_WEBHOOK_TOKEN_ENABLED.getKey(), true);
+        MockSecureSettings secureSettings = new MockSecureSettings();
+        secureSettings.setString(
+            WebhookService.SETTING_WEBHOOK_HOST_TOKEN_PAIRS.getKey(),
+            "host1.com:1234=token1234,host2.org:2345=token2345"
+        );
+        builder.setSecureSettings(secureSettings);
+        Settings finalSettings = builder.build();
+
+        HttpClient mockClient = mock(HttpClient.class);
+        WebhookService service = new WebhookService(
+            finalSettings,
+            mockClient,
+            new ClusterSettings(
+                finalSettings,
+                Set.of(WebhookService.SETTING_WEBHOOK_TOKEN_ENABLED, WebhookService.SETTING_WEBHOOK_HOST_TOKEN_PAIRS)
+            )
+        );
+
+        HttpRequest nonTokenReq = new HttpRequest(
+            "example.com",
+            80,
+            Scheme.HTTP,
+            HttpMethod.GET,
+            "/",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+        );
+        HttpRequest host1Req = new HttpRequest(
+            "host1.com",
+            1234,
+            Scheme.HTTP,
+            HttpMethod.GET,
+            "/",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+        );
+        HttpRequest host1WrongPortReq = new HttpRequest(
+            "host1.com",
+            3456,
+            Scheme.HTTP,
+            HttpMethod.GET,
+            "/",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+        );
+        HttpRequest host2Req = new HttpRequest(
+            "host2.org",
+            2345,
+            Scheme.HTTP,
+            HttpMethod.GET,
+            "/",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null
+        );
+
+        assertSame(service.maybeModifyHttpRequest(nonTokenReq), nonTokenReq);
+        assertSame(service.maybeModifyHttpRequest(host1WrongPortReq), host1WrongPortReq);
+        assertThat(service.maybeModifyHttpRequest(host2Req).headers().get(WebhookService.TOKEN_HEADER_NAME), equalTo("token2345"));
+
+        HttpRequest host1ModReq = service.maybeModifyHttpRequest(host1Req);
+        assertThat(host1ModReq.headers().get(WebhookService.TOKEN_HEADER_NAME), equalTo("token1234"));
+        when(mockClient.execute(any(HttpRequest.class))).thenReturn(new HttpResponse(200, "{}"));
+        service.modifyAndExecuteHttpRequest(host1Req);
+        verify(mockClient).execute(host1ModReq);
+    }
+}

+ 25 - 1
x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/email/attachment/HttpEmailAttachementParserTests.java

@@ -7,8 +7,11 @@
 package org.elasticsearch.xpack.watcher.notification.email.attachment;
 
 import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.collect.MapBuilder;
+import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.xcontent.ToXContent;
 import org.elasticsearch.xcontent.XContentBuilder;
@@ -20,6 +23,7 @@ import org.elasticsearch.xpack.watcher.common.http.HttpClient;
 import org.elasticsearch.xpack.watcher.common.http.HttpRequest;
 import org.elasticsearch.xpack.watcher.common.http.HttpRequestTemplate;
 import org.elasticsearch.xpack.watcher.common.http.HttpResponse;
+import org.elasticsearch.xpack.watcher.notification.WebhookService;
 import org.elasticsearch.xpack.watcher.notification.email.attachment.EmailAttachmentParser.EmailAttachment;
 import org.elasticsearch.xpack.watcher.test.MockTextTemplateEngine;
 import org.junit.Before;
@@ -31,9 +35,14 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
+import static org.elasticsearch.xpack.watcher.notification.email.attachment.ReportingAttachmentParser.INTERVAL_SETTING;
+import static org.elasticsearch.xpack.watcher.notification.email.attachment.ReportingAttachmentParser.REPORT_WARNING_ENABLED_SETTING;
+import static org.elasticsearch.xpack.watcher.notification.email.attachment.ReportingAttachmentParser.REPORT_WARNING_TEXT;
+import static org.elasticsearch.xpack.watcher.notification.email.attachment.ReportingAttachmentParser.RETRIES_SETTING;
 import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.mockExecutionContextBuilder;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.core.Is.is;
@@ -52,7 +61,13 @@ public class HttpEmailAttachementParserTests extends ESTestCase {
         httpClient = mock(HttpClient.class);
 
         attachmentParsers = new HashMap<>();
-        attachmentParsers.put(HttpEmailAttachementParser.TYPE, new HttpEmailAttachementParser(httpClient, new MockTextTemplateEngine()));
+        attachmentParsers.put(
+            HttpEmailAttachementParser.TYPE,
+            new HttpEmailAttachementParser(
+                new WebhookService(Settings.EMPTY, httpClient, mockClusterService().getClusterSettings()),
+                new MockTextTemplateEngine()
+            )
+        );
         emailAttachmentsParser = new EmailAttachmentsParser(attachmentParsers);
     }
 
@@ -172,4 +187,13 @@ public class HttpEmailAttachementParserTests extends ESTestCase {
             .buildMock();
     }
 
+    private ClusterService mockClusterService() {
+        ClusterService clusterService = mock(ClusterService.class);
+        ClusterSettings clusterSettings = new ClusterSettings(
+            Settings.EMPTY,
+            Set.of(INTERVAL_SETTING, RETRIES_SETTING, REPORT_WARNING_ENABLED_SETTING, REPORT_WARNING_TEXT)
+        );
+        when(clusterService.getClusterSettings()).thenReturn(clusterSettings);
+        return clusterService;
+    }
 }

+ 16 - 4
x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/notification/email/attachment/ReportingAttachmentParserTests.java

@@ -29,6 +29,7 @@ import org.elasticsearch.xpack.watcher.common.http.HttpRequest;
 import org.elasticsearch.xpack.watcher.common.http.HttpResponse;
 import org.elasticsearch.xpack.watcher.common.text.TextTemplate;
 import org.elasticsearch.xpack.watcher.common.text.TextTemplateEngine;
+import org.elasticsearch.xpack.watcher.notification.WebhookService;
 import org.elasticsearch.xpack.watcher.notification.email.Attachment;
 import org.elasticsearch.xpack.watcher.notification.email.attachment.EmailAttachmentParser.EmailAttachment;
 import org.elasticsearch.xpack.watcher.test.MockTextTemplateEngine;
@@ -83,7 +84,8 @@ public class ReportingAttachmentParserTests extends ESTestCase {
     public void init() throws Exception {
         httpClient = mock(HttpClient.class);
         clusterSettings = mockClusterService().getClusterSettings();
-        reportingAttachmentParser = new ReportingAttachmentParser(Settings.EMPTY, httpClient, templateEngine, clusterSettings);
+        WebhookService webhookService = new WebhookService(Settings.EMPTY, httpClient, clusterSettings);
+        reportingAttachmentParser = new ReportingAttachmentParser(Settings.EMPTY, webhookService, templateEngine, clusterSettings);
         attachmentParsers.put(ReportingAttachmentParser.TYPE, reportingAttachmentParser);
         emailAttachmentsParser = new EmailAttachmentsParser(attachmentParsers);
     }
@@ -413,7 +415,12 @@ public class ReportingAttachmentParserTests extends ESTestCase {
 
         Settings settings = Settings.builder().put(INTERVAL_SETTING.getKey(), "1ms").put(RETRIES_SETTING.getKey(), retries).build();
 
-        reportingAttachmentParser = new ReportingAttachmentParser(settings, httpClient, templateEngine, clusterSettings);
+        reportingAttachmentParser = new ReportingAttachmentParser(
+            settings,
+            new WebhookService(settings, httpClient, clusterSettings),
+            templateEngine,
+            clusterSettings
+        );
         expectThrows(
             ElasticsearchException.class,
             () -> reportingAttachmentParser.toAttachment(createWatchExecutionContext(), Payload.EMPTY, attachment)
@@ -445,7 +452,7 @@ public class ReportingAttachmentParserTests extends ESTestCase {
         );
         reportingAttachmentParser = new ReportingAttachmentParser(
             Settings.EMPTY,
-            httpClient,
+            new WebhookService(Settings.EMPTY, httpClient, clusterSettings),
             replaceHttpWithHttpsTemplateEngine,
             clusterSettings
         );
@@ -468,7 +475,12 @@ public class ReportingAttachmentParserTests extends ESTestCase {
         Settings invalidSettings = Settings.builder().put("xpack.notification.reporting.retries", -10).build();
         e = expectThrows(
             IllegalArgumentException.class,
-            () -> new ReportingAttachmentParser(invalidSettings, httpClient, templateEngine, clusterSettings)
+            () -> new ReportingAttachmentParser(
+                invalidSettings,
+                new WebhookService(invalidSettings, httpClient, clusterSettings),
+                templateEngine,
+                clusterSettings
+            )
         );
         assertThat(e.getMessage(), is("Failed to parse value [-10] for setting [xpack.notification.reporting.retries] must be >= 0"));
     }