Skip to content

Commit

Permalink
Fixes #14870: Use ZIO for effect management in Rudder
Browse files Browse the repository at this point in the history
  • Loading branch information
fanf committed May 15, 2019
1 parent bdce155 commit e39c66a
Show file tree
Hide file tree
Showing 288 changed files with 11,230 additions and 9,818 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ target*
.worksheet
**/elm-stuff/*
**/generated/*
**/git-ignored/*
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@

package com.normation.inventory.domain

import net.liftweb.common._
import com.normation.errors._
import scalaz.zio._
import scalaz.zio.syntax._

/**
* The enumeration holding the values for the agent
Expand Down Expand Up @@ -131,15 +133,15 @@ object AgentType {

def allValues = ca.mrvisser.sealerate.values[AgentType]

def fromValue(value : String) : Box[AgentType] = {
def fromValue(value: String): Either[InventoryError.AgentType, AgentType] = {
// Check if the value is correct compared to the agent tag name (fusion > 2.3) or its toString value (added by CFEngine)
def checkValue( agent : AgentType) = {
agent.inventoryAgentNames.contains(value.toLowerCase)
}

allValues.find(checkValue) match {
case None => Failure(s"Wrong type of value for the agent '${value}'")
case Some(agent) => Full(agent)
allValues.find(checkValue) match {
case None => Left(InventoryError.AgentType(s"Wrong type of value for the agent '${value}'"))
case Some(agent) => Right(agent)
}
}
}
Expand Down Expand Up @@ -176,10 +178,10 @@ object AgentInfoSerialisation {
)
}

def parseSecurityToken(agentType : AgentType, tokenJson: JValue, tokenDefault : Option[String]) : Box[SecurityToken]= {
def parseSecurityToken(agentType : AgentType, tokenJson: JValue, tokenDefault : Option[String]) : Either[InventoryError.SecurityToken, SecurityToken]= {
import net.liftweb.json.compactRender

def extractValue(tokenJson : JValue) = {
def extractValue(tokenJson : JValue): Option[String] = {
tokenJson \ "value" match {
case JString(s) => Some(s)
case _ => None
Expand All @@ -190,31 +192,31 @@ object AgentInfoSerialisation {
agentType match {
case Dsc => tokenJson \ "type" match {
case JString(Certificate.kind) => extractValue(tokenJson) match {
case Some(token) => Full(Certificate(token))
case None => Failure(error(Certificate.kind))
case Some(token) => Right(Certificate(token))
case None => Left(InventoryError.SecurityToken(error(Certificate.kind)))
}
case JString(PublicKey.kind) => Failure("Cannot have a public Key for dsc agent, only a certificate is valid")
case JNothing => Failure(error(Certificate.kind))
case invalidJson => Failure(s"Invalid value for security token, ${compactRender(invalidJson)}")
case JString(PublicKey.kind) => Left(InventoryError.SecurityToken("Cannot have a public Key for dsc agent, only a certificate is valid"))
case JNothing => Left(InventoryError.SecurityToken(error(Certificate.kind)))
case invalidJson => Left(InventoryError.SecurityToken(s"Invalid value for security token, ${compactRender(invalidJson)}"))
}
case _ => tokenJson \ "type" match {
case JString(Certificate.kind) => extractValue(tokenJson) match {
case Some(token) => Full(Certificate(token))
case None => Failure(error(Certificate.kind))
case Some(token) => Right(Certificate(token))
case None => Left(InventoryError.SecurityToken(error(Certificate.kind)))
}
case JString(PublicKey.kind) => extractValue(tokenJson) match {
case Some(token) => Full(PublicKey(token))
case None => Failure(error(PublicKey.kind))
case Some(token) => Right(PublicKey(token))
case None => Left(InventoryError.SecurityToken(error(PublicKey.kind)))
}
case invalidJson =>
tokenDefault match {
case Some(default) => Full(PublicKey(default))
case Some(default) => Right(PublicKey(default))
case None =>
val error = invalidJson match {
case JNothing => "no value define for security token"
case x => compactRender(invalidJson)
}
Failure(s"Invalid value for security token: ${error}, and no public key were stored")
Left(InventoryError.SecurityToken(s"Invalid value for security token: ${error}, and no public key were stored"))
}
}
}
Expand All @@ -225,22 +227,22 @@ object AgentInfoSerialisation {
* but version isn't, and even if we don't parse it correctly, we
* successfully return an agent (without version).
*/
def parseJson(s: String, optToken : Option[String]): Box[AgentInfo] = {
def parseJson(s: String, optToken : Option[String]): IOResult[AgentInfo] = {
for {
json <- try { Full(parse(s)) } catch { case ex: Exception => Failure(s"Can not parse agent info: ${ex.getMessage }", Full(ex), Empty) }
json <- IO.effect { parse(s) } mapError { ex => InventoryError.Deserialisation(s"Can not parse agent info: ${ex.getMessage }", ex) }
agentType <- (json \ "agentType") match {
case JString(tpe) => AgentType.fromValue(tpe)
case JNothing => Failure("No value defined for security token")
case invalidJson => Failure(s"Error when trying to parse string as JSON Agent Info (missing required field 'agentType'): ${compactRender(invalidJson)}")
case JString(tpe) => AgentType.fromValue(tpe).toIO
case JNothing => InventoryError.SecurityToken("No value defined for security token").fail
case invalidJson => InventoryError.AgentType(s"Error when trying to parse string as JSON Agent Info (missing required field 'agentType'): ${compactRender(invalidJson)}").fail
}
agentVersion = json \ "version" match {
case JString(version) => Some(AgentVersion(version))
case _ => None
}
token <- json \ "securityToken" match {
token <- (json \ "securityToken" match {
case JObject(json) => parseSecurityToken(agentType, json, optToken)
case _ => parseSecurityToken(agentType, JNothing, optToken)
}
}).toIO

} yield {
AgentInfo(agentType, agentVersion, token)
Expand All @@ -252,19 +254,18 @@ object AgentInfoSerialisation {
* - try to parse in json: if ok, we have the new version
* - else, try to parse in old format, put None to version.
*/
def parseCompatNonJson(s: String, optToken : Option[String]): Box[AgentInfo] = {
parseJson(s, optToken) match {
case Full(info) => Full(info)
case eb: EmptyBox =>
val jsonError = eb ?~! "Error when parsing JSON information about the agent type."
def parseCompatNonJson(s: String, optToken : Option[String]): IOResult[AgentInfo] = {
parseJson(s, optToken).catchAll { eb =>

val jsonError = "Error when parsing JSON information about the agent type: " + eb.msg
for {
agentType <- AgentType.fromValue(s) ?~! (
s"Error when mapping '${s}' to an agent info. We are expecting either " +
s"an agentType with allowed values in ${AgentType.allValues.mkString(", ")}" +
s" or " +
s"a json like {'agentType': type, 'version': opt_version, 'securityToken': ...} but we get: ${jsonError.messageChain}"
)
token <- parseSecurityToken(agentType, JNothing, optToken)
agentType <- AgentType.fromValue(s).toIO.chainError(
s"Error when mapping '${s}' to an agent info. We are expecting either " +
s"an agentType with allowed values in ${AgentType.allValues.mkString(", ")}" +
s" or " +
s"a json like {'agentType': type, 'version': opt_version, 'securityToken': ...} but we get: ${jsonError}"
)
token <- IO.fromEither(parseSecurityToken(agentType, JNothing, optToken))
} yield {
AgentInfo(agentType, None, token)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ import com.normation.utils.Utils._
import com.normation.utils.HashcodeCaching
import org.bouncycastle.openssl.PEMParser
import java.io.StringReader

import com.normation.NamedZioLogger
import com.normation.errors._
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import net.liftweb.common._
import org.bouncycastle.cert.X509CertificateHolder
import scalaz.zio._
import scalaz.zio.syntax._

/**
* A file that contains all the simple data types, like Version,
Expand Down Expand Up @@ -97,16 +101,17 @@ final case class PublicKey(value : String) extends SecurityToken with HashcodeCa
|-----END RSA PUBLIC KEY-----""".stripMargin
}
}
def publicKey : Box[java.security.PublicKey] = {
try {
val reader = new PEMParser(new StringReader(key))
def publicKey : IOResult[java.security.PublicKey] = {
IO.effect {
new PEMParser(new StringReader(key))
}.mapError { ex =>
InventoryError.CryptoEx(s"Key '${key}' cannot be parsed as a public key", ex)
}.flatMap { reader =>
reader.readObject() match {
case a : SubjectPublicKeyInfo =>
Full(new JcaPEMKeyConverter().getPublicKey(a))
case _ => Failure(s"Key '${key}' cannot be parsed as a public key")
(new JcaPEMKeyConverter().getPublicKey(a)).succeed
case _ => InventoryError.Crypto(s"Key '${key}' cannot be parsed as a public key").fail
}
} catch {
case e:Exception => Failure(s"Key '${key}' cannot be parsed as a public key")
}
}

Expand All @@ -124,16 +129,23 @@ final case class Certificate(value : String) extends SecurityToken with Hashcode
|-----END CERTIFICATE-----""".stripMargin
}
}
def cert : Box[X509CertificateHolder] = {
try {
val reader = new PEMParser(new StringReader(key))
reader.readObject() match {
case a : X509CertificateHolder =>
Full(a)
case _ => Failure(s"Key '${key}' cannot be parsed as a valid certificate")
}
} catch {
case e:Exception => Failure(s"Key '${key}' cannot be parsed as a valid certificate")
def cert : IO[InventoryError, X509CertificateHolder] = {
for {
reader <- IO.effect {
new PEMParser(new StringReader(key))
} mapError { e =>
InventoryError.CryptoEx(s"Key '${key}' cannot be parsed as a valid certificate", e)
}
obj <- IO.effect(reader.readObject()).mapError { e =>
InventoryError.CryptoEx(s"Key '${key}' cannot be parsed as a valid certificate", e)
}
res <- obj match {
case a : X509CertificateHolder =>
a.succeed
case _ => InventoryError.Crypto(s"Key '${key}' cannot be parsed as a valid certificate").fail
}
} yield {
res
}
}

Expand Down Expand Up @@ -164,3 +176,24 @@ final class Version(val value:String) extends Comparable[Version] {
}

}


object InventoryLogger extends NamedZioLogger(){ val loggerName = "inventory-logger"}


sealed trait InventoryError extends RudderError

object InventoryError {

final case class Crypto(msg: String) extends InventoryError
final case class CryptoEx(hint: String, ex: Throwable) extends InventoryError {
def msg = hint + "; root exception was: " + ex.getMessage()
}
final case class AgentType(msg: String) extends InventoryError
final case class SecurityToken(msg: String) extends InventoryError
final case class Deserialisation(msg: String, ex: Throwable) extends InventoryError
final case class Inconsistency(msg: String) extends InventoryError
final case class System(msg: String) extends InventoryError
}


Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import java.net.InetAddress

import com.normation.utils.HashcodeCaching
import org.joda.time.DateTime
import net.liftweb.common._
import net.liftweb.json.JsonAST.JValue

sealed trait NodeElement {
Expand Down Expand Up @@ -344,11 +343,11 @@ final case class CustomProperty(
)

object KeyStatus {
def apply(value : String) : Box[KeyStatus] = {
def apply(value : String) : Either[InventoryError.SecurityToken, KeyStatus] = {
value match {
case CertifiedKey.value => Full(CertifiedKey)
case UndefinedKey.value => Full(UndefinedKey)
case _ => Failure(s"${value} is not a valid key status")
case CertifiedKey.value => Right(CertifiedKey)
case UndefinedKey.value => Right(UndefinedKey)
case _ => Left(InventoryError.SecurityToken(s"${value} is not a valid key status"))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@

package com.normation.inventory.services.core

import com.normation.errors._
import com.normation.inventory.domain._
import net.liftweb.common.Box

trait ReadOnlyMachineRepository {
/**
Expand All @@ -48,7 +48,7 @@ trait ReadOnlyMachineRepository {
* it should not. In that case, return the machine with the most
* prioritary status (Accepted > Pending > Removed)
*/
def get(id:MachineUuid) : Box[MachineInventory]
def get(id:MachineUuid) : IOResult[Option[MachineInventory]]

}

Expand All @@ -72,7 +72,7 @@ trait WriteOnlyMachineRepository[R] {
* that you will actually get a copy if there is already a machine
* with a different inventoryStatus).0
*/
def save(machine:MachineInventory) : Box[R]
def save(machine:MachineInventory) : IOResult[R]

/**
* Delete the corresponding machine. Does not fail if the machine does
Expand All @@ -81,7 +81,7 @@ trait WriteOnlyMachineRepository[R] {
* That method should take all action needed to assure that consistency
* is kept, especially deleting reference to that machine.
*/
def delete(id:MachineUuid) : Box[R]
def delete(id:MachineUuid) : IOResult[R]

/**
* Change the status of a machine.
Expand All @@ -91,7 +91,7 @@ trait WriteOnlyMachineRepository[R] {
* - changing the status of a machine should change all reference to the
* machine accordingly
*/
def move(id:MachineUuid, into : InventoryStatus) : Box[R]
def move(id:MachineUuid, into : InventoryStatus) : IOResult[R]
}

trait MachineRepository[R] extends ReadOnlyMachineRepository with WriteOnlyMachineRepository[R]
Expand All @@ -101,21 +101,21 @@ trait ReadOnlyFullInventoryRepository {
* Retrieve a full ServerAndMachine.
* TODO: allows to lazy-load some heavy parts, like software, machine elements, etc.
*/
def get(id:NodeId, inventoryStatus : InventoryStatus) : Box[FullInventory]
def get(id:NodeId) : Box[FullInventory]
def getMachineId(id:NodeId, inventoryStatus : InventoryStatus) : Box[(MachineUuid, InventoryStatus)]
def get(id:NodeId, inventoryStatus : InventoryStatus) : IOResult[Option[FullInventory]]
def get(id:NodeId) : IOResult[Option[FullInventory]]
def getMachineId(id:NodeId, inventoryStatus : InventoryStatus) : IOResult[Option[(MachineUuid, InventoryStatus)]]

def getAllInventories(inventoryStatus : InventoryStatus): Box[Map[NodeId, FullInventory]]
def getAllInventories(inventoryStatus : InventoryStatus): IOResult[Map[NodeId, FullInventory]]

def getAllNodeInventories(inventoryStatus : InventoryStatus): Box[Map[NodeId, NodeInventory]]
def getAllNodeInventories(inventoryStatus : InventoryStatus): IOResult[Map[NodeId, NodeInventory]]
}

trait WriteOnlyFullInventoryRepository[R] {
def save(serverAndMachine:FullInventory) : Box[R]
def delete(id:NodeId, inventoryStatus : InventoryStatus) : Box[R]
def move(id:NodeId, from: InventoryStatus, into : InventoryStatus) : Box[R]
def save(serverAndMachine:FullInventory) : IOResult[R]
def delete(id:NodeId, inventoryStatus : InventoryStatus) : IOResult[R]
def move(id:NodeId, from: InventoryStatus, into : InventoryStatus) : IOResult[R]

def moveNode(id:NodeId, from: InventoryStatus, into : InventoryStatus) : Box[R]
def moveNode(id:NodeId, from: InventoryStatus, into : InventoryStatus) : IOResult[R]
}

trait FullInventoryRepository[R] extends ReadOnlyFullInventoryRepository with WriteOnlyFullInventoryRepository[R]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@

package com.normation.inventory.services.core

import com.normation.inventory.domain.{NodeId, SoftwareUuid, Software, InventoryStatus}
import net.liftweb.common.Box
import com.normation.errors._
import com.normation.inventory.domain.{InventoryStatus, NodeId, Software, SoftwareUuid}

trait ReadOnlySoftwareDAO {
def getSoftware(ids:Seq[SoftwareUuid]) : Box[Seq[Software]]
def getSoftware(ids:Seq[SoftwareUuid]) : IOResult[Seq[Software]]

/**
* Return softwares for the node id, as efficiently
* as possible
*/
def getSoftwareByNode(nodeIds: Set[NodeId], status: InventoryStatus): Box[Map[NodeId, Seq[Software]]]
def getSoftwareByNode(nodeIds: Set[NodeId], status: InventoryStatus): IOResult[Map[NodeId, Seq[Software]]]
}
Loading

0 comments on commit e39c66a

Please sign in to comment.