Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #22525: Directives applied twice don't show in rule details (they should be skipped) #5600

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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