Skip to content

Commit

Permalink
Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
ElaadF committed May 6, 2020
1 parent b0b812a commit bc08660
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 7 deletions.
10 changes: 10 additions & 0 deletions change-validation/pom-template.xml
Expand Up @@ -130,6 +130,16 @@
<groupId>com.normation.plugins</groupId>
<artifactId>plugins-common-private</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>com.funcit</groupId>
<artifactId>zio-email</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>
<repositories>
<repository>
Expand Down
Expand Up @@ -57,9 +57,11 @@ import net.liftweb.common.Loggable
import net.liftweb.common._
import org.joda.time.DateTime
import zio.interop.catz._
import zio._
import zio.syntax._

import scala.xml.Elem

import NotificationService._
class RoChangeRequestJdbcRepository(
doobie: Doobie
, mapper: ChangeRequestMapper
Expand Down Expand Up @@ -195,6 +197,7 @@ class WoChangeRequestJdbcRepository(
Failure(msg)
case Some(x) => Full(x)
}
_ <- sendEmail( "test", TwoValidationStepsWorkflowServiceImpl.Validation)
} yield {
cr
}
Expand Down
@@ -0,0 +1,166 @@
package com.normation.plugins.changevalidation

import java.util.Properties

import bootstrap.liftweb.RudderConfig
import bootstrap.liftweb.UserDetailList
import com.normation.plugins.changevalidation.TwoValidationStepsWorkflowServiceImpl.Deployment
import com.normation.plugins.changevalidation.TwoValidationStepsWorkflowServiceImpl.Validation
import com.normation.rudder.AuthorizationType
import com.normation.rudder.domain.workflows.WorkflowNode
import javax.mail.Session
import javax.mail._
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeMessage

import scala.io.Source

case class Email(value: String)

case class Username(value: String)

case class SMTPConf(
smtpHostServer : String
, port : Int
, email : Email
, login : Option[Username]
, password : Option[String]
)

case class EmailInfo(
state: WorkflowNode
, to: Set[Email]
, replyTo: Set[Email]
, cc: Set[Email]
, bcc: Set[Email]
, subject: String
, template: Option[String]

This comment has been minimized.

Copy link
@fanf

fanf May 6, 2020

I think that it should be mandatory. Well, actually: either you have a common section for email info and two templates (one for validation, one for deployement). In that case, EmailInfo doesn't, contain any template info.
Or you have one full set of email info for each step, and in that case template is mandatory

)

object NotificationService {

This comment has been minimized.

Copy link
@VinceMacBuche

VinceMacBuche May 6, 2020

You should separate reading configuration file in another Service / Object

Apart this reading the conf is mostly OK here, what was the problem ?

No you need to do like francois said: bc08660#r38980597

This comment has been minimized.

Copy link
@VinceMacBuche

VinceMacBuche May 6, 2020

I totally misread the code, and thought you used properties like we do in Rudder, please ignore my comment on mostly OK and see François Comment for more details

This comment has been minimized.

Copy link
@fanf

fanf May 6, 2020

Agree, I think.
The general scenario could (should?) be: each time a CR need validation (respectively deployment):

  • read / validate configuration for corresponding step (1 high level methods "get config for validation", that can fail, which read config file, check existance of param/type of data)
  • parse template file and replace interesting things in it (1 high level methods that read template, parse, change properties - a first implementation of parsing can just be regex on some token, like %%LINK%% or whatever). Of course can fail.
  • finally a method that take email config + filled template and try to send it (of course that can fail).

That scenario is the core logic of you service, it is in a for yield ZIO loop in your main service. If it fails, we need to log and be sad because notification can't be send.

Each of the step can have different implementation (at least, likely, one for test and one real - for ex sending email could be just writing in a file for test). So they need to have an interface with the interesting method, and implementation(s).


val path = "/vagrant/email.conf"
val config = getSMTPConf(path)
val (validationEmail, deploymentEmail) = getMailParameter(path)

//TO-DO : handle errors with ZIO, exceptions, Task ?
def sendEmail(text: String, state: WorkflowNode) = {
val prop = new Properties();
prop.put("mail.smtp.host", config.smtpHostServer);
prop.put("mail.smtp.port", config.port);
prop.put("mail.smtp.auth", "true");
prop.put("mail.smtp.starttls.enable", "true");
val session = (config.login, config.password) match {
case (Some(l), Some(p)) =>
val auth = new Authenticator() {
override protected def getPasswordAuthentication = new PasswordAuthentication(l.value, p)
}
Session.getInstance(prop, auth)
case (Some(_), None) =>
println("------ DEBUG : MISSING PASSWORD")
Session.getInstance(prop, null)
case (None, Some(_)) =>
println("------ DEBUG : MISSING LOGIN")
Session.getInstance(prop, null)
}
val message = new MimeMessage(session);
message.setFrom(new InternetAddress(config.email.value))
state match {
case Validation =>
message.setRecipients(
Message.RecipientType.TO,
validationEmail.to.map(_.value).mkString(",")
)
message.setSubject(validationEmail.subject);
case Deployment =>
message.setRecipients(
Message.RecipientType.TO,
deploymentEmail.to.map(_.value).mkString(",")
)
message.setSubject(deploymentEmail.subject);
}
message.setText(text);
Transport.send(message);

Some("toto")
}

// hostServer=smtp.gmail.com
// port=587
// auth=true
// email=elaad.f@gmail.com
// login=elaad.f@gmail.com
// password=toto
def getConfParameter(path: String) = {
Source.fromFile(path).getLines().toList.map {

This comment has been minimized.

Copy link
@fanf

fanf May 6, 2020

No, you need to use hocon methods, you really don't want to parse a config file by hand. It's a complex problem, with ton of edges cases (escaping chars, encoding, etc).
See for example: RudderProperties, it should be something like: ConfigFactory.load(ConfigFactory.parseFile(file))

line => {
val parameters = line.split("=")
(parameters.head, parameters.tail.head)
}
}.toMap
}

//TO-DO have to deal with the case where a parameter is not set (logger)
def getSMTPConf(path: String): SMTPConf = {
val config = getConfParameter(path)
SMTPConf(
config("hostServer")
, config("port").toInt
, Email(config("email"))
, if (config.contains("login")) Some(Username(config("login"))) else None
, if (config.contains("password")) Some(config("password")) else None
)
}

def getMailParameter(path: String) = {
val config = getConfParameter(path)
val validationInfo =
EmailInfo(
Validation
, config("validation-to").split(",").map(Email).toSet
, config("validation-reply-to").split(",").map(Email).toSet
, config("validation-cc").split(",").map(Email).toSet
, config("validation-bcc").split(",").map(Email).toSet
, config("validation-subject")
, if (config.contains("validation-template")) Some(config("validation-template")) else None
)

val deploymentInfo =
EmailInfo(
Validation
, config("deployment-to").split(",").map(Email).toSet
, config("deployment-reply-to").split(",").map(Email).toSet
, config("deployment-cc").split(",").map(Email).toSet
, config("deployment-bcc").split(",").map(Email).toSet
, config("deployment-subject")
, if (config.contains("deployment-template")) Some(config("deployment-template")) else None
)
(validationInfo, deploymentInfo)
}

// def readEmails(path: String): Map[Username, Email] = {
// Source.fromFile(path).getLines().toList.map {
// line => {
// val parameters = line.split("=")
// (Username(parameters.head), Email(parameters.tail.head))
// }
// }.toMap
//
// }

// def getValidators(allMailing: Map[Username, Email]): Map[Username, Email] = {
//
// val userDetails: UserDetailList = RudderConfig.rudderUserListProvider.authConfig
// val usersInFile = userDetails.users.keySet
// val usersWithRegisterEmails = allMailing.keySet.map(_.value)
// val existingUser = allMailing.filter {
// v =>
// usersInFile.intersect(usersWithRegisterEmails).contains(v._1.value)
// }
// val canValidate = existingUser.filter { x =>
// userDetails.users(x._1.value).authz.authorizationTypes.contains(AuthorizationType.Deployer.Write) ||
// userDetails.users(x._1.value).authz.authorizationTypes.contains(AuthorizationType.Validator.Write)
// }
// canValidate
// }
}
Expand Up @@ -72,6 +72,7 @@ import org.joda.time.DateTime

import scala.xml._
import com.normation.box._
import com.normation.rudder.domain.nodes.GenericPropertyUtils

object ChangeRequestChangesForm {
def form = ChooseTemplate(
Expand Down Expand Up @@ -653,9 +654,9 @@ class ChangeRequestChangesForm(
private[this] def displayGlobalParameter(param: GlobalParameter) = (
"#paramName" #> createGlobalParameterLink(param.name) &
"#name" #> param.name.value &
"#value" #> param.value &
"#description" #> param.description &
"#overridable" #> param.overridable
"#value" #> GenericPropertyUtils.serializeValue(param.value) &
"#description" #> param.description
// "#overridable" #> param.overridable
)(globalParameterXML)

This comment has been minimized.

Copy link
@fanf

fanf May 6, 2020

If you notice a bug in a plugin because of a change in rudder core: please, open a ticket, correct it.

This comment has been minimized.

Copy link
@ElaadF

ElaadF May 6, 2020

Author Owner

have been fixed here : Normation@c31c933


private[this] def displayGlobalParameterDiff (
Expand All @@ -664,9 +665,9 @@ class ChangeRequestChangesForm(
) = {
( "#paramName" #> createGlobalParameterLink(param.name) &
"#name" #> param.name.value &
"#value" #> displaySimpleDiff(diff.modValue,"value",Text(param.value)) &
"#description" #> displaySimpleDiff(diff.modDescription,"description",Text(param.description)) &
"#overridable" #> displaySimpleDiff(diff.modOverridable,"overridable",Text(param.overridable.toString))
"#value" #> displaySimpleDiff(diff.modValue,"value",Text( GenericPropertyUtils.serializeValue(param.value))) &
"#description" #> displaySimpleDiff(diff.modDescription,"description",Text(param.description))
// "#overridable" #> displaySimpleDiff(diff.modOverridable,"overridable",Text(param.overridable.toString))
) (globalParameterXML)
}

Expand Down

0 comments on commit bc08660

Please sign in to comment.