Skip to content

Commit

Permalink
Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
fanf committed Mar 24, 2023
1 parent 5403de6 commit fe6b0c5
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 44 deletions.
7 changes: 7 additions & 0 deletions change-validation/src/main/resources/change-validation.conf
@@ -1,3 +1,10 @@

# Properties to decide if the change validation plugins use supervised groups (default) or
# if all changes need to have change request. This feature does not override validated user one.
# Values: supervisedGroups , anyChanges
change.enable=supervisedGroups


# Email server configuration
smtp.hostServer=""
smtp.port=587
Expand Down
Expand Up @@ -67,6 +67,7 @@ import com.normation.plugins.changevalidation.WoValidatedUserJdbcRepository
import com.normation.plugins.changevalidation.WoValidatedUserRepository
import com.normation.plugins.changevalidation.WoWorkflowJdbcRepository
import com.normation.plugins.changevalidation.api.{ChangeRequestApi, ChangeRequestApiImpl, SupervisedTargetsApi, SupervisedTargetsApiImpl, ValidatedUserApiImpl}
import com.normation.plugins.changevalidation.CheckValidationKind
import com.normation.rudder.AuthorizationType
import com.normation.rudder.AuthorizationType.Deployer
import com.normation.rudder.AuthorizationType.Validator
Expand All @@ -86,14 +87,16 @@ import com.normation.rudder.services.workflows.NodeGroupChangeRequest
import com.normation.rudder.services.workflows.RuleChangeRequest
import com.normation.rudder.services.workflows.WorkflowLevelService
import com.normation.rudder.services.workflows.WorkflowService

import net.liftweb.common.Box
import net.liftweb.common.Full

import com.normation.box._
import com.normation.plugins.changevalidation.EmailNotificationService
import com.normation.plugins.changevalidation.NotificationService
import com.normation.plugins.changevalidation.RoValidatedUserRepository

import net.liftweb.common.EmptyBox
import net.liftweb.common.Failure

/*
* The validation workflow level
Expand Down Expand Up @@ -207,10 +210,12 @@ class ChangeValidationWorkflowLevelService(
*/
object ChangeValidationConf extends RudderPluginModule {

val configFilePath = "/opt/rudder/etc/plugins/change-validation.conf"

lazy val notificationService = new NotificationService(
new EmailNotificationService()
, RudderConfig.linkUtil
, "/opt/rudder/etc/plugins/change-validation.conf"
, configFilePath
)
// by build convention, we have only one of that on the classpath
lazy val pluginStatusService = new CheckRudderPluginEnableImpl(RudderConfig.nodeInfoService)
Expand Down Expand Up @@ -256,19 +261,19 @@ object ChangeValidationConf extends RudderPluginModule {
}


// other service instanciation / initialization
// other service instantiation / initialization
RudderConfig.workflowLevelService.overrideLevel(
new ChangeValidationWorkflowLevelService(
pluginStatusService
, RudderConfig.workflowLevelService.defaultWorkflowService
, validationWorkflowService
, Seq( new NodeGroupValidationNeeded(
, Seq( new CheckValidationKind(configFilePath, new NodeGroupValidationNeeded(
supervisedTargetRepo.load _
, roChangeRequestRepository
, RudderConfig.roRuleRepository
, RudderConfig.roNodeGroupRepository
, RudderConfig.nodeInfoService
)
))
)
, () => RudderConfig.configService.rudder_workflow_enabled().toBox
, roValidatedUserRepository
Expand Down
Expand Up @@ -9,6 +9,7 @@ import bootstrap.liftweb.FileSystemResource
import com.github.mustachejava.DefaultMustacheFactory
import com.github.mustachejava.MustacheFactory
import com.normation.NamedZioLogger

import com.normation.errors._
import com.normation.plugins.changevalidation.TwoValidationStepsWorkflowServiceImpl.Cancelled
import com.normation.plugins.changevalidation.TwoValidationStepsWorkflowServiceImpl.Deployed
Expand All @@ -17,18 +18,52 @@ import com.normation.plugins.changevalidation.TwoValidationStepsWorkflowServiceI
import com.normation.rudder.domain.workflows.ChangeRequest
import com.normation.rudder.domain.workflows.WorkflowNode
import com.normation.rudder.web.model.LinkUtil

import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import zio.ZIO

import zio.ZIO
import jakarta.mail.Session
import jakarta.mail._
import jakarta.mail.internet.InternetAddress
import jakarta.mail.internet.MimeMessage
import zio.syntax._

import zio.syntax._
import scala.jdk.CollectionConverters._

sealed trait ChangeEnableFor {
def name: String
}

object ReadConfigFile {
protected[changevalidation] def getConfig(path: String): IOResult[Config] = {
val file = new File(path)
IOResult.effectM {
for {
configResource <- if (file.exists && file.canRead) {
FileSystemResource(file).succeed
} else {
Inconsistency(s"Configuration file not found: ${file.getPath}").fail
}
} yield {
ConfigFactory.load(ConfigFactory.parseFile(configResource.file))
}
}
}
}

object ChangeEnableFor {

object SupervisedGroups extends ChangeEnableFor { val name = "supervisedGroups" }
object AnyChanges extends ChangeEnableFor { val name = "anyChanges" }

def all = ca.mrvisser.sealerate.values[ChangeEnableFor]

def parse(s: String): Option[ChangeEnableFor] = {
all.collectFirst { case x if(x.name == s) => x}
}
}

final case class Email(value: String)

final case class Username(value: String)
Expand Down Expand Up @@ -109,12 +144,16 @@ class EmailNotificationService {
}
}



class NotificationService(
emailService : EmailNotificationService
, linkUtil : LinkUtil
, configMailPath: String
emailService: EmailNotificationService
, linkUtil : LinkUtil
, configFile : String
) {

val logger = NamedZioLogger("plugin.change-validation")

// we want all our string to be trimmed
implicit class ConfigExtension(config: Config) {
def getTrimmedString(path: String) = config.getString(path).trim
Expand Down Expand Up @@ -142,15 +181,14 @@ class NotificationService(
}
}

val logger = NamedZioLogger("plugin.change-validation")

def sendNotification(step: WorkflowNode, cr: ChangeRequest): IOResult[Unit] = {
for {
serverConfig <- getSMTPConf(configMailPath)
serverConfig <- getSMTPConf(configFile)
_ <- ZIO.when(serverConfig.smtpHostServer.nonEmpty) {
for {
emailConf <- getStepMailConf(step, configMailPath)
rudderBaseUrl <- getRudderBaseUrl(configMailPath)
emailConf <- getStepMailConf(step, configFile)
rudderBaseUrl <- getRudderBaseUrl(configFile)
params = extractChangeRequestInfo(rudderBaseUrl, cr)
mf = new DefaultMustacheFactory()
emailBody <- getContentFromTemplate(mf, emailConf, params)
Expand All @@ -161,24 +199,9 @@ class NotificationService(
} yield ()
}

protected[changevalidation] def getConfig(path: String): IOResult[Config] = {
val file = new File(path)
IOResult.effectM {
for {
configResource <- if (file.exists && file.canRead) {
FileSystemResource(file).succeed
} else {
Inconsistency(s"Configuration file not found: ${file.getPath}").fail
}
} yield {
ConfigFactory.load(ConfigFactory.parseFile(configResource.file))
}
}
}

protected[changevalidation] def getRudderBaseUrl(path: String): IOResult[String] = {
for {
config <- getConfig(path)
config <- ReadConfigFile.getConfig(path)
rudderBaseUrl <- IOResult.effect(s"An error occurs while parsing RUDDER base url in ${path}"){
config.getTrimmedString("rudder.base.url")
}
Expand All @@ -187,7 +210,7 @@ class NotificationService(

protected[changevalidation] def getSMTPConf(path: String): IOResult[SMTPConf] = {
for {
config <- getConfig(path)
config <- ReadConfigFile.getConfig(path)
smtp <- IOResult.effect(s"An error occurs while parsing SMTP conf in ${path}") {
val hostServer = config.getTrimmedString("smtp.hostServer")
val port = config.getInt("smtp.port")
Expand All @@ -213,7 +236,7 @@ class NotificationService(

protected[changevalidation] def getStepMailConf(step: WorkflowNode, path: String): IOResult[EmailConf] = {
for {
config <- getConfig(path)
config <- ReadConfigFile.getConfig(path)
s <- step match {
case Validation => "validation".succeed
case Deployment => "deployment".succeed
Expand Down
Expand Up @@ -13,16 +13,15 @@ import com.normation.rudder.services.workflows.DirectiveChangeRequest
import com.normation.rudder.services.workflows.GlobalParamChangeRequest
import com.normation.rudder.services.workflows.NodeGroupChangeRequest
import com.normation.rudder.services.workflows.RuleChangeRequest
import com.normation.NamedZioLogger

import net.liftweb.common.Box
import net.liftweb.common.Full
import com.normation.box._

object bddMock {
val USER_AUTH_NEEDED = Map(
"admin" -> false,
"Jean" -> true
)
}
import com.normation.box._
import com.normation.errors.IOResult
import com.normation.zio._
import zio.syntax._

/**
* Check is an external validation is needed for the change, given some
Expand All @@ -38,6 +37,55 @@ trait ValidationNeeded {
def forGlobalParam(actor: EventActor, change: GlobalParamChangeRequest): Box[Boolean]
}

object CheckValidationKind {
val defaultChangeMode = ChangeEnableFor.SupervisedGroups
}
class CheckValidationKind(configFile: String, backend: ValidationNeeded) extends ValidationNeeded {
val logger = NamedZioLogger("plugin.change-validation")

val changeEnableFor = {
(
for {
c <- ReadConfigFile.getConfig(configFile)
v <- IOResult.effect(ChangeEnableFor.parse(c.getString("change.enable")).getOrElse(CheckValidationKind.defaultChangeMode)).
chainError(s"Error when reading configuration parameter 'change.enable' in '${configFile}', using " +
s"default value '${CheckValidationKind.defaultChangeMode.name}'"
)
_ <- logger.info(s"Change validation mode for supervised groups: '${v.name}' ")
} yield {
v
}
).catchAll(err => logger.info(err.fullMsg) *> CheckValidationKind.defaultChangeMode.succeed).runNow
}


def checkValidationMode(f: () => Box[Boolean]) = {
if (changeEnableFor == ChangeEnableFor.SupervisedGroups) {
logger.logEffect.debug(s"Change request follows supervised group logic")
f()
} else {
logger.logEffect.debug(s"Change request must be validation by configuration")
Full(true)
}
}

override def forRule(actor: EventActor, change: RuleChangeRequest): Box[Boolean] = {
checkValidationMode(() => backend.forRule(actor, change))
}

override def forDirective(actor: EventActor, change: DirectiveChangeRequest): Box[Boolean] = {
checkValidationMode(() => backend.forDirective(actor, change))
}

override def forNodeGroup(actor: EventActor, change: NodeGroupChangeRequest): Box[Boolean] = {
checkValidationMode(() => backend.forNodeGroup(actor, change))
}

override def forGlobalParam(actor: EventActor, change: GlobalParamChangeRequest): Box[Boolean] = {
checkValidationMode(() => backend.forGlobalParam(actor, change))
}
}

/*
* A version of the "validationNeeded" plugin which bases its oracle on a list
* of group. The list of group is used to mark nodes.
Expand All @@ -54,11 +102,11 @@ trait ValidationNeeded {
* Note that a validated user will always bypass this validation (see https://issues.rudder.io/issues/22188#note-5)
*/
class NodeGroupValidationNeeded(
monitoredTargets: () => Box[Set[SimpleTarget]]
, repos : RoChangeRequestRepository
, ruleLib : RoRuleRepository
, groupLib : RoNodeGroupRepository
, nodeInfoService : NodeInfoService
monitoredTargets : () => Box[Set[SimpleTarget]]
, repos : RoChangeRequestRepository
, ruleLib : RoRuleRepository
, groupLib : RoNodeGroupRepository
, nodeInfoService : NodeInfoService
) extends ValidationNeeded {

/*
Expand Down

0 comments on commit fe6b0c5

Please sign in to comment.