Skip to content

Commit

Permalink
Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
VinceMacBuche committed Apr 16, 2020
2 parents 8e395cb + 2addd2d commit 02e605c
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class CheckInventoryUpdate(
) {

val logger = ScheduledJobLoggerPure

ZIO.effect()
//start batch
if(updateInterval < 1.second) {
logger.logEffect.info(s"Disable automatic check for node inventories main information updates (update interval less than 1s)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,14 @@ import com.normation.errors.Unexpected
import cats.data.NonEmptyList
import com.normation.inventory.domain.Version
import com.normation.inventory.domain.AgentType
import com.normation.rudder.ncf
import com.normation.rudder.ncf.Constraint.Constraint
import com.normation.rudder.ncf.Constraint.CheckResult
import com.normation.rudder.ncf.ParameterType.ParameterTypeService
import net.liftweb.json.JField
import net.liftweb.json.JObject
import net.liftweb.json.JString
import net.liftweb.json.JValue

sealed trait NcfId {
def value : String
Expand Down Expand Up @@ -132,6 +138,7 @@ object ParameterType {

trait ParameterTypeService {
def create(value : String) : PureResult[ParameterType]
def value(parameterType: ParameterType) : PureResult[String]
def translate(value : String, paramType : ParameterType, agentType: AgentType) : PureResult[String]
}

Expand All @@ -145,6 +152,14 @@ object ParameterType {
}
}

def value(parameterType: ParameterType) = {
parameterType match {
case StringParameter => Right("string")
case Raw => Right("raw")
case HereString => Right("HereString")
case _ => Left(Unexpected(s"parameter type '${parameterType}' has no value defined"))
}
}
def translate(value : String, paramType : ParameterType, agentType: AgentType) : PureResult[String] = {
(paramType,agentType) match {
case (Raw,_) => Right(value)
Expand All @@ -170,6 +185,12 @@ object ParameterType {
case(service, _) => service.create(value)
}
}
def value(parameterType: ParameterType) = {
(innerServices foldRight (Left(Unexpected(s"parameter type '${parameterType}' has no value defined")) : PureResult[String])) {
case(_, res @ Right(_)) => res
case(service, _) => service.value(parameterType)
}
}

def translate(value : String, paramType : ParameterType, agentType: AgentType) : PureResult[String] = {
(innerServices foldRight (Left(Unexpected(s"'${value}' is not a valid method parameter type")) : PureResult[String])) {
Expand All @@ -191,19 +212,19 @@ object Constraint {
}
import NonEmptyList.one

case object NonEmpty extends Constraint {
case class AllowEmpty(allow : Boolean) extends Constraint {
def check(value: String): CheckResult = {
if (value.nonEmpty) {
if (allow || value.nonEmpty) {
OK
} else {
NOK(one("Must not be empty"))
}
}
}

case object NoWhiteSpace extends Constraint {
case class AllowWhiteSpace(allow: Boolean) extends Constraint {
def check(value: String): CheckResult = {
if (Pattern.compile("""^(?!\s).*(?<!\s)$""", Pattern.DOTALL).asPredicate().test(value)) {
if (allow || Pattern.compile("""^(?!\s).*(?<!\s)$""", Pattern.DOTALL).asPredicate().test(value)) {
OK
} else {
NOK(one("Must not have leading or trailing whitespaces"))
Expand Down Expand Up @@ -274,3 +295,93 @@ object CheckConstraint {
}
}
}

class TechniqueSerializer(parameterTypeService: ParameterTypeService) {

import net.liftweb.json.JsonDSL._
def serializeTechniqueMetadata(technique : ncf.Technique) : JValue = {

def serializeTechniqueParameter(parameter : TechniqueParameter) : JValue = {
( ( "id" -> parameter.id.value )
~ ( "name" -> parameter.name.value )
)
}
def serializeMethodCall(call : MethodCall) : JValue = {
val params : JValue = call.parameters.map {
case (methodId, value) =>
( ( "methodId" -> methodId.value )
~ ( "value" -> value )
)
}

( ( "method_name" -> call.methodId.value )
~ ( "class_context" -> call.condition )
~ ( "component" -> call.component )
~ ( "args" -> call.parameters.values )
~ ( "parameters" -> params )
)
}

def serializeResource(resourceFile: ResourceFile) = {
( ( "name" -> resourceFile.path )
~ ( "state" -> resourceFile.state.value )
)
}

val resource = technique.ressources.map(serializeResource)
val parameters = technique.parameters.map(serializeTechniqueParameter).toList
val calls = technique.methodCalls.map(serializeMethodCall).toList
( ( "bundle_name" -> technique.bundleName.value )
~ ( "version" -> technique.version.value )
~ ( "category" -> technique.category )
~ ( "description" -> technique.description )
~ ( "name" -> technique.name )
~ ( "method_calls" -> calls )
~ ( "parameter" -> parameters )
~ ( "resources" -> resource )
)
}


def serializeMethodMetadata(method : GenericMethod) : JValue = {
def serializeMethodParameter(param: MethodParameter) : JValue = {

def serializeMethodConstraint(constraint: ncf.Constraint.Constraint) : JField = {
constraint match {
case ncf.Constraint.AllowEmpty(allow) => JField("allow_empty_string", allow)
case ncf.Constraint.AllowWhiteSpace(allow) => JField("allow_whitespace_string", allow)
case ncf.Constraint.MaxLength(max) => JField("max_length", max)
case ncf.Constraint.MinLength(min) => JField("min_length", min)
case ncf.Constraint.MatchRegex(re) => JField("regex", re)
case ncf.Constraint.NotMatchRegex(re) => JField("not_regex", re)
case ncf.Constraint.FromList(list) => JField("select", list)
}
}
val constraints = JObject(param.constraint.map(serializeMethodConstraint))
val paramType = JString(parameterTypeService.value(param.parameterType).getOrElse("Unknown"))
( ( "name" -> param.id.value )
~ ( "description" -> param.description )
~ ( "constraints" -> constraints )
~ ( "type" -> paramType )
)
}
def serializeAgentSupport(agent : AgentType) = {
agent match {
case AgentType.Dsc => JString("dsc")
case AgentType.CfeCommunity | AgentType.CfeEnterprise => JString("cfengine-community")
}
}
val parameters = method.parameters.map(serializeMethodParameter)
val agentSupport = method.agentSupport.map(serializeAgentSupport)
( ( "bundle_name" -> method.id.value )
~ ( "name" -> method.name )
~ ( "description" -> method.description )
~ ( "class_prefix" -> method.classPrefix )
~ ( "class_parameter" -> method.classParameter.value )
~ ( "agent_support" -> agentSupport )
~ ( "parameter" -> parameters )
)

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class TechniqueWriter (
, xmlPrettyPrinter : RudderPrettyPrinter
, basePath : String
, parameterTypeService: ParameterTypeService
, techniqueSerializer: TechniqueSerializer
) {

private[this] val agentSpecific = new ClassicTechniqueWriter(basePath, parameterTypeService) :: new DSCTechniqueWriter(basePath, translater, parameterTypeService) :: Nil
Expand Down Expand Up @@ -276,6 +277,7 @@ class TechniqueWriter (
for {
agentFiles <- writeAgentFiles(technique, methods, modId, committer)
metadata <- writeMetadata(technique, methods, modId, committer)
json <- writeJson(technique)
commit <- archiver.commitTechnique(technique,metadata +: agentFiles, modId, committer, s"Committing technique ${technique.name}")
} yield {
metadata +: agentFiles
Expand Down Expand Up @@ -309,6 +311,23 @@ class TechniqueWriter (
}


def writeJson(technique: Technique) = {
val metadataPath = s"techniques/${technique.category}/${technique.bundleName.value}/${technique.version.value}/technique.json"

val path = s"${basePath}/${metadataPath}"

val content = techniqueSerializer.serializeTechniqueMetadata(technique)
for {

_ <- IOResult.effect(s"An error occurred while creating json file for Technique '${technique.name}'") {
implicit val charSet = StandardCharsets.UTF_8
val file = File (path).createFileIfNotExists (true)
file.write (net.liftweb.json.prettyRender(content))
}
} yield {
metadataPath
}
}
}

trait AgentSpecificTechniqueWriter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,15 @@ class TestTechniqueWriter extends Specification with ContentMatchers with Loggab
override def getAllCategories: Map[TechniqueCategoryId, TechniqueCategory] = ???
}


val valueCompiler = new InterpolatedValueCompilerImpl
val parameterTypeService : PlugableParameterTypeService = new PlugableParameterTypeService
val writer = new TechniqueWriter(TestTechniqueArchiver,TestLibUpdater,valueCompiler, readDirectives, techRepo, workflowLevelService, new RudderPrettyPrinter(Int.MaxValue, 2), basePath, parameterTypeService)
val writer = new TechniqueWriter(TestTechniqueArchiver,TestLibUpdater,valueCompiler, readDirectives, techRepo, workflowLevelService, new RudderPrettyPrinter(Int.MaxValue, 2), basePath, parameterTypeService, new TechniqueSerializer(parameterTypeService) )
val dscWriter = new DSCTechniqueWriter(basePath, valueCompiler, new ParameterType.PlugableParameterTypeService)
val classicWriter = new ClassicTechniqueWriter(basePath, new ParameterType.PlugableParameterTypeService)

import ParameterType._
val defaultConstraint = Constraint.NonEmpty :: Constraint.NoWhiteSpace :: Constraint.MaxLength(16384) :: Nil
val defaultConstraint = Constraint.AllowEmpty(false) :: Constraint.AllowWhiteSpace(false) :: Constraint.MaxLength(16384) :: Nil
val methods = ( GenericMethod(
BundleName("package_install_version")
, "Package install version"
Expand Down Expand Up @@ -419,12 +420,12 @@ class TestTechniqueWriter extends Specification with ContentMatchers with Loggab
val value5 = "sdfsqdfsqfsdf sfhdskjhdfs jkhsdkfjhksqdhf"
val value6 = ""

Constraint.NoWhiteSpace.check(value1) must equalTo(Constraint.OK)
Constraint.NoWhiteSpace.check(value2) must equalTo(Constraint.OK)
Constraint.NoWhiteSpace.check(value3) must equalTo(Constraint.OK)
Constraint.NoWhiteSpace.check(value4) must equalTo(Constraint.OK)
Constraint.NoWhiteSpace.check(value5) must equalTo(Constraint.OK)
Constraint.NoWhiteSpace.check(value6) must equalTo(Constraint.OK)
Constraint.AllowWhiteSpace(false).check(value1) must equalTo(Constraint.OK)
Constraint.AllowWhiteSpace(false).check(value2) must equalTo(Constraint.OK)
Constraint.AllowWhiteSpace(false).check(value3) must equalTo(Constraint.OK)
Constraint.AllowWhiteSpace(false).check(value4) must equalTo(Constraint.OK)
Constraint.AllowWhiteSpace(false).check(value5) must equalTo(Constraint.OK)
Constraint.AllowWhiteSpace(false).check(value6) must equalTo(Constraint.OK)
}

"Correctly refuse text starting or ending with withspace" in {
Expand All @@ -435,10 +436,10 @@ class TestTechniqueWriter extends Specification with ContentMatchers with Loggab
val value3 = " "
val value4 = "sdfsqdfsqfsdf sfhdskjhdfs jkhsdkfjhksqdhf "

Constraint.NoWhiteSpace.check(value1) must haveClass[Constraint.NOK]
Constraint.NoWhiteSpace.check(value2) must haveClass[Constraint.NOK]
Constraint.NoWhiteSpace.check(value3) must haveClass[Constraint.NOK]
Constraint.NoWhiteSpace.check(value4) must haveClass[Constraint.NOK]
Constraint.AllowWhiteSpace(false).check(value1) must haveClass[Constraint.NOK]
Constraint.AllowWhiteSpace(false).check(value2) must haveClass[Constraint.NOK]
Constraint.AllowWhiteSpace(false).check(value3) must haveClass[Constraint.NOK]
Constraint.AllowWhiteSpace(false).check(value4) must haveClass[Constraint.NOK]
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.normation.rudder.ncf

import better.files.File
import com.normation.errors.IOResult
import com.normation.errors.Inconsistancy
import com.normation.rudder.rest.RestExtractorService
import net.liftweb.common.EmptyBox
import net.liftweb.common.Full
import net.liftweb.json.JsonAST.JArray
import net.liftweb.json.JsonAST.JObject
import net.liftweb.json.parse
import zio.ZIO

class TechniqueReader(
restExtractor : RestExtractorService
) {


import zio.syntax._
import better.files.File._
val configuration_repository = root / "var" / "rudder" / "configuration-repository"
val methodsFile = configuration_repository / "ncf" / "generic_methods.json"
def getAllTechniqueFiles(currentPath : File): List[File]= {
val (subdirs, files) = currentPath.children.partition(_.isDirectory)
val techniqueFilePath = currentPath / "technique.json"
val checkSubdirs = subdirs.flatMap(getAllTechniqueFiles).toList
if (techniqueFilePath.exists) {
techniqueFilePath :: checkSubdirs
} else {
checkSubdirs
}
}
def readTechniquesMetadataFile: IOResult[List[Technique]] = {
for {
methods <- readMethodsMetadataFile
techniques <- ZIO.foreach(getAllTechniqueFiles(configuration_repository / "techniques"))(
techniqueFile =>
restExtractor.extractNcfTechnique(parse(techniqueFile.contentAsString), methods, false) match {
case Full(m) => m.succeed
case eb: EmptyBox =>
val fail = eb ?~! s"An Error occured while extracting data from techniques ncf API"
Inconsistancy(fail.messageChain).fail
})
} yield {
techniques
}
}

def readMethodsMetadataFile : IOResult[Map[BundleName, GenericMethod]] = {
for {
genericMethodContent <- IOResult.effect(s"error while reading ${methodsFile.pathAsString}")(methodsFile.contentAsString)
methods <- parse(genericMethodContent) match {
case JObject(fields) =>
restExtractor.extractGenericMethod(JArray(fields.map(_.value))).map(_.map(m => (m.id, m)).toMap) match {
case Full(m) => m.succeed
case eb: EmptyBox =>
val fail = eb ?~! s"An Error occured while extracting data from generic methods ncf API"
Inconsistancy(fail.messageChain).fail
}
case a => Inconsistancy(s"Could not extract methods from ncf api, expecting an object got: ${a}").fail
}
} yield {
methods
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,21 @@ object NcfApi extends ApiModuleProvider[NcfApi] {
val (action, path) = GET / "techniques" / "{techniqueId}" / "{techniqueVersion}" / "resources"
}
final case object GetNewResources extends NcfApi with TwoParam with StartsAtVersion15 with SortIndex { val z = implicitly[Line].value
val description = "Get ressources of a new technique"
val description = "Get resources of a new technique"
val (action, path) = GET / "techniques" / "{techniqueId}" / "new" / "{techniqueVersion}" / "resources"
}
final case object ParameterCheck extends NcfApi with ZeroParam with StartsAtVersion15 with SortIndex { val z = implicitly[Line].value
val description = "Get currently deployed resources of a technique"
val (action, path) = POST / "techniques" / "parameter" / "check"
}
final case object GetTechniques extends NcfApi with ZeroParam with StartsAtVersion15 with SortIndex { val z = implicitly[Line].value
val description = "Get all Techniques metadata"
val (action, path) = GET / "ncf" / "techniques"
}
final case object GetMethods extends NcfApi with ZeroParam with StartsAtVersion15 with SortIndex { val z = implicitly[Line].value
val description = "Get all methods metadata"
val (action, path) = GET / "ncf" / "methods"
}

def endpoints = ca.mrvisser.sealerate.values[NcfApi].toList.sortBy( _.z )
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ import com.normation.rudder.rule.category.RuleCategory
import com.normation.rudder.rule.category.RuleCategoryId
import com.normation.rudder.repository.FullNodeGroupCategory
import com.normation.rudder.repository.FullActiveTechnique

import com.normation.rudder.api.ApiAccount
import net.liftweb.json.JsonDSL._
import com.normation.rudder.web.components.DateFormaterService
Expand Down Expand Up @@ -99,8 +98,8 @@ trait RestDataSerializer {
}

final case class RestDataSerializerImpl (
readTechnique : TechniqueRepository
, diffService : DiffService
readTechnique : TechniqueRepository
, diffService : DiffService
) extends RestDataSerializer with Loggable {

private[this] def serializeMachineType(machine: Option[MachineType]): JValue = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1168,9 +1168,9 @@ final case class RestExtractorService (
select <- OptionnalJson.extractJsonListString(json, "select")

} yield {
( if (allowEmpty) Nil else NonEmpty :: Nil) :::
( if (allowWS) Nil else NoWhiteSpace :: Nil) :::
( MaxLength(maxLength) ::
( AllowEmpty(allowEmpty) ::
AllowWhiteSpace(allowWS) ::
MaxLength(maxLength) ::
minLength.map(MinLength).toList :::
regex.map(MatchRegex).toList :::
notRegex.map(NotMatchRegex).toList :::
Expand Down
Loading

0 comments on commit 02e605c

Please sign in to comment.