Skip to content

Commit

Permalink
Fixes #22525: Directives applied twice don't show in rule details (th…
Browse files Browse the repository at this point in the history
…ey should be skipped)
  • Loading branch information
clarktsiory authored and fanf committed Apr 17, 2024
1 parent b608506 commit c99e311
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 14 deletions.
Expand Up @@ -38,12 +38,15 @@
package com.normation.rudder.rest.data

import com.normation.inventory.domain.NodeId
import com.normation.rudder.domain.policies.Directive
import com.normation.rudder.domain.policies.DirectiveId
import com.normation.rudder.domain.policies.PolicyMode
import com.normation.rudder.domain.policies.Rule
import com.normation.rudder.domain.policies.RuleId
import com.normation.rudder.domain.reports.*
import com.normation.rudder.domain.reports.ComplianceLevel
import com.normation.rudder.reports.ComplianceModeName
import com.normation.rudder.repository.FullActiveTechnique
import java.lang
import net.liftweb.json.*
import net.liftweb.json.JsonAST
Expand Down Expand Up @@ -123,10 +126,11 @@ final case class ByDirectiveByNodeRuleCompliance(
)

final case class ByRuleDirectiveCompliance(
id: DirectiveId,
name: String,
compliance: ComplianceLevel,
components: Seq[ByRuleComponentCompliance]
id: DirectiveId,
name: String,
compliance: ComplianceLevel,
skippedDetails: Option[SkippedDetails],
components: Seq[ByRuleComponentCompliance]
)

sealed trait ByRuleComponentCompliance {
Expand Down Expand Up @@ -194,6 +198,44 @@ final case class ByRuleByNodeByDirectiveByValueCompliance(
values: Seq[ComponentValueStatusReport]
) extends ByRuleByNodeByDirectiveByComponentCompliance

final case class SkippedDetails(
overridingRuleId: RuleId,
overridingRuleName: String
)
final case class DirectiveComplianceOverride(
overridenRuleId: RuleId,
directiveId: DirectiveId,
directiveName: String,
overridingRuleId: RuleId
) {
def toComplianceByRule(rules: Map[RuleId, Rule]): ByRuleDirectiveCompliance = {
ByRuleDirectiveCompliance(
directiveId,
directiveName,
ComplianceLevel(),
Some(SkippedDetails(overridingRuleId, rules.get(overridingRuleId).map(_.name).getOrElse("unknown rule"))),
Seq.empty
)
}
}

object ComplianceOverrides {
def getOverridenDirective(
overrides: List[OverridenPolicy],
directives: Map[DirectiveId, (FullActiveTechnique, Directive)]
): List[DirectiveComplianceOverride] = {
val overridesData = for {
over <- overrides
(_, overridenDir) <- directives.get(over.policy.directiveId)
(_, overridingDir) <- directives.get(over.overridenBy.directiveId)
} yield {
DirectiveComplianceOverride(over.policy.ruleId, over.policy.directiveId, overridenDir.name, over.overridenBy.ruleId)

}
overridesData.toList
}
}

object GroupComponentCompliance {
// This function do the recursive treatment of components, we will have each time a pair of Sequence of tuple (NodeId , component compliance structure)
def recurseComponent(
Expand Down Expand Up @@ -644,6 +686,9 @@ object JsonCompliance {
~ ("name" -> directive.name)
~ ("compliance" -> directive.compliance.complianceWithoutPending(precision))
~ ("complianceDetails" -> percents(directive.compliance, precision))
~ ("skippedDetails" -> directive.skippedDetails.map(s =>
("overridingRuleId" -> s.overridingRuleId.serialize) ~ ("overridingRuleName" -> s.overridingRuleName)
))
~ ("components" -> components(directive.components, level, precision))
)
})
Expand Down
Expand Up @@ -66,6 +66,7 @@ import com.normation.rudder.rest.RestUtils.*
import com.normation.rudder.rest.data.*
import com.normation.rudder.services.nodes.NodeInfoService
import com.normation.rudder.services.reports.ReportingService
import com.normation.rudder.services.reports.ReportingServiceUtils
import com.normation.zio.currentTimeMillis
import net.liftweb.common.*
import net.liftweb.http.LiftResponse
Expand All @@ -74,6 +75,7 @@ import net.liftweb.http.Req
import net.liftweb.json.*
import net.liftweb.json.JsonDSL.*
import scala.collection.immutable
import zio.ZIO
import zio.syntax.*

class ComplianceApi(
Expand Down Expand Up @@ -559,12 +561,35 @@ class ComplianceAPIService(
t6 <- currentTimeMillis
_ <- TimingDebugLoggerPure.trace(s"getByRulesCompliance - findRuleNodeStatusReports in ${t6 - t5} ms")

reportsByRule = reportsByNode.flatMap { case (_, status) => status.reports }.groupBy(_.ruleId)
t7 = System.currentTimeMillis()
_ <- TimingDebugLoggerPure.trace(s"getByRulesCompliance - group reports by rules in ${t7 - t6} ms")

// make a map of directive overrides for each rule, to add to the directives of a rule
directiveOverridesByRules = ruleObjects.keys.map { ruleId =>
val overridenDirectives = ComplianceOverrides
.getOverridenDirective(
ReportingServiceUtils.buildRuleStatusReport(ruleId, reportsByNode).overrides,
directives
)
ruleId -> overridenDirectives
}.toMap

// we need to fetch info for rules pulled from overriden directives of our rules
allRuleObjects <-
ZIO
.foreach(
directiveOverridesByRules.values.toList
.flatMap(_.map(_.overridingRuleId))
)(rulesRepo.getOpt(_))
.map(rules => ruleObjects ++ rules.flatten.map(r => (r.id, r)).toMap)

directivesOverrides = directiveOverridesByRules.view.mapValues(_.map(_.toComplianceByRule(allRuleObjects)))

t8 <- currentTimeMillis
_ <- TimingDebugLoggerPure.trace(s"getByRulesCompliance - get directive overrides and rules infos in ${t8 - t7} ms")
} yield {

val reportsByRule = reportsByNode.flatMap { case (_, status) => status.reports }.groupBy(_.ruleId)
val t7 = System.currentTimeMillis()
TimingDebugLoggerPure.logEffect.trace(s"getByRulesCompliance - group reports by rules in ${t7 - t6} ms")

// for each rule for each node, we want to have a
// directiveId -> reporttype map
val nonEmptyRules = reportsByRule.toSeq.map {
Expand All @@ -588,9 +613,10 @@ class ComplianceAPIService(
directives.get(directiveId).map(_._2.name).getOrElse("Unknown directive"),
ComplianceLevel.sum(
nodeDirectives.map(_._2.compliance)
), // here we want the compliance by components of the directive.
// if level is high enough, get all components and group by their name
{
),
None, {
// here we want the compliance by components of the directive.
// if level is high enough, get all components and group by their name
val byComponents: Map[String, immutable.Iterable[(NodeId, ComponentStatusReport)]] = if (computedLevel < 3) {
Map()
} else {
Expand Down Expand Up @@ -638,7 +664,9 @@ class ComplianceAPIService(
)

// return the full list
val result = nonEmptyRules ++ initializedCompliances
val singleRuleCompliance = nonEmptyRules ++ initializedCompliances
// add overrides to that result
val result = singleRuleCompliance.map(r => r.copy(directives = r.directives ++ directivesOverrides(r.id)))

val t10 = System.currentTimeMillis()
TimingDebugLoggerPure.logEffect.trace(s"getByRulesCompliance - Compute result in ${t10 - t9} ms")
Expand Down
Expand Up @@ -155,10 +155,10 @@ type alias DirectiveCompliance value =
, name : String
, compliance : Float
, complianceDetails : ComplianceDetails
, skippedDetails : Maybe SkippedDetails
, components : List (ComponentCompliance value)
}


type ComponentCompliance value = Block (BlockCompliance value) | Value (ComponentValueCompliance value)
type alias BlockCompliance value =
{ component : String
Expand Down Expand Up @@ -191,6 +191,11 @@ type alias ValueLine =
, status : String
}

type alias SkippedDetails =
{ overridingRuleId : RuleId
, overridingRuleName : String
}

type alias Report =
{ status : String
, message : Maybe String
Expand Down
Expand Up @@ -202,8 +202,15 @@ decodeDirectiveCompliance elem decoder =
|> required "name" string
|> required "compliance" float
|> required "complianceDetails" decodeComplianceDetails
|> optional "skippedDetails" (maybe decodeSkippedDirectiveDetails) Nothing
|> required "components" (list (decodeComponentCompliance elem decoder ) )

decodeSkippedDirectiveDetails : Decoder SkippedDetails
decodeSkippedDirectiveDetails =
succeed SkippedDetails
|> required "overridingRuleId" (map RuleId string)
|> required "overridingRuleName" string

decodeRuleChanges: Decoder (Dict String (List Changes))
decodeRuleChanges =
at [ "data" ] (dict (list decodeChanges))
Expand Down
Expand Up @@ -216,7 +216,7 @@ byDirectiveCompliance mod subFun =
List.sortWith sortFunction item.components
)
(\m i -> (Maybe.withDefault (Directive i.directiveId i.name "" "" "" False False "" []) (Dict.get i.directiveId.value m.directives), i ))
[ ("Directive", \(d,_) -> span [] [ badgePolicyMode globalPolicy d.policyMode, text d.displayName, buildTagsTree d.tags, goToBtn (getDirectiveLink contextPath d.id) ], (\(_,d1) (_,d2) -> N.compare d1.name d2.name ))
[ ("Directive", \(d,c) -> span [] [ c.skippedDetails |> Maybe.map badgeSkipped |> Maybe.withDefault (badgePolicyMode globalPolicy d.policyMode), text d.displayName, buildTagsTree d.tags, goToBtn (getDirectiveLink contextPath d.id) ], (\(_,d1) (_,d2) -> N.compare d1.name d2.name ))
, ("Compliance", \(_,i) -> buildComplianceBar i.complianceDetails, (\(_,d1) (_,d2) -> Basics.compare d1.compliance d2.compliance ))
]
(.directiveId >> .value)
Expand Down Expand Up @@ -466,6 +466,13 @@ badgePolicyMode globalPolicyMode policyMode =
in
span [class ("treeGroupName tooltipable bs-tooltip rudder-label label-sm label-" ++ mode), attribute "data-toggle" "tooltip", attribute "data-placement" "bottom", attribute "data-container" "body", attribute "data-html" "true", attribute "data-original-title" (buildTooltipContent "Policy mode" msg)][]

badgeSkipped : SkippedDetails -> Html Msg
badgeSkipped { overridingRuleId, overridingRuleName } =
let
msg = "This directive is skipped because it is overridden by the rule <b>" ++ overridingRuleName ++ "</b> (with id " ++ overridingRuleId.value ++ ")."
in
span [class "treeGroupName tooltipable bs-tooltip rudder-label label-sm label-overriden", attribute "data-toggle" "tooltip", attribute "data-placement" "bottom", attribute "data-container" "body", attribute "data-html" "true", attribute "data-original-title" (buildTooltipContent "Skipped directive" msg)][]

-- WARNING:
--
-- Here the content is an HTML so it need to be already escaped.
Expand Down

0 comments on commit c99e311

Please sign in to comment.