IGNITE-16829 Support configurable mail smtp host, disabling auth and SSL (#190)
diff --git a/conf/branches.json b/conf/branches.json
index a2f7f1b..c61c3f8 100644
--- a/conf/branches.json
+++ b/conf/branches.json
@@ -95,13 +95,22 @@
}
],
"notifications": {
- /* Email sending settings, only smtp.gmail.com is now supported. */
+ /* Email sending settings */
"email": {
- /* Username, equal to from: field */
+ /* Username, and 'from' field for emails. */
"username": "ignitetcbot@gmail.com",
+ "auth": true,
/** Email password encoded using Password Encoder. */
- "pwd": ""
- //todo ^ specify password
+ "pwd": "",
+ //todo ^ specify password or set auth to false
+ "smtp": {
+ /** Host or IP */
+ "host": "127.0.0.1",
+ /** Default is 25 for non-ssl, 465 for ssl enabled */
+ "port": 465,
+ /** SSL enabled, default true */
+ "ssl": true
+ }
},
"channels": [
{
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
index d6ff6ec..313ce2e 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
@@ -102,11 +102,7 @@
/** {@inheritDoc} */
@Override public NotificationsConfig notifications() {
- NotificationsConfig notifications = getConfig().notifications();
- if (notifications != null && !notifications.isEmpty())
- return notifications;
-
- return NotificationsConfig.backwardConfig();
+ return getConfig().notifications();
}
/** {@inheritDoc} */
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
index 19b0c26..c488331 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
@@ -299,7 +299,7 @@
String builds = next.buildIdToIssue.keySet().toString();
String subj = "[MTCGA]: " + next.countIssues() + " new failures in builds " + builds + " needs to be handled";
- emailSender.sendEmail(addr, subj, next.toHtml(), next.toPlainText(), notifications);
+ emailSender.sendEmail(addr, subj, next.toHtml(), next.toPlainText(), notifications.email());
sndStat.computeIfAbsent(addr, k -> new AtomicInteger()).incrementAndGet();
}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
index 706a2db..e947aa7 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Version.java
@@ -28,7 +28,7 @@
public static final String GITHUB_REF = "https://github.com/apache/ignite-teamcity-bot";
/** TC Bot Version. */
- public static final String VERSION = "20211207";
+ public static final String VERSION = "20220414";
/** Java version, where Web App is running. */
public String javaVer;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/monitoring/MonitoringService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/monitoring/MonitoringService.java
index b1f2712..e196ec3 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/monitoring/MonitoringService.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/monitoring/MonitoringService.java
@@ -27,6 +27,8 @@
import org.apache.ignite.tcbot.engine.conf.INotificationChannel;
import org.apache.ignite.tcbot.engine.conf.ITcBotConfig;
import org.apache.ignite.tcbot.engine.conf.NotificationsConfig;
+import org.apache.ignite.tcbot.notify.IEmailSender;
+import org.apache.ignite.tcbot.notify.ISendEmailConfig;
import org.apache.ignite.tcbot.notify.ISlackSender;
import javax.annotation.security.PermitAll;
@@ -37,7 +39,6 @@
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
@@ -120,13 +121,35 @@
slackSender.sendMessage(channel.slack(), "Test Slack notification message!", notifications);
}
}
- catch (IOException e) {
+ catch (Exception e) {
return new SimpleResult("Failed to send test Slack message: " + e.getMessage());
}
return new SimpleResult("Ok");
}
+ @POST
+ @PermitAll
+ @Path("testEmailNotification")
+ public SimpleResult testEmailNotification(String address) {
+ IEmailSender emailSender = CtxListener.getInjector(ctx).getInstance(IEmailSender.class);
+
+ ITcBotConfig tcBotConfig = CtxListener.getInjector(ctx).getInstance(ITcBotConfig.class);
+
+ try {
+ NotificationsConfig notifications = tcBotConfig.notifications();
+ String subj = "[MTCGA]: test email notification";
+
+ ISendEmailConfig email = notifications.email();
+ String plainText = "Test Email notification message!";
+ emailSender.sendEmail(address, subj, plainText, plainText, email);
+ } catch (Exception e) {
+ return new SimpleResult("Failed to send test Email message: " + e.getMessage());
+ }
+
+ return new SimpleResult("Ok");
+ }
+
@GET
@PermitAll
diff --git a/ignite-tc-helper-web/src/main/webapp/monitoring.html b/ignite-tc-helper-web/src/main/webapp/monitoring.html
index 7597a52..aceec3e 100644
--- a/ignite-tc-helper-web/src/main/webapp/monitoring.html
+++ b/ignite-tc-helper-web/src/main/webapp/monitoring.html
@@ -157,6 +157,24 @@
alert(JSON.stringify(result));
}
});
+
+ return false;
+ }
+
+ function testEmailNotification() {
+ $.ajax({
+ url: "rest/monitoring/testEmailNotification",
+ method: "post",
+ data: $("#emailForm").serialize(),
+ success: function(result) {
+ alert(JSON.stringify(result));
+ },
+ error: function(result) {
+ alert(JSON.stringify(result));
+ }
+ });
+
+ return false;
}
</script>
@@ -178,6 +196,14 @@
<hr>
<b>Test Slack notification:</b> <button onclick="testSlackNotification()">Send</button>
+<b>Test Email notification:</b>
+
+<form id="emailForm"><b>Email Address:</b>
+ <input type="text" placeholder="Enter Address" name="address" required>
+</form>
+
+<button onclick="testEmailNotification()">Send</button>
+
<br>
<div id="loadStatus"></div>
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/EmailSettings.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/EmailSettings.java
index 0c130f9..47f06f5 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/EmailSettings.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/EmailSettings.java
@@ -16,13 +16,29 @@
*/
package org.apache.ignite.tcbot.engine.conf;
-public class EmailSettings {
+import com.google.common.base.Preconditions;
+import org.apache.ignite.tcbot.common.conf.PasswordEncoder;
+import org.apache.ignite.tcbot.notify.ISendEmailConfig;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+@SuppressWarnings("unused")
+public class EmailSettings implements ISendEmailConfig {
/** Email to send notifications from. */
private String username;
/** Email password, encoded using Password Encoder. */
private String pwd;
+ /** Custom smtp server, default is gmail */
+ private SmtpSettings smtp;
+
+ /** Default is true. */
+ private Boolean auth;
+
/**
* @return Email to send notifications from.
*/
@@ -50,4 +66,63 @@
public void password(String pwd) {
this.pwd = pwd;
}
+
+ @Nullable
+ public SmtpSettings smtp() {
+ return smtp;
+ }
+
+ @Nullable public Boolean isAuthRequired() {
+ return auth;
+ }
+
+ @Nullable
+ @Override
+ public Boolean isSmtpSsl() {
+ SmtpSettings smtp = smtp();
+
+ return smtp != null ? smtp.ssl() : null;
+ }
+
+ @Nullable
+ @Override
+ public Integer smtpPort() {
+ SmtpSettings smtp = smtp();
+
+ return smtp != null ? smtp.port() : null;
+ }
+
+ /**
+ *
+ */
+ @Nonnull
+ @Override public String usernameMandatory() {
+ String username = username();
+
+ Preconditions.checkState(!isNullOrEmpty(username),
+ "notifications/email/username property should be filled in branches.json");
+
+ return username;
+ }
+
+ /**
+ * @return Email password.
+ */
+ @Nonnull
+ @Override public String passwordClearMandatory() {
+ Preconditions.checkState(!isNullOrEmpty(password()),
+ "notifications/email/pwd property should be filled in branches.json");
+
+ return PasswordEncoder.decode(password());
+ }
+
+ @Nullable
+ @Override
+ public String smtpHost() {
+ SmtpSettings smtp = smtp();
+
+ return smtp != null ? smtp.host() : null;
+ }
+
+
}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/NotificationChannel.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/NotificationChannel.java
index 9f1b57d..3ad0b16 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/NotificationChannel.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/NotificationChannel.java
@@ -26,7 +26,7 @@
*
*/
public class NotificationChannel implements INotificationChannel {
- /** (Destionation) Email. */
+ /** (Destination) Email. */
private String email;
/** Slack. */
@@ -74,14 +74,6 @@
return (tagsFilter != null) && !tagsFilter.isEmpty();
}
- public void slack(String slack) {
- this.slack = slack;
- }
-
- public void subscribe(String trackedBranchName) {
- this.subscribed.add(trackedBranchName);
- }
-
/** {@inheritDoc} */
@Override public String toString() {
return MoreObjects.toStringHelper(this)
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/NotificationsConfig.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/NotificationsConfig.java
index 3d4da02..b63c10e 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/NotificationsConfig.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/NotificationsConfig.java
@@ -19,28 +19,23 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
-import org.apache.ignite.tcbot.common.conf.ITcServerConfig;
import org.apache.ignite.tcbot.common.conf.PasswordEncoder;
-import org.apache.ignite.tcbot.common.conf.TcBotWorkDir;
+import org.apache.ignite.tcbot.notify.ISendEmailConfig;
+import org.apache.ignite.tcbot.notify.ISlackBotConfig;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Properties;
-import javax.annotation.Nonnull;
-import org.apache.ignite.tcbot.notify.ISendEmailConfig;
-import org.apache.ignite.tcbot.notify.ISlackBotConfig;
import static com.google.common.base.Strings.isNullOrEmpty;
/**
* Notifications Config
*/
-public class NotificationsConfig implements ISendEmailConfig, ISlackBotConfig {
+public class NotificationsConfig implements ISlackBotConfig {
/** (Source) Email. */
private EmailSettings email = new EmailSettings();
@@ -48,60 +43,9 @@
private String slackAuthTok;
/** Channels to send notifications to. */
+ @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private List<NotificationChannel> channels = new ArrayList<>();
- private static final String MAIL_PROPS = "mail.auth.properties";
- private static final String USERNAME = "username";
- private static final String ENCODED_PASSWORD = "encoded_password";
- /** Slack authorization token property name. */
- private static final String SLACK_AUTH_TOKEN = "slack.auth_token";
- @Deprecated
- private static final String SLACK_CHANNEL = "slack.channel";
-
- @Nonnull
- public static NotificationsConfig backwardConfig() {
- Properties cfgProps = loadEmailSettings();
-
- NotificationsConfig cfg = new NotificationsConfig();
-
- cfg.slackAuthTok = cfgProps.getProperty(SLACK_AUTH_TOKEN);
-
- cfg.email.username(cfgProps.getProperty(USERNAME));
-
- cfg.email.password(cfgProps.getProperty(ENCODED_PASSWORD));
-
- String slackCh = cfgProps.getProperty(SLACK_CHANNEL);
- if (!Strings.isNullOrEmpty(slackCh)) {
- NotificationChannel ch = new NotificationChannel();
- ch.slack("#" + slackCh);
- ch.subscribe(ITcServerConfig.DEFAULT_TRACKED_BRANCH_NAME);
- cfg.channels.add(ch);
- }
-
- return cfg;
- }
-
- public static Properties loadEmailSettings() {
- try {
- return loadProps(new File(TcBotWorkDir.resolveWorkDir(), MAIL_PROPS));
- }
- catch (IOException e) {
- e.printStackTrace();
- return new Properties();
- }
- }
-
- private static Properties loadProps(File file) throws IOException {
- Properties props = new Properties();
-
- try (FileReader reader = new FileReader(file)) {
- props.load(reader);
- }
-
- return props;
- }
-
-
public boolean isEmpty() {
return (email == null || Strings.isNullOrEmpty(email.username()))
&& (email == null || Strings.isNullOrEmpty(email.password()))
@@ -112,50 +56,12 @@
return slackAuthTok;
}
- /**
- * @return Email to send notifications from.
- */
- public String emailUsername() {
- return email == null ? null : email.username();
- }
-
- /**
- *
- */
- @Nonnull
- @Override public String emailUsernameMandatory() {
- String username = emailUsername();
-
- Preconditions.checkState(!isNullOrEmpty(username),
- "notifications/email/username property should be filled in branches.json");
-
- return username;
- }
-
- /**
- * @return Email password.
- */
- @Nonnull
- @Override public String emailPasswordClearMandatory() {
- Preconditions.checkNotNull(email,
- "notifications/email/pwd property should be filled in branches.json");
- Preconditions.checkState(!isNullOrEmpty(email.password()),
- "notifications/email/pwd property should be filled in branches.json");
-
- return PasswordEncoder.decode(email.password());
- }
public Collection<? extends INotificationChannel> channels() {
- if (channels == null)
- return Collections.emptyList();
-
- return Collections.unmodifiableList(channels);
+ return channels == null ? Collections.emptyList() : Collections.unmodifiableList(channels);
}
- public void addChannel(NotificationChannel ch) {
- if (channels == null)
- this.channels = new ArrayList<>();
-
- channels.add(ch);
+ public ISendEmailConfig email() {
+ return email;
}
}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/SmtpSettings.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/SmtpSettings.java
new file mode 100644
index 0000000..4911a05
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/conf/SmtpSettings.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ignite.tcbot.engine.conf;
+
+import javax.annotation.Nullable;
+
+public class SmtpSettings {
+ /** Default is gmail server: "smtp.gmail.com". */
+ private String host;
+
+ /** Default is 465. */
+ private Integer port;
+
+ /** Default is true. */
+ private Boolean ssl;
+
+ @Nullable public String host() {
+ return host;
+ }
+
+ public Integer port() {
+ return port;
+ }
+
+ public Boolean ssl() {
+ return ssl;
+ }
+}
diff --git a/tcbot-notify/src/main/java/org/apache/ignite/tcbot/notify/EmailSender.java b/tcbot-notify/src/main/java/org/apache/ignite/tcbot/notify/EmailSender.java
index 87933d6..6c895bc 100644
--- a/tcbot-notify/src/main/java/org/apache/ignite/tcbot/notify/EmailSender.java
+++ b/tcbot-notify/src/main/java/org/apache/ignite/tcbot/notify/EmailSender.java
@@ -30,39 +30,60 @@
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
+import static com.google.common.base.Strings.isNullOrEmpty;
+
/**
* Class for sending email with configured credentials.
*/
class EmailSender implements IEmailSender {
/** {@inheritDoc} */
@Override public void sendEmail(String to, String subject, String html, String plainText,
- ISendEmailConfig notifications) throws MessagingException {
+ ISendEmailConfig emailConfig) throws MessagingException {
- String user = notifications.emailUsernameMandatory();
+ String user = emailConfig.usernameMandatory();
- String from = user;
+ Authenticator authenticator;
+ Boolean authRequired = emailConfig.isAuthRequired();
+ boolean auth = authRequired == null || authRequired;
+ if (auth) {
+ String pwd = emailConfig.passwordClearMandatory();
- final String pwd = notifications.emailPasswordClearMandatory();
-
- Properties props = new Properties();
- props.put("mail.smtp.host", "smtp.gmail.com");
- props.put("mail.smtp.socketFactory.port", "465");
- props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
- props.put("mail.smtp.auth", "true");
- props.put("mail.smtp.port", "465");
-
- Session ses = Session.getInstance(props,
- new Authenticator() {
- @Override protected PasswordAuthentication getPasswordAuthentication() {
+ authenticator = new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, pwd);
}
- });
+ };
+ } else {
+ authenticator = null;
+ }
+
+ String smtpHost = emailConfig.smtpHost();
+
+ Boolean sslCfg = emailConfig.isSmtpSsl();
+ boolean useSsl = sslCfg == null || sslCfg;
+ int defaultPort = useSsl ? 465 : 25;
+ Integer smtpPortCfg = emailConfig.smtpPort();
+ String smtpPort = Integer.toString(smtpPortCfg == null ? defaultPort : smtpPortCfg);
+
+ Properties props = new Properties();
+ props.put("mail.smtp.host", isNullOrEmpty(smtpHost) ? "smtp.gmail.com" : smtpHost);
+
+ if (useSsl) {
+ props.put("mail.smtp.socketFactory.port", smtpPort);
+ props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+ }
+
+ props.put("mail.smtp.auth", Boolean.toString(auth));
+ props.put("mail.smtp.port", smtpPort);
+
+ Session ses = Session.getInstance(props, authenticator);
// Create a default MimeMessage object.
MimeMessage msg = new MimeMessage(ses);
// Set From: header field of the header.
- msg.setFrom(new InternetAddress(from));
+ msg.setFrom(new InternetAddress(user));
// Set To: header field of the header.
msg.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
diff --git a/tcbot-notify/src/main/java/org/apache/ignite/tcbot/notify/ISendEmailConfig.java b/tcbot-notify/src/main/java/org/apache/ignite/tcbot/notify/ISendEmailConfig.java
index 307d042..bb51cdf 100644
--- a/tcbot-notify/src/main/java/org/apache/ignite/tcbot/notify/ISendEmailConfig.java
+++ b/tcbot-notify/src/main/java/org/apache/ignite/tcbot/notify/ISendEmailConfig.java
@@ -18,6 +18,7 @@
package org.apache.ignite.tcbot.notify;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
/**
* Source email configuration. Now only gmail server is supported.
@@ -27,11 +28,18 @@
* @return Email username.
*/
@Nonnull
- public String emailUsernameMandatory() ;
+ String usernameMandatory() ;
/**
* @return Email password.
*/
@Nonnull
- public String emailPasswordClearMandatory() ;
+ String passwordClearMandatory() ;
+
+ @Nullable Boolean isAuthRequired();
+
+ @Nullable Boolean isSmtpSsl();
+ @Nullable String smtpHost();
+
+ @Nullable Integer smtpPort();
}