Skip to content

Commit

Permalink
Merge branch 'ust_22150/api_for_directive_compliance_pr'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins CI committed Jan 9, 2023
2 parents 2c6f017 + b3a987c commit 98490c5
Show file tree
Hide file tree
Showing 5 changed files with 331 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ package com.normation.rudder.services.reports
import com.normation.errors.IOResult
import com.normation.inventory.domain.NodeId
import com.normation.rudder.domain.logger.TimingDebugLogger
import com.normation.rudder.domain.policies.Directive
import com.normation.rudder.domain.policies.DirectiveId
import com.normation.rudder.domain.policies.RuleId
import com.normation.rudder.domain.reports.ComplianceLevel
import com.normation.rudder.domain.reports.NodeStatusReport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ object ComplianceApi extends ApiModuleProvider[ComplianceApi] {
val dataContainer = Some("globalCompliance")
}

final case object GetDirectiveComplianceId extends ComplianceApi with OneParam with StartsAtVersion10 with SortIndex {
val z = implicitly[Line].value
val description = "Get a directive's compliance"
val (action, path) = GET / "compliance" / "directives" / "{id}"
val dataContainer = Some("directiveCompliance")
}

def endpoints = ca.mrvisser.sealerate.values[ComplianceApi].toList.sortBy(_.z)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.normation.rudder.Role
import com.normation.rudder.api.{ApiAuthorization => ApiAuthz}
import com.normation.rudder.api.AclPathSegment
import com.normation.rudder.api.ApiAclElement
import com.normation.rudder.rest

/*
* The goal of that class is to map Authorization to what API
Expand Down Expand Up @@ -122,7 +123,7 @@ object AuthorizationApiMapping {
case Compliance.Read =>
ComplianceApi.GetGlobalCompliance.x :: ComplianceApi.GetRulesCompliance.x :: ComplianceApi.GetRulesComplianceId.x ::
ComplianceApi.GetNodesCompliance.x :: ComplianceApi.GetNodeComplianceId.x :: ChangesApi.GetRuleRepairedReports.x ::
ChangesApi.GetRecentChanges.x :: Nil
ChangesApi.GetRecentChanges.x :: ComplianceApi.GetDirectiveComplianceId.x :: Nil
case Compliance.Write => Nil
case Compliance.Edit => Nil

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,49 @@ final case class ByRuleRuleCompliance(
lazy val nodes = ByRuleByNodeCompliance.fromDirective(directives).toSeq
}

final case class ByDirectiveRuleCompliance(
id: DirectiveId,
name: String,
compliance: ComplianceLevel,
mode: ComplianceModeName,
rules: Seq[ByDirectiveByRuleComponentCompliance],
nodes: Seq[ByDirectiveNodeCompliance]
)

final case class ByDirectiveByRuleComponentCompliance(
id: RuleId,
name: String,
compliance: ComplianceLevel,
components: Seq[ByRuleComponentCompliance]
)

final case class ByDirectiveByRuleByComponentNodeCompliance(
name: String,
compliance: ComplianceLevel,
nodes: Seq[ByRuleNodeCompliance]
) extends ByRuleComponentCompliance

final case class ByDirectiveNodeCompliance(
id: NodeId,
name: String,
compliance: ComplianceLevel,
mode: ComplianceModeName,
rules: Seq[ByDirectiveByNodeRuleCompliance]
)

final case class ByDirectiveByNodeRuleCompliance(
id: RuleId,
name: String,
compliance: ComplianceLevel,
components: Seq[ByRuleComponentCompliance]
)

final case class ByDirectiveByNodeByRuleComponentCompliance(
name: String,
compliance: ComplianceLevel,
values: Seq[ComponentValueStatusReport]
) extends ByRuleComponentCompliance

final case class ByRuleDirectiveCompliance(
id: DirectiveId,
name: String,
Expand Down Expand Up @@ -301,6 +344,167 @@ object JsonCompliance {
}
}

implicit class JsonbyDirectiveCompliance(val directive: ByDirectiveRuleCompliance) extends AnyVal {
def toJsonV6 = (
("id" -> directive.id.serialize)
~ ("name" -> directive.name)
~ ("compliance" -> directive.compliance.complianceWithoutPending())
~ ("complianceDetails" -> percents(directive.compliance, CompliancePrecision.Level2))
~ ("rules" -> rules(directive.rules, 10, CompliancePrecision.Level2))
~ ("rules" -> byNodes(directive.nodes, 10, CompliancePrecision.Level2))
)

def toJson(level: Int, precision: CompliancePrecision) = (
("id" -> directive.id.serialize)
~ ("name" -> directive.name)
~ ("compliance" -> directive.compliance.complianceWithoutPending(precision))
~ ("mode" -> directive.mode.name)
~ ("complianceDetails" -> percents(directive.compliance, precision))
~ ("rules" -> rules(directive.rules, level, precision))
~ ("nodes" -> byNodes(directive.nodes, level, precision))
)

private[this] def byNodes(
nodes: Seq[ByDirectiveNodeCompliance],
level: Int,
precision: CompliancePrecision
): Option[JsonAST.JValue] = {
if (level < 2) None
else {
Some(nodes.map { node =>
(
("id" -> node.id.value)
~ ("name" -> node.name)
~ ("compliance" -> node.compliance.complianceWithoutPending(precision))
~ ("complianceDetails" -> percents(node.compliance, precision))
~ ("rules" -> byRule(node.rules, level, precision))
)
})
}
}

private[this] def byRule(
rules: Seq[ByDirectiveByNodeRuleCompliance],
level: Int,
precision: CompliancePrecision
): Option[JsonAST.JValue] = {
if (level < 3) None
else {
Some(rules.map { rule =>
(
("id" -> rule.id.uid.value)
~ ("name" -> rule.name)
~ ("compliance" -> rule.compliance.complianceWithoutPending(precision))
~ ("complianceDetails" -> percents(rule.compliance, precision))
~ ("components" -> byNodeByComponents(rule.components, level, precision))
)
})
}
}

private[this] def byNodeByComponents(
comps: Seq[ByRuleComponentCompliance],
level: Int,
precision: CompliancePrecision
): Option[JsonAST.JValue] = {
if (level < 3) None
else {
Some(comps.map { component =>
(
("name" -> component.name)
~ ("compliance" -> component.compliance.complianceWithoutPending(precision))
~ ("complianceDetails" -> percents(component.compliance, precision))
~ (component match {
case component: ByRuleBlockCompliance => // TODO: this case should not happened because we are only get nodes compliance here
("components" -> byNodeByComponents(component.subComponents, level, precision))
case component: ByDirectiveByNodeByRuleComponentCompliance =>
("values" -> values(component.values, level))
})
)
})
}
}

private[this] def rules(
rules: Seq[ByDirectiveByRuleComponentCompliance],
level: Int,
precision: CompliancePrecision
): Option[JsonAST.JValue] = {
if (level < 2) None
else {
Some(rules.map { rule =>
(
("id" -> rule.id.uid.value)
~ ("name" -> rule.name)
~ ("compliance" -> rule.compliance.complianceWithoutPending(precision))
~ ("complianceDetails" -> percents(rule.compliance, precision))
~ ("components" -> components(rule.components, level, precision))
)
})
}
}

private[this] def components(
comps: Seq[ByRuleComponentCompliance],
level: Int,
precision: CompliancePrecision
): Option[JsonAST.JValue] = {
if (level < 3) None
else {
Some(comps.map { component =>
(
("name" -> component.name)
~ ("compliance" -> component.compliance.complianceWithoutPending(precision))
~ ("complianceDetails" -> percents(component.compliance, precision))
~ (component match {
case component: ByRuleBlockCompliance => // TODO: this case should not happened because we are only get nodes compliance here
("components" -> components(component.subComponents, level, precision))
case component: ByRuleValueCompliance =>
("nodes" -> nodes(component.nodes, level, precision))
})
)
})
}
}

def values(values: Seq[ComponentValueStatusReport], level: Int): Option[JsonAST.JValue] = {
if (level < 5) None
else {
Some(values.map { value =>
(
("value" -> value.componentValue)
~ ("reports" -> value.messages.map { report =>
(
("status" -> statusDisplayName(report.reportType))
~ ("message" -> report.message)
)
})
)
})
}
}
private[this] def nodes(
nodes: Seq[ByRuleNodeCompliance],
level: Int,
precision: CompliancePrecision
): Option[JsonAST.JValue] = {
if (level < 4) None
else {
Some(nodes.map { node =>
(
("id" -> node.id.value)
~ ("name" -> node.name)
~ ("compliance" -> node.compliance.complianceWithoutPending(precision))
~ ("complianceDetails" -> percents(node.compliance, precision))
~ ("values" -> values(node.values, level))
)
})
}

}

}

implicit class JsonbyRuleCompliance(val rule: ByRuleRuleCompliance) extends AnyVal {
def toJsonV6 = (
("id" -> rule.id.serialize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,12 @@ class ComplianceApi(
API.endpoints
.map(e => {
e match {
case API.GetRulesCompliance => GetRules
case API.GetRulesComplianceId => GetRuleId
case API.GetNodesCompliance => GetNodes
case API.GetNodeComplianceId => GetNodeId
case API.GetGlobalCompliance => GetGlobal
case API.GetRulesCompliance => GetRules
case API.GetRulesComplianceId => GetRuleId
case API.GetNodesCompliance => GetNodes
case API.GetNodeComplianceId => GetNodeId
case API.GetGlobalCompliance => GetGlobal
case API.GetDirectiveComplianceId => GetDirectiveId
}
})
.toList
Expand Down Expand Up @@ -189,6 +190,55 @@ class ComplianceApi(
}
}

object GetDirectiveId extends LiftApiModule {
val schema = API.GetDirectiveComplianceId
val restExtractor = restExtractorService
def process(
version: ApiVersion,
path: ApiPath,
directiveId: String,
req: Req,
params: DefaultParams,
authzToken: AuthzToken
): LiftResponse = {
implicit val action = schema.name
implicit val prettify = params.prettify

(for {
level <- restExtractor.extractComplianceLevel(req.params)
t1 = System.currentTimeMillis
precision <- restExtractor.extractPercentPrecision(req.params)
id <- DirectiveId.parse(directiveId).toBox
t2 = System.currentTimeMillis

directive <- complianceService.getDirectiveCompliance(id, level)
t3 = System.currentTimeMillis
_ = TimingDebugLogger.trace(s"API GetDirectiveId - getting query param in ${t2 - t1} ms")
_ = TimingDebugLogger.trace(s"API GetDirectiveId - getting directive compliance in ${t3 - t2} ms")

} yield {
if (version.value <= 6) {
directive.toJsonV6
} else {
val json = directive.toJson(
level.getOrElse(10),
precision.getOrElse(CompliancePrecision.Level2)
) // by default, all details are displayed
val t4 = System.currentTimeMillis
TimingDebugLogger.trace(s"API GetDirectiveId - serialize to json in ${t4 - t3} ms")
json
}
}) match {
case Full(rule) =>
toJsonResponse(None, ("directiveCompliance" -> rule))

case eb: EmptyBox =>
val message = (eb ?~ (s"Could not get compliance for directive '${directiveId}'")).messageChain
toJsonError(None, JString(message))
}
}
}

object GetNodes extends LiftApiModule0 {
val schema = API.GetNodesCompliance
val restExtractor = restExtractorService
Expand Down Expand Up @@ -469,6 +519,67 @@ class ComplianceAPIService(
}
}.toBox

def getDirectiveCompliance(directiveId: DirectiveId, level: Option[Int]): Box[ByDirectiveRuleCompliance] = {
for {
rules <- rulesRepo.getAll()
allGroups <- nodeGroupRepo.getAllNodeIds()
allNodeInfos <- nodeInfoService.getAll()
directive <- directiveRepo.getDirective(directiveId.uid)
relevantRules = rules.filter(_.directiveIds.contains(directiveId))
relevantNodes = rules.map(rule => (rule, RoNodeGroupRepository.getNodeIds(allGroups, rule.targets, allNodeInfos)))

byRules <- getByRulesCompliance(relevantRules, level)
rules = byRules.flatMap { rule =>
rule.directives.map { directive =>
ByDirectiveByRuleComponentCompliance(
rule.id,
rule.name,
rule.compliance,
directive.components
)
}
}

byNodes <- getByNodesCompliance(None)

reportsNodes = byNodes.filter(n => relevantNodes.flatMap(_._2).contains(n.id))
nodes = reportsNodes.map { node =>
ByDirectiveNodeCompliance(
node.id,
node.name,
node.compliance,
node.mode,
node.nodeCompliances.map { rule =>
ByDirectiveByNodeRuleCompliance(
rule.id,
rule.name,
rule.compliance,
rule.directives.map { directive =>
ByDirectiveByNodeByRuleComponentCompliance(
directive.name,
directive.compliance,
directive.components.flatMap(_.componentValues)
)
}
)
}
)
}

compliance <- getGlobalComplianceMode().toIO

} yield {
ByDirectiveRuleCompliance(
directiveId,
directive.map(_.name).getOrElse("Unknown"),
ComplianceLevel.sum(byRules.map(_.compliance)),
compliance.mode,
rules,
nodes
)
}
}.toBox

def getRulesCompliance(level: Option[Int]): Box[Seq[ByRuleRuleCompliance]] = {
for {
rules <- rulesRepo.getAll()
Expand Down

0 comments on commit 98490c5

Please sign in to comment.