Repository: nifi Updated Branches: refs/heads/0.x f8ae10f75 -> 4eb59fc8e
NIFI-1578: Create PutSlack processor Signed-off-by: Matt Burgess <mattyb...@apache.org> This closes #256 Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/4eb59fc8 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/4eb59fc8 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/4eb59fc8 Branch: refs/heads/0.x Commit: 4eb59fc8eb56b555a1620fef9348d566aa82331f Parents: f8ae10f Author: Adam Lamar <adamond...@gmail.com> Authored: Thu Mar 10 10:17:09 2016 -0700 Committer: Matt Burgess <mattyb...@apache.org> Committed: Fri Jun 17 15:52:11 2016 -0400 ---------------------------------------------------------------------- nifi-assembly/pom.xml | 5 + .../nifi-slack-bundle/nifi-slack-nar/pom.xml | 41 ++++ .../src/main/resources/META-INF/NOTICE | 18 ++ .../nifi-slack-processors/pom.xml | 87 +++++++ .../apache/nifi/processors/slack/PutSlack.java | 245 +++++++++++++++++++ .../org.apache.nifi.processor.Processor | 15 ++ .../additionalDetails.html | 48 ++++ .../nifi/processors/slack/CaptureServlet.java | 69 ++++++ .../nifi/processors/slack/PutSlackTest.java | 163 ++++++++++++ .../nifi/processors/slack/TestServer.java | 164 +++++++++++++ nifi-nar-bundles/nifi-slack-bundle/pom.xml | 35 +++ .../nifi/processors/slack/TestServer.java | 163 ++++++++++++ nifi-nar-bundles/pom.xml | 1 + pom.xml | 6 + 14 files changed, 1060 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-assembly/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml index 17ec567..9d68ba2 100644 --- a/nifi-assembly/pom.xml +++ b/nifi-assembly/pom.xml @@ -337,6 +337,11 @@ language governing permissions and limitations under the License. --> <artifactId>nifi-mqtt-nar</artifactId> <type>nar</type> </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-slack-nar</artifactId> + <type>nar</type> + </dependency> </dependencies> <properties> http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-nar/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-nar/pom.xml b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-nar/pom.xml new file mode 100644 index 0000000..07c4510 --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-nar/pom.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-slack-bundle</artifactId> + <version>0.7.0-SNAPSHOT</version> + </parent> + + <artifactId>nifi-slack-nar</artifactId> + <version>0.7.0-SNAPSHOT</version> + <packaging>nar</packaging> + <properties> + <maven.javadoc.skip>true</maven.javadoc.skip> + <source.skip>true</source.skip> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-slack-processors</artifactId> + <version>0.7.0-SNAPSHOT</version> + </dependency> + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-nar/src/main/resources/META-INF/NOTICE ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-nar/src/main/resources/META-INF/NOTICE b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-nar/src/main/resources/META-INF/NOTICE new file mode 100644 index 0000000..759452a --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-nar/src/main/resources/META-INF/NOTICE @@ -0,0 +1,18 @@ +nifi-slack-nar +Copyright 2016 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +************************ +Common Development and Distribution License 1.1 +************************ + +The following binary components are provided under the Common Development and Distribution License 1.1. See project link for details. + + (CDDL 1.1) (GPL2 w/ CPE) JSON Processing API (javax.json:javax.json-api:jar:1.0 - http://json-processing-spec.java.net) + (CDDL 1.1) (GPL2 w/ CPE) JSON Processing Default Provider (org.glassfish:javax.json:jar:1.0.4 - https://jsonp.java.net) + +The following binary components are provided under the Common Development and Distribution License v1.0. See project link for details. + + (CDDL 1.0) JSR311 API (javax.ws.rs:jsr311-api:jar:1.1.1 - https://jsr311.dev.java.net) http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/pom.xml b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/pom.xml new file mode 100644 index 0000000..45f28bc --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/pom.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-slack-bundle</artifactId> + <version>0.7.0-SNAPSHOT</version> + </parent> + + <artifactId>nifi-slack-processors</artifactId> + <packaging>jar</packaging> + + <dependencies> + <dependency> + <groupId>javax.json</groupId> + <artifactId>javax.json-api</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>org.glassfish</groupId> + <artifactId>javax.json</artifactId> + <version>1.0.4</version> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-processor-utils</artifactId> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-mock</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.11</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + <version>9.2.11.v20150529</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlet</artifactId> + <version>9.2.11.v20150529</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>javax.ws.rs-api</artifactId> + <version>2.0.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-ssl-context-service</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/java/org/apache/nifi/processors/slack/PutSlack.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/java/org/apache/nifi/processors/slack/PutSlack.java b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/java/org/apache/nifi/processors/slack/PutSlack.java new file mode 100644 index 0000000..e5e2c03 --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/java/org/apache/nifi/processors/slack/PutSlack.java @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.slack; + +import org.apache.nifi.annotation.behavior.InputRequirement; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.ValidationContext; +import org.apache.nifi.components.ValidationResult; +import org.apache.nifi.components.Validator; +import org.apache.nifi.flowfile.FlowFile; +import org.apache.nifi.processor.AbstractProcessor; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.stream.io.DataOutputStream; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Tags({"put", "slack", "notify"}) +@CapabilityDescription("Sends a message to your team on slack.com") +@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED) +public class PutSlack extends AbstractProcessor { + + public static final PropertyDescriptor WEBHOOK_URL = new PropertyDescriptor + .Builder() + .name("webhook-url") + .displayName("Webhook URL") + .description("The POST URL provided by Slack to send messages into a channel.") + .required(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .addValidator(StandardValidators.URL_VALIDATOR) + .sensitive(true) + .build(); + + public static final PropertyDescriptor WEBHOOK_TEXT = new PropertyDescriptor + .Builder() + .name("webhook-text") + .displayName("Webhook Text") + .description("The text sent in the webhook message") + .required(true) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor CHANNEL = new PropertyDescriptor + .Builder() + .name("channel") + .displayName("Channel") + .description("A public channel using #channel or direct message using @username. If not specified, " + + "the default webhook channel as specified in Slack's Incoming Webhooks web interface is used.") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor USERNAME = new PropertyDescriptor + .Builder() + .name("username") + .displayName("Username") + .description("The displayed Slack username") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .build(); + + public static final PropertyDescriptor ICON_URL = new PropertyDescriptor + .Builder() + .name("icon-url") + .displayName("Icon URL") + .description("Icon URL to be used for the message") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.URL_VALIDATOR) + .build(); + + public static final PropertyDescriptor ICON_EMOJI = new PropertyDescriptor + .Builder() + .name("icon-emoji") + .displayName("Icon Emoji") + .description("Icon Emoji to be used for the message. Must begin and end with a colon, e.g. :ghost:") + .required(false) + .expressionLanguageSupported(true) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .addValidator(new EmojiValidator()) + .build(); + + public static final Relationship REL_SUCCESS = new Relationship.Builder() + .name("success") + .description("FlowFiles are routed to success after being successfully sent to Slack") + .build(); + + public static final Relationship REL_FAILURE = new Relationship.Builder() + .name("failure") + .description("FlowFiles are routed to failure if unable to be sent to Slack") + .build(); + + public static final List<PropertyDescriptor> descriptors = Collections.unmodifiableList( + Arrays.asList(WEBHOOK_URL, WEBHOOK_TEXT, CHANNEL, USERNAME, ICON_URL, ICON_EMOJI)); + + public static final Set<Relationship> relationships = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList(REL_SUCCESS, REL_FAILURE))); + + @Override + public Set<Relationship> getRelationships() { + return this.relationships; + } + + @Override + public final List<PropertyDescriptor> getSupportedPropertyDescriptors() { + return descriptors; + } + + // Validate the channel (or username for a direct message) + private String validateChannel(String channel) { + if ((channel.startsWith("#") || channel.startsWith("@")) && channel.length() > 1) { + return null; + } + return "Channel must begin with '#' or '@'"; + } + + @Override + public void onTrigger(final ProcessContext context, final ProcessSession session) { + FlowFile flowFile = session.get(); + if ( flowFile == null ) { + return; + } + + JsonObjectBuilder builder = Json.createObjectBuilder(); + String text = context.getProperty(WEBHOOK_TEXT).evaluateAttributeExpressions(flowFile).getValue(); + if (text != null && !text.isEmpty()) { + builder.add("text", text); + } else { + // Slack requires the 'text' attribute + getLogger().error("FlowFile should have non-empty " + WEBHOOK_TEXT.getName()); + flowFile = session.penalize(flowFile); + session.transfer(flowFile, REL_FAILURE); + return; + } + + String channel = context.getProperty(CHANNEL).evaluateAttributeExpressions(flowFile).getValue(); + if (channel != null && !channel.isEmpty()) { + String error = validateChannel(channel); + if (error == null) { + builder.add("channel", channel); + } else { + getLogger().error("Invalid channel '{}': {}", new Object[]{channel, error}); + flowFile = session.penalize(flowFile); + session.transfer(flowFile, REL_FAILURE); + return; + } + } + + String username = context.getProperty(USERNAME).evaluateAttributeExpressions(flowFile).getValue(); + if (username != null && !username.isEmpty()) { + builder.add("username", username); + } + + String iconUrl = context.getProperty(ICON_URL).evaluateAttributeExpressions(flowFile).getValue(); + if (iconUrl != null && !iconUrl.isEmpty()) { + builder.add("icon_url", iconUrl); + } + + String iconEmoji = context.getProperty(ICON_EMOJI).evaluateAttributeExpressions(flowFile).getValue(); + if (iconEmoji != null && !iconEmoji.isEmpty()) { + builder.add("icon_emoji", iconEmoji); + } + + JsonObject jsonObject = builder.build(); + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = Json.createWriter(stringWriter); + jsonWriter.writeObject(jsonObject); + jsonWriter.close(); + + try { + URL url = new URL(context.getProperty(WEBHOOK_URL).getValue()); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream()); + String payload = "payload=" + URLEncoder.encode(stringWriter.getBuffer().toString(), "UTF-8"); + outputStream.writeBytes(payload); + outputStream.close(); + + int responseCode = conn.getResponseCode(); + if (responseCode >= 200 && responseCode < 300) { + getLogger().info("Successfully posted message to Slack"); + session.transfer(flowFile, REL_SUCCESS); + session.getProvenanceReporter().send(flowFile, context.getProperty(WEBHOOK_URL).getValue()); + } else { + getLogger().error("Failed to post message to Slack with response code {}", new Object[]{responseCode}); + flowFile = session.penalize(flowFile); + session.transfer(flowFile, REL_FAILURE); + context.yield(); + } + } catch (IOException e) { + getLogger().error("Failed to open connection", e); + flowFile = session.penalize(flowFile); + session.transfer(flowFile, REL_FAILURE); + context.yield(); + } + } + + private static class EmojiValidator implements Validator { + @Override + public ValidationResult validate(final String subject, final String input, final ValidationContext context) { + if (input.startsWith(":") && input.endsWith(":") && input.length() > 2) { + return new ValidationResult.Builder().subject(subject).input(input).valid(true).build(); + } + + return new ValidationResult.Builder().input(input).subject(subject).valid(false) + .explanation("Must begin and end with a colon") + .build(); + } + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor new file mode 100644 index 0000000..9a861ee --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor @@ -0,0 +1,15 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +org.apache.nifi.processors.slack.PutSlack \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/resources/docs/org.apache.nifi.processors.slack.PutSlack/additionalDetails.html ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/resources/docs/org.apache.nifi.processors.slack.PutSlack/additionalDetails.html b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/resources/docs/org.apache.nifi.processors.slack.PutSlack/additionalDetails.html new file mode 100644 index 0000000..53f2dc5 --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/main/resources/docs/org.apache.nifi.processors.slack.PutSlack/additionalDetails.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html lang="en"> + <!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + <head> + <meta charset="utf-8" /> + <title>PutSlack</title> + <link rel="stylesheet" href="../../css/component-usage.css" type="text/css" /> + </head> + + <body> + <!-- Processor Documentation ================================================== --> + <h2>Description:</h2> + <p> + The PutSlack processor sends messages to <a href="https://www.slack.com">Slack</a>, + a team-oriented messaging service. + </p> + <p> + This processor uses Slack's <a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">incoming webhooks</a> + custom integration to post messages to a specific channel. Before using PutSlack, your Slack team should be + configured for the incoming webhooks custom integration, and you'll need to configure at least one incoming + webhook. + </p> + <p> + To configure PutSlack, set the following mandatory properties: + <ul> + <li> + <code>Webhook URL</code>: The URL received from Slack that allows the processor to send messages to your team. + </li> + <li> + <code>Webhook Text</code>: The text of the message to send to Slack. + </li> + </ul> + </p> + </body> +</html> http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/CaptureServlet.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/CaptureServlet.java b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/CaptureServlet.java new file mode 100644 index 0000000..a605130 --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/CaptureServlet.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.slack; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Response.Status; + +import org.apache.nifi.stream.io.ByteArrayOutputStream; +import org.apache.nifi.stream.io.StreamUtils; +import org.apache.nifi.util.file.FileUtils; + +public class CaptureServlet extends HttpServlet { + + private static final long serialVersionUID = 8402271018449653919L; + + private volatile byte[] lastPost; + private volatile Map<String, String> lastPostHeaders; + + public byte[] getLastPost() { + return lastPost; + } + + public Map<String, String> getLastPostHeaders() { + return lastPostHeaders; + } + + @Override + protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Capture all the headers for reference. Intentionally choosing to not special handling for headers with multiple values for clarity + final Enumeration<String> headerNames = request.getHeaderNames(); + lastPostHeaders = new HashMap<>(); + while (headerNames.hasMoreElements()) { + final String nextHeader = headerNames.nextElement(); + lastPostHeaders.put(nextHeader, request.getHeader(nextHeader)); + } + + try { + StreamUtils.copy(request.getInputStream(), baos); + this.lastPost = baos.toByteArray(); + } finally { + FileUtils.closeQuietly(baos); + } + response.setStatus(Status.OK.getStatusCode()); + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/PutSlackTest.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/PutSlackTest.java b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/PutSlackTest.java new file mode 100644 index 0000000..bef2e5e --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/PutSlackTest.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.slack; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.util.TestRunner; +import org.apache.nifi.util.TestRunners; +import org.eclipse.jetty.servlet.ServletHandler; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PutSlackTest { + + private TestRunner testRunner; + private TestServer server; + private CaptureServlet servlet; + public static final String WEBHOOK_TEST_TEXT = "Hello From Apache NiFi"; + + @Before + public void init() throws Exception { + testRunner = TestRunners.newTestRunner(PutSlack.class); + + // set up web service + ServletHandler handler = new ServletHandler(); + handler.addServletWithMapping(CaptureServlet.class, "/*"); + servlet = (CaptureServlet) handler.getServlets()[0].getServlet(); + + // create the service + server = new TestServer(); + server.addHandler(handler); + server.startServer(); + } + + @Test(expected = AssertionError.class) + public void testBlankText() { + testRunner.setProperty(PutSlack.WEBHOOK_URL, server.getUrl()); + testRunner.setProperty(PutSlack.WEBHOOK_TEXT, ""); + + testRunner.enqueue(new byte[0]); + testRunner.run(1); + } + + @Test + public void testBlankTextViaExpression() { + testRunner.setProperty(PutSlack.WEBHOOK_URL, server.getUrl()); + testRunner.setProperty(PutSlack.WEBHOOK_TEXT, "${invalid-attr}"); // Create a blank webhook text + + testRunner.enqueue(new byte[0]); + testRunner.run(1); + testRunner.assertAllFlowFilesTransferred(PutSlack.REL_FAILURE); + } + + @Test + public void testInvalidChannel() { + testRunner.setProperty(PutSlack.WEBHOOK_URL, server.getUrl()); + testRunner.setProperty(PutSlack.WEBHOOK_TEXT, WEBHOOK_TEST_TEXT); + testRunner.setProperty(PutSlack.CHANNEL, "invalid"); + + testRunner.enqueue(new byte[0]); + testRunner.run(1); + testRunner.assertAllFlowFilesTransferred(PutSlack.REL_FAILURE); + } + + @Test(expected = AssertionError.class) + public void testInvalidIconUrl() { + testRunner.setProperty(PutSlack.WEBHOOK_URL, server.getUrl()); + testRunner.setProperty(PutSlack.WEBHOOK_TEXT, WEBHOOK_TEST_TEXT); + testRunner.setProperty(PutSlack.ICON_URL, "invalid"); + + testRunner.enqueue(new byte[0]); + testRunner.run(1); + } + + @Test(expected = AssertionError.class) + public void testInvalidIconEmoji() { + testRunner.setProperty(PutSlack.WEBHOOK_URL, server.getUrl()); + testRunner.setProperty(PutSlack.WEBHOOK_TEXT, WEBHOOK_TEST_TEXT); + testRunner.setProperty(PutSlack.ICON_EMOJI, "invalid"); + + testRunner.enqueue(new byte[0]); + testRunner.run(1); + } + + @Test + public void testGetPropertyDescriptors() throws Exception { + PutSlack processor = new PutSlack(); + List<PropertyDescriptor> pd = processor.getSupportedPropertyDescriptors(); + assertEquals("size should be eq", 6, pd.size()); + assertTrue(pd.contains(PutSlack.WEBHOOK_TEXT)); + assertTrue(pd.contains(PutSlack.WEBHOOK_URL)); + assertTrue(pd.contains(PutSlack.CHANNEL)); + assertTrue(pd.contains(PutSlack.USERNAME)); + assertTrue(pd.contains(PutSlack.ICON_URL)); + assertTrue(pd.contains(PutSlack.ICON_EMOJI)); + } + + @Test + public void testSimplePut() { + testRunner.setProperty(PutSlack.WEBHOOK_URL, server.getUrl()); + testRunner.setProperty(PutSlack.WEBHOOK_TEXT, PutSlackTest.WEBHOOK_TEST_TEXT); + + testRunner.enqueue(new byte[0]); + testRunner.run(1); + testRunner.assertAllFlowFilesTransferred(PutSlack.REL_SUCCESS, 1); + + byte[] expected = "payload=%7B%22text%22%3A%22Hello+From+Apache+NiFi%22%7D".getBytes(); + assertTrue(Arrays.equals(expected, servlet.getLastPost())); + } + + @Test + public void testSimplePutWithAttributes() { + testRunner.setProperty(PutSlack.WEBHOOK_URL, server.getUrl()); + testRunner.setProperty(PutSlack.WEBHOOK_TEXT, PutSlackTest.WEBHOOK_TEST_TEXT); + testRunner.setProperty(PutSlack.CHANNEL, "#test-attributes"); + testRunner.setProperty(PutSlack.USERNAME, "integration-test-webhook"); + testRunner.setProperty(PutSlack.ICON_EMOJI, ":smile:"); + + testRunner.enqueue(new byte[0]); + testRunner.run(1); + testRunner.assertAllFlowFilesTransferred(PutSlack.REL_SUCCESS, 1); + + final String expected = "payload=%7B%22text%22%3A%22Hello+From+Apache+NiFi%22%2C%22channel%22%3A%22%23test-attributes%22%2C%22username%22%3A%22" + + "integration-test-webhook%22%2C%22icon_emoji%22%3A%22%3Asmile%3A%22%7D"; + assertTrue(Arrays.equals(expected.getBytes(), servlet.getLastPost())); + } + + @Test + public void testSimplePutWithAttributesIconURL() { + testRunner.setProperty(PutSlack.WEBHOOK_URL, server.getUrl()); + testRunner.setProperty(PutSlack.WEBHOOK_TEXT, PutSlackTest.WEBHOOK_TEST_TEXT); + testRunner.setProperty(PutSlack.CHANNEL, "#test-attributes-url"); + testRunner.setProperty(PutSlack.USERNAME, "integration-test-webhook"); + testRunner.setProperty(PutSlack.ICON_URL, "http://lorempixel.com/48/48/"); + + testRunner.enqueue(new byte[0]); + testRunner.run(1); + testRunner.assertAllFlowFilesTransferred(PutSlack.REL_SUCCESS, 1); + + final String expected = "payload=%7B%22text%22%3A%22Hello+From+Apache+NiFi%22%2C%22channel%22%3A%22%23test-attributes-url%22%2C%22username%22%3A%22" + + "integration-test-webhook%22%2C%22icon_url%22%3A%22http%3A%2F%2Florempixel.com%2F48%2F48%2F%22%7D"; + assertTrue(Arrays.equals(expected.getBytes(), servlet.getLastPost())); + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/TestServer.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/TestServer.java b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/TestServer.java new file mode 100644 index 0000000..6506fd8 --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/nifi-slack-processors/src/test/java/org/apache/nifi/processors/slack/TestServer.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.slack; + +import org.apache.nifi.ssl.StandardSSLContextService; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import java.util.Map; + +/** + * Test server to assist with unit tests that requires a server to be stood up. + */ +public class TestServer { + + public static final String NEED_CLIENT_AUTH = "clientAuth"; + + private Server jetty; + private boolean secure = false; + + /** + * Creates the test server. + */ + public TestServer() { + createServer(null); + } + + /** + * Creates the test server. + * + * @param sslProperties SSLProps to be used in the secure connection. The keys should should use the StandardSSLContextService properties. + */ + public TestServer(final Map<String, String> sslProperties) { + createServer(sslProperties); + } + + private void createServer(final Map<String, String> sslProperties) { + jetty = new Server(); + + // create the unsecure connector + createConnector(); + + // create the secure connector if sslProperties are specified + if (sslProperties != null) { + createSecureConnector(sslProperties); + } + + jetty.setHandler(new HandlerCollection(true)); + } + + /** + * Creates the http connection + */ + private void createConnector() { + final ServerConnector http = new ServerConnector(jetty); + http.setPort(0); + // Severely taxed environments may have significant delays when executing. + http.setIdleTimeout(30000L); + jetty.addConnector(http); + } + + private void createSecureConnector(final Map<String, String> sslProperties) { + SslContextFactory ssl = new SslContextFactory(); + + if (sslProperties.get(StandardSSLContextService.KEYSTORE.getName()) != null) { + ssl.setKeyStorePath(sslProperties.get(StandardSSLContextService.KEYSTORE.getName())); + ssl.setKeyStorePassword(sslProperties.get(StandardSSLContextService.KEYSTORE_PASSWORD.getName())); + ssl.setKeyStoreType(sslProperties.get(StandardSSLContextService.KEYSTORE_TYPE.getName())); + } + + if (sslProperties.get(StandardSSLContextService.TRUSTSTORE.getName()) != null) { + ssl.setTrustStorePath(sslProperties.get(StandardSSLContextService.TRUSTSTORE.getName())); + ssl.setTrustStorePassword(sslProperties.get(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName())); + ssl.setTrustStoreType(sslProperties.get(StandardSSLContextService.TRUSTSTORE_TYPE.getName())); + } + + final String clientAuth = sslProperties.get(NEED_CLIENT_AUTH); + if (clientAuth == null) { + ssl.setNeedClientAuth(true); + } else { + ssl.setNeedClientAuth(Boolean.parseBoolean(clientAuth)); + } + + // build the connector + final ServerConnector https = new ServerConnector(jetty, ssl); + + // set host and port + https.setPort(0); + // Severely taxed environments may have significant delays when executing. + https.setIdleTimeout(30000L); + + // add the connector + jetty.addConnector(https); + + // mark secure as enabled + secure = true; + } + + public void clearHandlers() { + HandlerCollection hc = (HandlerCollection) jetty.getHandler(); + Handler[] ha = hc.getHandlers(); + if (ha != null) { + for (Handler h : ha) { + hc.removeHandler(h); + } + } + } + + public void addHandler(Handler handler) { + ((HandlerCollection) jetty.getHandler()).addHandler(handler); + } + + public void startServer() throws Exception { + jetty.start(); + } + + public void shutdownServer() throws Exception { + jetty.stop(); + jetty.destroy(); + } + + private int getPort() { + if (!jetty.isStarted()) { + throw new IllegalStateException("Jetty server not started"); + } + return ((ServerConnector) jetty.getConnectors()[0]).getLocalPort(); + } + + private int getSecurePort() { + if (!jetty.isStarted()) { + throw new IllegalStateException("Jetty server not started"); + } + return ((ServerConnector) jetty.getConnectors()[1]).getLocalPort(); + } + + public String getUrl() { + return "http://localhost:" + getPort(); + } + + public String getSecureUrl() { + String url = null; + if (secure) { + url = "https://localhost:" + getSecurePort(); + } + return url; + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-slack-bundle/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-slack-bundle/pom.xml b/nifi-nar-bundles/nifi-slack-bundle/pom.xml new file mode 100644 index 0000000..52e72fe --- /dev/null +++ b/nifi-nar-bundles/nifi-slack-bundle/pom.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-nar-bundles</artifactId> + <version>0.7.0-SNAPSHOT</version> + </parent> + + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-slack-bundle</artifactId> + <version>0.7.0-SNAPSHOT</version> + <packaging>pom</packaging> + + <modules> + <module>nifi-slack-processors</module> + <module>nifi-slack-nar</module> + </modules> + +</project> http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/slack/TestServer.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/slack/TestServer.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/slack/TestServer.java new file mode 100644 index 0000000..1037ca6 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/slack/TestServer.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.slack; + +import java.util.Map; +import org.apache.nifi.ssl.StandardSSLContextService; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +/** + * Test server to assist with unit tests that requires a server to be stood up. + */ +public class TestServer { + + public static final String NEED_CLIENT_AUTH = "clientAuth"; + + private Server jetty; + private boolean secure = false; + + /** + * Creates the test server. + */ + public TestServer() { + createServer(null); + } + + /** + * Creates the test server. + * + * @param sslProperties SSLProps to be used in the secure connection. The keys should should use the StandardSSLContextService properties. + */ + public TestServer(final Map<String, String> sslProperties) { + createServer(sslProperties); + } + + private void createServer(final Map<String, String> sslProperties) { + jetty = new Server(); + + // create the unsecure connector + createConnector(); + + // create the secure connector if sslProperties are specified + if (sslProperties != null) { + createSecureConnector(sslProperties); + } + + jetty.setHandler(new HandlerCollection(true)); + } + + /** + * Creates the http connection + */ + private void createConnector() { + final ServerConnector http = new ServerConnector(jetty); + http.setPort(0); + // Severely taxed environments may have significant delays when executing. + http.setIdleTimeout(30000L); + jetty.addConnector(http); + } + + private void createSecureConnector(final Map<String, String> sslProperties) { + SslContextFactory ssl = new SslContextFactory(); + + if (sslProperties.get(StandardSSLContextService.KEYSTORE.getName()) != null) { + ssl.setKeyStorePath(sslProperties.get(StandardSSLContextService.KEYSTORE.getName())); + ssl.setKeyStorePassword(sslProperties.get(StandardSSLContextService.KEYSTORE_PASSWORD.getName())); + ssl.setKeyStoreType(sslProperties.get(StandardSSLContextService.KEYSTORE_TYPE.getName())); + } + + if (sslProperties.get(StandardSSLContextService.TRUSTSTORE.getName()) != null) { + ssl.setTrustStorePath(sslProperties.get(StandardSSLContextService.TRUSTSTORE.getName())); + ssl.setTrustStorePassword(sslProperties.get(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName())); + ssl.setTrustStoreType(sslProperties.get(StandardSSLContextService.TRUSTSTORE_TYPE.getName())); + } + + final String clientAuth = sslProperties.get(NEED_CLIENT_AUTH); + if (clientAuth == null) { + ssl.setNeedClientAuth(true); + } else { + ssl.setNeedClientAuth(Boolean.parseBoolean(clientAuth)); + } + + // build the connector + final ServerConnector https = new ServerConnector(jetty, ssl); + + // set host and port + https.setPort(0); + // Severely taxed environments may have significant delays when executing. + https.setIdleTimeout(30000L); + + // add the connector + jetty.addConnector(https); + + // mark secure as enabled + secure = true; + } + + public void clearHandlers() { + HandlerCollection hc = (HandlerCollection) jetty.getHandler(); + Handler[] ha = hc.getHandlers(); + if (ha != null) { + for (Handler h : ha) { + hc.removeHandler(h); + } + } + } + + public void addHandler(Handler handler) { + ((HandlerCollection) jetty.getHandler()).addHandler(handler); + } + + public void startServer() throws Exception { + jetty.start(); + } + + public void shutdownServer() throws Exception { + jetty.stop(); + jetty.destroy(); + } + + private int getPort() { + if (!jetty.isStarted()) { + throw new IllegalStateException("Jetty server not started"); + } + return ((ServerConnector) jetty.getConnectors()[0]).getLocalPort(); + } + + private int getSecurePort() { + if (!jetty.isStarted()) { + throw new IllegalStateException("Jetty server not started"); + } + return ((ServerConnector) jetty.getConnectors()[1]).getLocalPort(); + } + + public String getUrl() { + return "http://localhost:" + getPort(); + } + + public String getSecureUrl() { + String url = null; + if (secure) { + url = "https://localhost:" + getSecurePort(); + } + return url; + } +} http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/nifi-nar-bundles/pom.xml ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/pom.xml b/nifi-nar-bundles/pom.xml index b2395cd..e05b3b3 100644 --- a/nifi-nar-bundles/pom.xml +++ b/nifi-nar-bundles/pom.xml @@ -63,6 +63,7 @@ <module>nifi-hive-bundle</module> <module>nifi-site-to-site-reporting-bundle</module> <module>nifi-mqtt-bundle</module> + <module>nifi-slack-bundle</module> </modules> <dependencyManagement> http://git-wip-us.apache.org/repos/asf/nifi/blob/4eb59fc8/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 80b4b28..981f528 100644 --- a/pom.xml +++ b/pom.xml @@ -1103,6 +1103,12 @@ language governing permissions and limitations under the License. --> </dependency> <dependency> <groupId>org.apache.nifi</groupId> + <artifactId>nifi-slack-nar</artifactId> + <version>0.7.0-SNAPSHOT</version> + <type>nar</type> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> <artifactId>nifi-elasticsearch-nar</artifactId> <version>0.7.0-SNAPSHOT</version> <type>nar</type>