Skip to content

Commit

Permalink
Add support for per-build webhook configuration
Browse files Browse the repository at this point in the history
Add field to build for webhook token or URL. This overrides
any saved authentication and channel data and can only post
to a single channel.

Update `RocketChatClientImpl` and `RocketChatClientCallBuilder` to
take webhook token and invoke a new authenticator for webhooks.

Related: JENKINS-42099
  • Loading branch information
SonOfBowser committed Dec 29, 2017
1 parent 8efccc4 commit c7a0696
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 9 deletions.
Expand Up @@ -48,6 +48,7 @@ public class RocketChatNotifier extends Notifier {
private CommitInfoChoice commitInfoChoice;
private boolean includeCustomMessage;
private String customMessage;
private String webhookToken;

@Override
public DescriptorImpl getDescriptor() {
Expand Down Expand Up @@ -116,12 +117,16 @@ public String getCustomMessage() {
return customMessage;
}

public String getWebhookToken() {
return webhookToken;
}

@DataBoundConstructor
public RocketChatNotifier(final String rocketServerUrl, final boolean trustSSL, final String username, final String password, final String channel, final String buildServerUrl,
final boolean startNotification, final boolean notifyAborted, final boolean notifyFailure,
final boolean notifyNotBuilt, final boolean notifySuccess, final boolean notifyUnstable, final boolean notifyBackToNormal,
final boolean notifyRepeatedFailure, final boolean includeTestSummary, CommitInfoChoice commitInfoChoice,
boolean includeCustomMessage, String customMessage) {
boolean includeCustomMessage, String customMessage, String webhookToken) {
super();
this.rocketServerUrl = rocketServerUrl;
this.trustSSL = trustSSL;
Expand All @@ -141,6 +146,7 @@ public RocketChatNotifier(final String rocketServerUrl, final boolean trustSSL,
this.commitInfoChoice = commitInfoChoice;
this.includeCustomMessage = includeCustomMessage;
this.customMessage = customMessage;
this.webhookToken = webhookToken;
}

public BuildStepMonitor getRequiredMonitorService() {
Expand Down Expand Up @@ -176,6 +182,9 @@ public RocketClient newRocketChatClient(AbstractBuild r, BuildListener listener)
username = env.expand(username);
password = env.expand(password);

if (!StringUtils.isEmpty(webhookToken)) {
return new RocketClientWebhookImpl(serverUrl, trustSSL, webhookToken);
}
return new RocketClientImpl(serverUrl, trustSSL, username, password, channel);
}

Expand Down Expand Up @@ -214,6 +223,7 @@ public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
private String password;
private String channel;
private String buildServerUrl;
private String webhookToken;

public static final CommitInfoChoice[] COMMIT_INFO_CHOICES = CommitInfoChoice.values();

Expand All @@ -240,6 +250,10 @@ public String getPassword() {
public String getChannel() {
return channel;
}

public String getWebhookToken() {
return webhookToken;
}

public String getBuildServerUrl() {
if (buildServerUrl == null || buildServerUrl.equalsIgnoreCase("")) {
Expand Down Expand Up @@ -274,9 +288,10 @@ public RocketChatNotifier newInstance(StaplerRequest sr, JSONObject json) {
CommitInfoChoice commitInfoChoice = CommitInfoChoice.forDisplayName(sr.getParameter("rocketCommitInfoChoice"));
boolean includeCustomMessage = "on".equals(sr.getParameter("includeCustomMessage"));
String customMessage = sr.getParameter("customMessage");
String webhookToken = sr.getParameter("webhookToken");
return new RocketChatNotifier(rocketServerUrl, trustSSL, username, password, channel, buildServerUrl, startNotification, notifyAborted,
notifyFailure, notifyNotBuilt, notifySuccess, notifyUnstable, notifyBackToNormal, notifyRepeatedFailure,
includeTestSummary, commitInfoChoice, includeCustomMessage, customMessage);
includeTestSummary, commitInfoChoice, includeCustomMessage, customMessage, webhookToken);
}
return null;
}
Expand All @@ -296,6 +311,7 @@ public boolean configure(StaplerRequest sr, JSONObject formData) throws FormExce
if (buildServerUrl != null && !buildServerUrl.endsWith("/")) {
buildServerUrl = buildServerUrl + "/";
}
webhookToken = sr.getParameter("webhookToken");
save();
return super.configure(sr, formData);
}
Expand All @@ -310,7 +326,8 @@ public FormValidation doTestConnection(@QueryParameter("rocketServerUrl") final
@QueryParameter("rocketUsername") final String username,
@QueryParameter("rocketPassword") final String password,
@QueryParameter("rocketChannel") final String channel,
@QueryParameter("rocketBuildServerUrl") final String buildServerUrl) throws FormException {
@QueryParameter("rocketBuildServerUrl") final String buildServerUrl,
@QueryParameter("webhookToken") final String webhookToken) throws FormException {
try {
String targetServerUrl = rocketServerUrl + RocketClientImpl.API_PATH;
if (StringUtils.isEmpty(rocketServerUrl)) {
Expand All @@ -336,7 +353,17 @@ public FormValidation doTestConnection(@QueryParameter("rocketServerUrl") final
if (StringUtils.isEmpty(targetBuildServerUrl)) {
targetBuildServerUrl = this.buildServerUrl;
}
RocketClient rocketChatClient = new RocketClientImpl(targetServerUrl, targetTrustSSL, targetUsername, targetPassword, targetChannel);
String targetWebhookToken = webhookToken;
if (StringUtils.isEmpty(targetWebhookToken)) {
targetWebhookToken = this.webhookToken;
}

RocketClient rocketChatClient;
if (!StringUtils.isEmpty(targetWebhookToken)){
rocketChatClient = new RocketClientWebhookImpl(targetServerUrl, targetTrustSSL, targetWebhookToken);
} else {
rocketChatClient = new RocketClientImpl(targetServerUrl, targetTrustSSL, targetUsername, targetPassword, targetChannel);
}
String message = "RocketChat/Jenkins plugin: you're all set on " + targetBuildServerUrl;
LOGGER.fine("Start validating config");
rocketChatClient.validate();
Expand Down
@@ -0,0 +1,71 @@
package jenkins.plugins.rocketchatnotifier;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import jenkins.plugins.rocketchatnotifier.rocket.RocketChatClient;
import jenkins.plugins.rocketchatnotifier.rocket.RocketChatClientImpl;
import sun.security.validator.ValidatorException;

public class RocketClientWebhookImpl implements RocketClient {

private static final Logger LOGGER = Logger.getLogger(RocketClientImpl.class.getName());

private RocketChatClient client;

public RocketClientWebhookImpl(String serverUrl, boolean trustSSL, String token) {
client = new RocketChatClientImpl(serverUrl, trustSSL, token);
}

public boolean publish(String message) {
try {
LOGGER.fine("Starting sending message to webhook");
this.client.send("", message);
return true;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "I/O error error during publishing message", e);
return false;
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Unknown error error during publishing message", e);
return false;
}
}

@Override
public boolean publish(final String message, final String emoji, final String avatar) {
try {
LOGGER.fine("Starting sending message to webhook");
this.client.send("", message, emoji, avatar);
return true;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "I/O error error during publishing message", e);
return false;
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Unknown error error during publishing message", e);
return false;
}
}

@Override
public boolean publish(final String message, final String emoji, final String avatar, final List<Map<String, Object>> attachments) {
try {
LOGGER.fine("Starting sending message to webhook");
this.client.send("", message, emoji, avatar, attachments);
return true;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "I/O error error during publishing message", e);
return false;
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Unknown error error during publishing message", e);
return false;
}
}

@Override
public void validate() throws ValidatorException, IOException {
this.client.send("", "Test message from Jenkins via Webhook");
}
}
Expand Up @@ -48,6 +48,10 @@ protected RocketChatClientCallBuilder(String serverUrl, boolean trustSSL, String
this(new RocketChatBasicCallAuthentication(serverUrl, user, password), serverUrl, trustSSL);
}

protected RocketChatClientCallBuilder(String serverUrl, boolean trustSSL, String webhookToken) {
this(new RocketChatWebhookAuthentication(serverUrl, webhookToken), serverUrl, trustSSL);
}

protected RocketChatClientCallBuilder(RocketChatCallAuthentication authentication, String serverUrl,
boolean trustSSL) {
this.authentication = authentication;
Expand Down
Expand Up @@ -5,6 +5,8 @@
import jenkins.plugins.rocketchatnotifier.model.Response;
import jenkins.plugins.rocketchatnotifier.model.Room;
import jenkins.plugins.rocketchatnotifier.model.User;

import org.apache.commons.lang.StringUtils;
import org.json.simple.JSONValue;
import sun.security.validator.ValidatorException;

Expand Down Expand Up @@ -41,6 +43,18 @@ public RocketChatClientImpl(String serverUrl, boolean trustSSL, String user, Str
this.callBuilder = new RocketChatClientCallBuilder(serverUrl, trustSSL, JSONValue.escape(user), JSONValue.escape(password));
}

/**
* Initialize a new instance of the client providing the server's url along with webhook for
* posting notifications.
*
* @param serverUrl of the Rocket.Chat server, with or without it ending in "/api/"
* @param trustSSL if set set the SSL certificate of the rpcket server will not be checked
* @param webhookToken authentication token or URL
*/
public RocketChatClientImpl(String serverUrl, boolean trustSSL, String webhookToken) {
this.callBuilder = new RocketChatClientCallBuilder(serverUrl, trustSSL, webhookToken);
}

@Override
public User[] getUsers() throws IOException {
Response res = this.callBuilder.buildCall(RocketChatRestApiV1.UsersList);
Expand Down Expand Up @@ -125,12 +139,15 @@ public void send(final String channelName, final String message, final String em

private void sendSingleMessage(final String singleChannelName, final String message, final String emoji, final String avatar, final List<Map<String, Object>> attachments) throws IOException {
Map body = new HashMap<String, String>();
String targetChannelName = singleChannelName.trim();
if (!targetChannelName.matches("^[@#].+")) {
targetChannelName = "#" + targetChannelName;
if (!StringUtils.isEmpty(singleChannelName)) {
String targetChannelName = singleChannelName.trim();
if (!targetChannelName.matches("^[@#].+")) {
targetChannelName = "#" + targetChannelName;
}

body.put("channel", targetChannelName);
}

body.put("channel", targetChannelName);
body.put("text", message);
if (this.getInfo().getVersion().compareTo("0.50.1") >= 0) {
if (emoji != null)
Expand Down
@@ -0,0 +1,45 @@
package jenkins.plugins.rocketchatnotifier.rocket;

import java.io.IOException;
import com.mashape.unirest.request.HttpRequest;

public class RocketChatWebhookAuthentication implements RocketChatCallAuthentication {
private static final String HOOKS_PATH = "hooks/";

private String serverUrl;
private String webhookUrl;

public RocketChatWebhookAuthentication(String serverUrl, String webhookToken) {
super();
this.serverUrl = serverUrl + (serverUrl.endsWith("/") ? "" : "/");
this.webhookUrl = webhookToken.contains(HOOKS_PATH) ? webhookToken : (this.serverUrl + HOOKS_PATH + webhookToken);
}

@Override
public boolean isAuthenticated() {
// No authentication needed
return true;
}

@Override
public void doAuthentication() throws IOException {
// No authentication needed
}

@Override
public String getUrlForRequest(RocketChatRestApiV1 call) {
switch (call) {
case Info:
return serverUrl + "/api/" + call.getMethodName();
case PostMessage:
return webhookUrl;
default:
throw new UnsupportedOperationException("Unable to handle request " + call.name() + " using webhook");
}
}

@Override
public void addAuthenticationDataToRequest(HttpRequest request) {
// No authentication needed
}
}
Expand Up @@ -55,8 +55,12 @@
<f:entry title="Project Channel" description="Comma separated list of rooms (e.g. #project) and / or persons (e.g. @john)">
<f:textbox name="rocketChannel" value="${instance.getChannel()}"/>
</f:entry>
<f:entry title="Incoming Webhook Token" description="Webhook token for posting messages. Overrides global authentication data and channel. ">
<f:textbox name="webhookToken" value="${instance.getWebhookToken()}"/>
</f:entry>

<f:validateButton
title="${%Test Connection}" progress="${%Testing...}"
method="testConnection" with="rocketChannel"/>
method="testConnection" with="rocketChannel,webhookToken"/>
</f:advanced>
</j:jelly>

0 comments on commit c7a0696

Please sign in to comment.