Skip to content

Commit

Permalink
Fixes #25984: Add description/doc field to node settable by API
Browse files Browse the repository at this point in the history
  • Loading branch information
fanf committed Dec 4, 2024
1 parent 28f03c2 commit a3ce553
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -425,10 +425,11 @@ object JsonQueryObjects {
}
}
final case class JQUpdateNode(
properties: Option[List[NodeProperty]],
policyMode: Option[Option[PolicyMode]],
state: Option[NodeState],
agentKey: Option[JQAgentKey]
properties: Option[List[NodeProperty]],
policyMode: Option[Option[PolicyMode]],
state: Option[NodeState],
agentKey: Option[JQAgentKey],
documentation: Option[String]
) {
val keyInfo: (Option[SecurityToken], Option[KeyStatus]) = agentKey.map(_.toKeyInfo).getOrElse((None, None))
}
Expand Down Expand Up @@ -927,8 +928,9 @@ class ZioJsonExtractor(queryParser: CmdbQueryParser with JsonQueryLexer) {
policyMode <- params.parse2("policyMode", PolicyMode.parseDefault(_))
state <- params.parseString("state", NodeState.parse(_))
agentKey <- params.parse("agentKey", JsonDecoder[JQAgentKey])
doc = params.optGet("documentation")
} yield {
JQUpdateNode(properties, policyMode, state, agentKey)
JQUpdateNode(properties, policyMode, state, agentKey, doc)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,27 +216,23 @@ object JsonResponseObjects {
)

final case class JRUpdateNode(
id: NodeId,
properties: Chunk[JRProperty], // sorted by name
policyMode: Option[PolicyMode],
state: NodeState
id: NodeId,
properties: Chunk[JRProperty], // sorted by name
policyMode: Option[PolicyMode],
state: NodeState,
documentation: Option[String]
)
object JRUpdateNode {
implicit val transformer: Transformer[CoreNodeFact, JRUpdateNode] = {
Transformer
.define[CoreNodeFact, JRUpdateNode]
.withFieldComputed(
_.state,
_.rudderSettings.state
)
.withFieldComputed(
_.policyMode,
_.rudderSettings.policyMode
)
.withFieldComputed(_.state, _.rudderSettings.state)
.withFieldComputed(_.policyMode, _.rudderSettings.policyMode)
.withFieldComputed(
_.properties,
_.properties.sortBy(_.name).map(JRProperty.fromNodeProp).transformInto[Chunk[JRProperty]]
)
.withFieldComputed(_.documentation, _.documentation)
.buildTransformer
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,15 @@ object NodeState extends Enum[NodeState] {
* For now, other simple properties are not handle.
*/
final case class ModifyNodeDiff(
id: NodeId,
modHeartbeat: Option[SimpleDiff[Option[HeartbeatConfiguration]]],
modAgentRun: Option[SimpleDiff[Option[AgentRunInterval]]],
modProperties: Option[SimpleDiff[List[NodeProperty]]],
modPolicyMode: Option[SimpleDiff[Option[PolicyMode]]],
modKeyValue: Option[SimpleDiff[SecurityToken]],
modKeyStatus: Option[SimpleDiff[KeyStatus]],
modNodeState: Option[SimpleDiff[NodeState]]
id: NodeId,
modHeartbeat: Option[SimpleDiff[Option[HeartbeatConfiguration]]],
modAgentRun: Option[SimpleDiff[Option[AgentRunInterval]]],
modProperties: Option[SimpleDiff[List[NodeProperty]]],
modPolicyMode: Option[SimpleDiff[Option[PolicyMode]]],
modKeyValue: Option[SimpleDiff[SecurityToken]],
modKeyStatus: Option[SimpleDiff[KeyStatus]],
modNodeState: Option[SimpleDiff[NodeState]],
modDocumentation: Option[SimpleDiff[String]]
)

object ModifyNodeDiff {
Expand Down Expand Up @@ -197,7 +198,10 @@ object ModifyNodeDiff {

val state = if (oldNode.state == newNode.state) None else Some(SimpleDiff(oldNode.state, newNode.state))

ModifyNodeDiff(newNode.id, heartbeat, agentRun, properties, policyMode, keyValue, keyStatus, state)
val documentation =
if (oldNode.description == newNode.description) None else Some(SimpleDiff(oldNode.description, newNode.description))

ModifyNodeDiff(newNode.id, heartbeat, agentRun, properties, policyMode, keyValue, keyStatus, state, documentation)
}

def keyInfo(
Expand All @@ -220,6 +224,6 @@ object ModifyNodeDiff {
case Some(s) => if (s == oldStatus) None else Some(SimpleDiff(oldStatus, s))
}

ModifyNodeDiff(nodeId, None, None, None, None, keyInfo, keyStatus, None)
ModifyNodeDiff(nodeId, None, None, None, None, keyInfo, keyStatus, None, None)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ object MinimalNodeFactInterface {
}

eq(_.id) &&
eq(_.description) &&
eq(_.documentation) &&
eq(_.fqdn) &&
eq(_.os) &&
eq(_.machine) &&
Expand All @@ -257,7 +257,7 @@ object MinimalNodeFactInterface {
def toNode(node: MinimalNodeFactInterface): Node = Node(
node.id,
node.fqdn,
"", // description
node.documentation.getOrElse(""),
node.rudderSettings.state,
isSystem(node),
isSystem((node)),
Expand Down Expand Up @@ -704,7 +704,7 @@ object NodeFact {
case _ =>
NodeFact(
a.id,
a.description,
a.documentation,
a.fqdn,
a.os,
a.machine,
Expand Down Expand Up @@ -843,7 +843,7 @@ object NodeFact {
def updateNode(node: NodeFact, n: Node): NodeFact = {
import com.softwaremill.quicklens.*
node
.modify(_.description)
.modify(_.documentation)
.setTo(Some(n.description))
.modify(_.rudderSettings.state)
.setTo(n.state)
Expand Down Expand Up @@ -871,7 +871,7 @@ object NodeFact {
val pad = "\n * "
val ignored = new StringBuilder().append(pad).append("ignored: ")
val sb = new StringBuilder(s"""id:${diff(n1.id.value, n2.id.value)}
| * description:${diff(n1.description, n2.description)}
| * description:${diff(n1.documentation, n2.documentation)}
| * fqdn:${diff(n1.fqdn, n2.fqdn)}
| * os:${diff(n1.os, n2.os)}
| * machine:${diff(n1.machine, n2.machine)}
Expand Down Expand Up @@ -915,7 +915,7 @@ object NodeFact {

trait MinimalNodeFactInterface {
def id: NodeId
def description: Option[String]
def documentation: Option[String]
def fqdn: String
def os: OsDetails
def machine: MachineInfo
Expand Down Expand Up @@ -995,7 +995,7 @@ trait MinimalNodeFactInterface {
*/
final case class CoreNodeFact(
id: NodeId,
description: Option[String],
documentation: Option[String],
@jsonField("hostname")
fqdn: String,
os: OsDetails,
Expand All @@ -1018,8 +1018,8 @@ object CoreNodeFact {
def updateNode(node: CoreNodeFact, n: Node): CoreNodeFact = {
import com.softwaremill.quicklens.*
node
.modify(_.description)
.setTo(Some(n.description))
.modify(_.documentation)
.setTo(if (n.description.isBlank) None else Some(n.description))
.modify(_.rudderSettings.state)
.setTo(n.state)
.modify(_.rudderSettings.kind)
Expand All @@ -1040,7 +1040,7 @@ object CoreNodeFact {
case _ =>
CoreNodeFact(
a.id,
a.description,
a.documentation,
a.fqdn,
a.os,
a.machine,
Expand Down Expand Up @@ -1398,7 +1398,7 @@ object SelectFacts {

final case class NodeFact(
id: NodeId,
description: Option[String],
documentation: Option[String],
@jsonField("hostname")
fqdn: String,
os: OsDetails,
Expand Down Expand Up @@ -1461,7 +1461,7 @@ final case class NodeFact(
val pad = "\n * "
val ignored = new StringBuilder().append(pad).append("ignored: ")
val sb = new StringBuilder(s"""id:${this.id.value}
| * description:${this.description}
| * documentation:${this.documentation}
| * fqdn:${this.fqdn}
| * os:${this.os}
| * machine:${this.machine}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1071,32 +1071,33 @@ class EventLogDetailsServiceImpl(

def getModifyNodeDetails(xml: NodeSeq): Box[ModifyNodeDiff] = {
for {
entry <- getEntryContent(xml)
node <- (entry \ "node").headOption ?~! ("Entry type is not node : " + entry.toString())
fileFormatOk <- TestFileFormat(node)
changeTypeOk <- {
entry <- getEntryContent(xml)
node <- (entry \ "node").headOption ?~! ("Entry type is not node : " + entry.toString())
fileFormatOk <- TestFileFormat(node)
changeTypeOk <- {
if (node.attribute("changeType").map(_.text) == Some("modify")) Full("OK")
else Failure(s"'Node modification' entry does not have attribute 'changeType' with value 'modify', entry is: ${entry}")
}
id <- (node \ "id").headOption.map(x => NodeId(x.text)) ?~! ("Missing element 'id' in entry type Node: " + entry.toString())
policyMode <- getFromTo[Option[PolicyMode]]((node \ "policyMode").headOption, x => PolicyMode.parseDefault(x.text).toBox)
agentRun <- getFromTo[Option[AgentRunInterval]]((node \ "agentRun").headOption, x => extractAgentRun(xml)(x))
heartbeat <-
id <- (node \ "id").headOption.map(x => NodeId(x.text)) ?~! ("Missing element 'id' in entry type Node: " + entry.toString())
policyMode <- getFromTo[Option[PolicyMode]]((node \ "policyMode").headOption, x => PolicyMode.parseDefault(x.text).toBox)
agentRun <- getFromTo[Option[AgentRunInterval]]((node \ "agentRun").headOption, x => extractAgentRun(xml)(x))
heartbeat <-
getFromTo[Option[HeartbeatConfiguration]]((node \ "heartbeat").headOption, x => extractHeartbeatConfiguration(xml)(x))
properties <-
properties <-
getFromTo[List[NodeProperty]]((node \ "properties").headOption, x => extractNodeProperties(xml)(x).map(_.toList))
agentKey <- getFromTo[SecurityToken](
(node \ "agentKey").headOption,
{ x =>
val s = x.text;
if (s.contains("BEGIN CERTIFICATE")) Full(Certificate(s)) else Failure(s"Unrecognized security token")
}
)
keyStatus <- getFromTo[KeyStatus](
(node \ "keyStatus").headOption,
x => KeyStatus.apply(x.text).map(Full(_)).getOrElse(Failure(s"Unrecognized agent key status '${x.text}'"))
)
nodeState <- getFromTo[NodeState]((node \ "nodeState").headOption, x => NodeState.parse(x.text).toBox)
agentKey <- getFromTo[SecurityToken](
(node \ "agentKey").headOption,
{ x =>
val s = x.text;
if (s.contains("BEGIN CERTIFICATE")) Full(Certificate(s)) else Failure(s"Unrecognized security token")
}
)
keyStatus <- getFromTo[KeyStatus](
(node \ "keyStatus").headOption,
x => KeyStatus.apply(x.text).map(Full(_)).getOrElse(Failure(s"Unrecognized agent key status '${x.text}'"))
)
nodeState <- getFromTo[NodeState]((node \ "nodeState").headOption, x => NodeState.parse(x.text).toBox)
documentation <- getFromTo[String]((node \ "documentation").headOption, x => Full(x.text))
} yield {
ModifyNodeDiff(
id,
Expand All @@ -1106,7 +1107,8 @@ class EventLogDetailsServiceImpl(
policyMode,
agentKey,
keyStatus,
nodeState
nodeState,
documentation
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,11 @@ class EventLogFactoryImpl(
case None => NodeSeq.Empty
case Some(x) => SimpleDiff.toXml(<nodeState/>, x)(x => Text(x.name))
}
}{
modifyDiff.modDocumentation match {
case None => NodeSeq.Empty
case Some(x) => SimpleDiff.toXml(<description/>, x)(x => Text(x))
}
}
</node>)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ class NodeEventLogFormatV6Test extends Specification {
modPolicyMode = None,
modKeyValue = None,
modKeyStatus = None,
modNodeState = None
modNodeState = None,
modDocumentation = None
)

val event_32_NodePropertiesModified: Elem = <entry><node changeType="modify" fileFormat="6">
Expand Down Expand Up @@ -123,7 +124,8 @@ class NodeEventLogFormatV6Test extends Specification {
modPolicyMode = None,
modKeyValue = None,
modKeyStatus = None,
modNodeState = None
modNodeState = None,
modDocumentation = None
)

val event_32_NodeAgentRunPeriodModified: Elem = <entry><node changeType="modify" fileFormat="6">
Expand All @@ -147,7 +149,8 @@ class NodeEventLogFormatV6Test extends Specification {
modPolicyMode = None,
modKeyValue = None,
modKeyStatus = None,
modNodeState = None
modNodeState = None,
modDocumentation = None
)

"Current EventTypeFactory" should {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1266,15 +1266,21 @@ class NodeApiService(
def updateRestNode(nodeId: NodeId, update: JQUpdateNode)(implicit cc: ChangeContext): IOResult[CoreNodeFact] = {

def updateNode(
node: CoreNodeFact,
update: JQUpdateNode,
newProperties: List[NodeProperty],
newKey: Option[SecurityToken],
newKeyStatus: Option[KeyStatus]
node: CoreNodeFact,
update: JQUpdateNode,
newProperties: List[NodeProperty],
newKey: Option[SecurityToken],
newKeyStatus: Option[KeyStatus],
newDocumentation: Option[String]
): CoreNodeFact = {
import com.softwaremill.quicklens.*

val propNames: Set[String] = newProperties.map(_.name).toSet
val documentation = newDocumentation match {
case Some("") => Some(None)
case Some(x) => Some(Some(x))
case None => None
}

node
.modify(_.properties)
Expand All @@ -1291,14 +1297,16 @@ class NodeApiService(
.setToIfDefined(newKey)
.modify(_.rudderSettings.keyStatus)
.setToIfDefined(newKeyStatus)
.modify(_.documentation)
.setToIfDefined(documentation)
}

implicit val qc: QueryContext = cc.toQuery

for {
nodeFact <- nodeFactRepository.get(nodeId).notOptional(s"node with id '${nodeId.value}' was not found")
newProperties <- CompareProperties.updateProperties(nodeFact.properties.toList, update.properties).toIO
updated = updateNode(nodeFact, update, newProperties, update.keyInfo._1, update.keyInfo._2)
updated = updateNode(nodeFact, update, newProperties, update.keyInfo._1, update.keyInfo._2, update.documentation)
_ <- if (CoreNodeFact.same(updated, nodeFact)) ZIO.unit
else nodeFactRepository.save(updated).unit
} yield {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,8 @@ class EventLogDetailsGenerator(
mapComplexDiff(modDiff.modKeyStatus, <b>Key status</b>)(x => Text(x.value))
}{mapComplexDiff(modDiff.modKeyValue, <b>Key value</b>)(x => Text(x.key))}{reasonHtml}{
xmlParameters(event.id)
}{
mapComplexDiff(modDiff.modDocumentation, <b>Description</b>)(x => Text(x))
}
</div>
case e: EmptyBox =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,12 @@ object DisplayNode extends Loggable {
}
</div>
</div>
<div class="rudder-info">
<h3>Documentation</h3>
<div class="markdown" id="nodeDocumentation">
</div>
{Script(OnLoad(JsRaw(s"generateMarkdown(${Str(nodeFact.documentation.getOrElse("")).toJsCmd}, '#nodeDocumentation')")))}
</div>
</div>
<div class="rudder-info">
<h3>Rudder information</h3>
Expand Down

0 comments on commit a3ce553

Please sign in to comment.