Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SMTP Mail Plugin #11

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ some officially supported implementations.
- defines an implementation for Prometheus Alertmanager
- [`lenses-cloudwatch-plugin`](./lenses-cloudwatch-plugin)
- defines an implementation for CloudWatch Events
- [`lenses-mail-alerts-plugin`](./lenses-mail-alerts-plugin)
- defines an implementation for SMTP emails

All modules are published to Maven central. In addition, standalone JARs of
each of plugin integration are available to download from Github releases, ready
Expand Down
17 changes: 16 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ ThisBuild / publishTo := sonatypePublishTo.value
val scalaJava8Compat = "org.scala-lang.modules" %% "scala-java8-compat" % "0.9.0"
val sl4fj = "org.slf4j" % "slf4j-api" % "1.7.30"
val jslack = "com.github.seratch" % "jslack" % "1.0.26"
val mail = "javax.mail" % "mail" % "1.5.0-b01"
val awsCloudWatchEvents = "software.amazon.awssdk" % "cloudwatchevents" % "2.9.26"
val httpclient = "org.apache.httpcomponents" % "httpclient" % "4.5.9"
val circeParser = "io.circe" %% "circe-parser" % "0.13.0"
Expand All @@ -74,14 +75,15 @@ val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5"
// Root project
lazy val root = (project in file("."))
.disablePlugins(AssemblyPlugin)
.aggregate(alertsPluginApi, slackAlertsPlugin, alertManagerPlugin, cloudWatchAlertsPlugin)
.aggregate(alertsPluginApi, slackAlertsPlugin, alertManagerPlugin, mailAlertsPlugin, cloudWatchAlertsPlugin)
.settings(
name := "lenses-alerts-plugin",
ghreleaseRepoOrg := "lensesio",
ghreleaseNotes := identity,
ghreleaseAssets := List(
(slackAlertsPlugin / assembly / assemblyOutputPath).value,
(alertManagerPlugin / assembly / assemblyOutputPath).value,
(mailAlertsPlugin / assembly / assemblyOutputPath).value,
(cloudWatchAlertsPlugin / assembly / assemblyOutputPath).value,
),
skip in publish := true
Expand Down Expand Up @@ -127,6 +129,19 @@ lazy val alertManagerPlugin = (project in file("lenses-alertmanager-plugin"))
assembly / assemblyJarName := s"${name.value}-standalone-${version.value}.jar",
)

lazy val mailAlertsPlugin = (project in file("lenses-mail-alerts-plugin"))
.disablePlugins(SbtGithubReleasePlugin)
.dependsOn(alertsPluginApi)
.settings(
name := "lenses-mail-alerts-plugin",
description := "Lenses.io Mail Alerts Plugin",
libraryDependencies += sl4fj % Provided,
libraryDependencies += mail,
libraryDependencies += logbackClassic % Test,
libraryDependencies += scalaTest % Test,
assembly / assemblyJarName := s"${name.value}-standalone-${version.value}.jar",
)

lazy val cloudWatchAlertsPlugin = (project in file("lenses-cloudwatch-plugin"))
.disablePlugins(SbtGithubReleasePlugin)
.dependsOn(alertsPluginApi)
Expand Down
56 changes: 56 additions & 0 deletions lenses-mail-alerts-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
## Lenses alert mail plugin

This is a Lenses plugin allowing to send Lenses raised alerts by email.

### Configuration

In order to enable the plugin, the following settings have to be set in Lenses:

```
lenses.alerting.plugins = [
{
class=io.lenses.alerting.plugin.mail.MailAlertPlugin
config={
key1=value1
key2=value2
...
}
}
]
```

Available configuration options:

|Configuration | Type | Description |
|-----------------|--------|--------------------------------------------------------------------|
| from-address | String | The sender mail address. |
| email-addresses | String | Comma separated address strings in RFC822 format. |
| username | String | The user name. |
| password | String | The password. |
| smtp-host | String | Host name of the SMTP mail server. |
| smtp-port | String | Port of the SMTP mail server. |
| smtp-auth | String | If true, attempt to authenticate the user using the AUTH command. |
| smtp-starttls | String | If true, enables the use of the STARTTLS command. |



Google SMTP server example:

```
lenses.alerting.plugins = [
{
class=io.lenses.alerting.plugin.mail.MailAlertPlugin
config={
from-address="[email protected]"
email-addresses="[email protected], [email protected]"
username="user"
password="passwd" // Generate application-specific password as described here: https://support.google.com/mail/?p=InvalidSecondFactor
smtp-host="smtp.gmail.com"
smtp-port="587"
smtp-auth="true"
smtp-starttls="true"
}
}
]
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.lenses.alerting.plugin.mail

import java.util.Properties

case class MailAlertConfig(senderAddress: String,
emailAddresses: String,
user: String,
passwd: String,
mailProps: Properties)
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.lenses.alerting.plugin.mail

import java.util
import java.util.Properties

import io.lenses.alerting.plugin.javaapi.util.{Try => JTry}
import io.lenses.alerting.plugin.javaapi.{AlertingPlugin, AlertingService, ConfigEntry}
import io.lenses.alerting.plugin.mail.MailAlertsPlugin._
import io.lenses.alerting.plugin.mail.TryUtils.TryExtension

import scala.collection.JavaConverters._
import scala.util.Try

class MailAlertPlugin extends AlertingPlugin with Metadata {

override val name: String = "Mail"

override val description: String = "Plugin to support pushing Lenses alerts as emails"

override def init(config: util.Map[String, String]): JTry[AlertingService] = Try {
val map = config.asScala
def getOrError(key: String): String = {
map.getOrElse(key, throw new IllegalArgumentException(s"Invalid configuration for Mail plugin'[$key]'"))
}

val senderAddress = getOrError(SENDER_ADDRESS)
val emailAddresses = getOrError(EMAIL_ADDRESSES)
val username = getOrError(USERNAME)
val password = getOrError(PASSWORD)

def buildMailProperties = {
val smtpHost = getOrError(SMTP_HOST)
val smtpPort = getOrError(SMTP_PORT)
val smtpAuth = getOrError(SMTP_AUTH)
val smtpStartTLS = getOrError(SMTP_STARTTLS)

// https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html
val props = new Properties()
props.put("mail.smtp.host", smtpHost)
props.put("mail.smtp.port", smtpPort)
props.put("mail.smtp.auth", smtpAuth)
props.put("mail.smtp.starttls.enable", smtpStartTLS)
props
}

val props: Properties = buildMailProperties
val as: AlertingService = new MailAlertService(name, description, MailAlertConfig(senderAddress, emailAddresses, username, password, props))
as
}.asJava

override def configKeys(): util.List[ConfigEntry] = {
import scala.collection.JavaConverters._
List(
new ConfigEntry(SENDER_ADDRESS, "The sender mail address"),
new ConfigEntry(EMAIL_ADDRESSES,"Comma separated address strings in RFC822 format"),
new ConfigEntry(USERNAME,"Username"),
new ConfigEntry(PASSWORD,"Password"),
new ConfigEntry(SMTP_HOST ,"Host name of the SMTP mail server"),
new ConfigEntry(SMTP_PORT,"Port of the SMTP mail server"),
new ConfigEntry(SMTP_AUTH,"If true, attempt to authenticate the user using the AUTH command"),
new ConfigEntry(SMTP_STARTTLS,"If true, enables the use of the STARTTLS command"),
).asJava
}
}

object MailAlertsPlugin {
val SENDER_ADDRESS = "from-address"
val EMAIL_ADDRESSES = "email-addresses"
val USERNAME = "username"
val PASSWORD = "password"
val SMTP_HOST = "smtp-host"
val SMTP_PORT = "smtp-port"
val SMTP_AUTH = "smtp-auth"
val SMTP_STARTTLS = "smtp-starttls"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.lenses.alerting.plugin.mail

import java.sql.Timestamp
import java.util
import java.util.Date

import io.lenses.alerting.plugin.Alert
import io.lenses.alerting.plugin.javaapi.AlertingService
import io.lenses.alerting.plugin.javaapi.util.{Try => JTry}
import io.lenses.alerting.plugin.mail.TryUtils.TryExtension
import javax.mail.internet.{InternetAddress, MimeMessage}
import javax.mail.{Authenticator, Message, PasswordAuthentication, Session, Transport}

import scala.util.Try

class MailAlertService(override val name: String,
override val description: String,
config: MailAlertConfig) extends AlertingService with Metadata {

val session: Session = Session.getInstance(config.mailProps, new Authenticator() {
override protected def getPasswordAuthentication = new PasswordAuthentication(config.user, config.passwd)
})

override def displayedInformation(): util.Map[String, String] = {
import scala.collection.JavaConverters._
Map("Email Addresses" -> config.emailAddresses).asJava
}

override def publish(alert: Alert): JTry[Alert] = {
Try {
val message = new MimeMessage(session)
message.setFrom(new InternetAddress(config.senderAddress))
message.setRecipients(Message.RecipientType.TO, config.emailAddresses)
message.setSubject("Lenses Alert")
message.setSentDate(new Date())
message.setText(alertToText(alert))

Transport.send(message)
alert
}.asJava
}

private def alertToText(alert: Alert): String = {
val sb = new StringBuilder()
sb.append("AlertId: ").append(alert.alertId).append("\n")
sb.append("Category: ").append(alert.category).append("\n")
sb.append("Level: ").append(alert.level).append("\n")
sb.append("Timestamp: ").append(new Timestamp(alert.timestamp).toString).append("\n")
sb.append("Labels: ").append(alert.labels).append("\n")
sb.append("Tags: ").append(alert.tags).append("\n")
sb.append("Summary: ").append(alert.summary).append("\n")
alert.docs.ifPresent(docs => sb.append("Docs: ").append(docs).append("\n"))
sb.toString()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.lenses.alerting.plugin.mail

trait Metadata {
def name(): String = "Mail"
def description(): String = "Send an email for each Lenses alert."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.lenses.alerting.plugin.mail

import io.lenses.alerting.plugin.javaapi.util.{Failure => JFailure, Success => JSuccess, Try => JTry}

import scala.util.{Failure, Success, Try}

object TryUtils {

implicit class TryExtension[T](val t: Try[T]) extends AnyVal {
def asJava: JTry[T] = {
t match {
case Success(value) => new JSuccess[T](value)
case Failure(exception) => new JFailure[T](exception)
}
}
}

}