diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFact.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFact.scala index 1c2a16f4141..3e3eaa9f4d9 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFact.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFact.scala @@ -1281,11 +1281,17 @@ object NodeFactChangeEvent { } } -final case class ChangeContext(modId: ModificationId, actor: EventActor, message: Option[String], actorIp: Option[String]) +final case class ChangeContext( + modId: ModificationId, + actor: EventActor, + eventDate: DateTime, + message: Option[String], + actorIp: Option[String] +) object ChangeContext { def newForRudder(msg: Option[String] = None, actorIp: Option[String] = None) = - ChangeContext(ModificationId(java.util.UUID.randomUUID.toString), eventlog.RudderEventActor, msg, actorIp) + ChangeContext(ModificationId(java.util.UUID.randomUUID.toString), eventlog.RudderEventActor, DateTime.now(), msg, actorIp) } final case class NodeFactChangeEventCC[+A <: MinimalNodeFactInterface]( diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactChangeEventCallback.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactChangeEventCallback.scala index 5f2a0b9e365..7c24697cdd6 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactChangeEventCallback.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactChangeEventCallback.scala @@ -40,7 +40,9 @@ package com.normation.rudder.facts.nodes import com.normation.errors.IOResult import com.normation.eventlog.ModificationId import com.normation.inventory.domain.AcceptedInventory +import com.normation.inventory.domain.InventoryStatus import com.normation.inventory.domain.NodeId +import com.normation.inventory.domain.PendingInventory import com.normation.inventory.domain.RemovedInventory import com.normation.rudder.batch.AsyncDeploymentActor import com.normation.rudder.batch.AutomaticStartDeployment @@ -218,7 +220,7 @@ class EventLogsNodeFactChangeEventCallback( next: MinimalNodeFactInterface ): IOResult[Unit] = { val diff = ModifyNodeDiff(toNode(old), toNode(next)) - eventLogRepository.saveModifyNode(cc.modId, cc.actor, diff, cc.message).unit + eventLogRepository.saveModifyNode(cc.modId, cc.actor, diff, cc.message, cc.eventDate).unit } change.event match { @@ -228,6 +230,7 @@ class EventLogsNodeFactChangeEventCallback( case NodeFactChangeEvent.Accepted(node) => val log = AcceptNodeEventLog.fromInventoryLogDetails( principal = change.cc.actor, + creationDate = change.cc.eventDate, inventoryDetails = InventoryLogDetails( nodeId = node.id, inventoryVersion = node.lastInventoryDate.getOrElse(node.factProcessedDate), @@ -248,6 +251,7 @@ class EventLogsNodeFactChangeEventCallback( case NodeFactChangeEvent.Refused(node) => val log = RefuseNodeEventLog.fromInventoryLogDetails( principal = change.cc.actor, + creationDate = change.cc.eventDate, inventoryDetails = InventoryLogDetails( nodeId = node.id, inventoryVersion = node.lastInventoryDate.getOrElse(node.factProcessedDate), @@ -268,8 +272,9 @@ class EventLogsNodeFactChangeEventCallback( case NodeFactChangeEvent.Deleted(node) => val log = DeleteNodeEventLog.fromInventoryLogDetails( None, - change.cc.actor, - InventoryLogDetails( + principal = change.cc.actor, + creationDate = change.cc.eventDate, + inventoryDetails = InventoryLogDetails( node.id, node.lastInventoryDate.getOrElse(node.factProcessedDate), node.fqdn, @@ -307,53 +312,74 @@ class HistorizeNodeState( override def run(change: NodeFactChangeEventCC[MinimalNodeFactInterface]): IOResult[Unit] = { - def save(nodeId: NodeId, alsoJDBC: Boolean) = { + def save(node: MinimalNodeFactInterface, eventDate: DateTime, alsoJDBC: Boolean, status: InventoryStatus): IOResult[Unit] = { // we want to save the fact with everything implicit val attrs = SelectFacts.all if (gitFactStorage == NoopFactStorage && !alsoJDBC) ZIO.unit else { - sourceFactStorage.getAccepted(nodeId).flatMap { - case None => ZIO.unit - case Some(full) => - for { - _ <- ZIO.when(alsoJDBC)(historyRepos.save(nodeId, FactLogData(full, change.cc.actor, AcceptedInventory))) - _ <- gitFactStorage.save(full) - } yield () + (if (status == PendingInventory) sourceFactStorage.getPending(node.id) + else sourceFactStorage.getAccepted(node.id)).flatMap { res => + val nf = res match { + case Some(x) => x + case None => // in case of refuse event, node is already deleted + NodeFact.fromMinimal(node) + } + for { + _ <- ZIO.when(alsoJDBC)(historyRepos.save(node.id, FactLogData(nf, change.cc.actor, AcceptedInventory), eventDate)) + _ <- gitFactStorage.save(nf) + } yield () } } - } - change.event match { - case NodeFactChangeEvent.NewPending(node) => ZIO.unit - case NodeFactChangeEvent.UpdatedPending(oldNode, newNode) => ZIO.unit - case NodeFactChangeEvent.Accepted(node) => save(node.id, true) - case NodeFactChangeEvent.Refused(node) => save(node.id, true) - case NodeFactChangeEvent.Updated(oldNode, newNode) => save(newNode.id, false) - case NodeFactChangeEvent.Deleted(node) => - /* - * This hook registers the deletion events into postgresql `nodefacts` table so that the inventory accept/refuse - * fact can be latter cleaned-up. - */ - (( + def delete(nodeId: NodeId): IOResult[Unit] = { + /* + * This hook registers the deletion events into postgresql `nodefacts` table so that the inventory accept/refuse + * fact can be latter cleaned-up. + */ + ( + ( if (cleanUpImmediately) { - historyRepos.delete(node.id) + historyRepos.delete(nodeId) } else { // save delete event, clean-up will be automatically done by script - historyRepos.saveDeleteEvent(node.id, DateTime.now(), change.cc.actor) + historyRepos.saveDeleteEvent(nodeId, change.cc.eventDate, change.cc.actor) } ).catchAll(err => { NodeLoggerPure - .warn(s"Error when updating node '${node.id.value}' historical inventory information in base: ${err.fullMsg}") - })) *> - NodeLoggerPure.Delete.debug(s" - delete fact about node '${node.id.value}'") *> - gitFactStorage - .changeStatus(node.id, RemovedInventory) - .catchAll(err => - NodeLoggerPure.info(s"Error when trying to update fact when deleting node '${node.id.value}': ${err.fullMsg}") - ) - .unit + .warn(s"Error when updating node '${nodeId.value}' historical inventory information in base: ${err.fullMsg}") + }) + ) *> + NodeLoggerPure.Delete.debug(s" - delete fact about node '${nodeId.value}'") *> + gitFactStorage + .changeStatus(nodeId, RemovedInventory) + .catchAll(err => + NodeLoggerPure.info(s"Error when trying to update fact when deleting node '${nodeId.value}': ${err.fullMsg}") + ) + .unit + } - case NodeFactChangeEvent.Noop(nodeId) => ZIO.unit + change.event match { + case NodeFactChangeEvent.NewPending(node) => + NodeLoggerPure.debug(s"Save new in node fact fs") *> + save(node, change.cc.eventDate, false, PendingInventory) + case NodeFactChangeEvent.UpdatedPending(oldNode, newNode) => + NodeLoggerPure.debug(s"Update pending in node fact fs") *> + save(newNode, change.cc.eventDate, false, PendingInventory) + case NodeFactChangeEvent.Accepted(node) => + NodeLoggerPure.debug(s"Accept in node fact fs and postgres") *> + save(node, change.cc.eventDate, true, AcceptedInventory) // callback done post accept + case NodeFactChangeEvent.Refused(node) => + NodeLoggerPure.debug(s"Refused in node fact fs and postgres") *> + save(node, change.cc.eventDate, true, AcceptedInventory) + case NodeFactChangeEvent.Updated(oldNode, newNode) => + NodeLoggerPure.debug(s"Update in node fact fs") *> + save(newNode, change.cc.eventDate, false, AcceptedInventory) + case NodeFactChangeEvent.Deleted(node) => + NodeLoggerPure.debug(s"Delete in node fact fs") *> + delete(node.id) + case NodeFactChangeEvent.Noop(nodeId) => + NodeLoggerPure.debug(s"noop") *> + ZIO.unit } } } diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactRepository.scala index a24bdbf5c00..ea05dafa0cd 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactRepository.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactRepository.scala @@ -45,7 +45,6 @@ import com.normation.rudder.domain.Constants import com.normation.rudder.domain.logger.NodeLoggerPure import com.normation.rudder.domain.nodes.NodeState import com.softwaremill.quicklens._ -import scala.annotation.nowarn import zio._ import zio.concurrent.ReentrantLock import zio.stream.ZStream @@ -359,8 +358,6 @@ class CoreNodeFactRepository( * - do we want to fork and timeout each callbacks ? likely so * - do we want to parallel exec them ? likely so, the user can build his own callback sequencer callback if he wants */ - // TODO: perhaps we only want to accept post hooks on core node facts - @nowarn("msg=abstract type A in type pattern .+ is unchecked.") private[nodes] def runCallbacks[A <: MinimalNodeFactInterface](e: NodeFactChangeEventCC[A]): IOResult[Unit] = { for { cs <- callbacks.get diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactServiceProxies.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactServiceProxies.scala index dbc06c18ed4..0032cee2c31 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactServiceProxies.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/facts/nodes/NodeFactServiceProxies.scala @@ -63,7 +63,10 @@ import com.normation.rudder.domain.nodes.NodeInfo import com.normation.rudder.domain.nodes.NodeKind import com.normation.rudder.repository.WoNodeRepository import com.normation.rudder.services.nodes.NodeInfoService + import com.softwaremill.quicklens._ +import org.joda.time.DateTime + import zio._ import zio.stream.ZSink import zio.syntax._ @@ -232,7 +235,7 @@ class WoFactNodeRepositoryProxy(backend: NodeFactRepository) extends WoNodeRepos case None => Inconsistency(s"Node with id '${node.id.value}' was not found").fail case Some(fact) => CoreNodeFact.updateNode(fact, node).succeed } - _ <- backend.save(NodeFact.fromMinimal(fact))(ChangeContext(modId, actor, reason, None), SelectFacts.none) + _ <- backend.save(NodeFact.fromMinimal(fact))(ChangeContext(modId, actor, DateTime.now(), reason, None), SelectFacts.none) } yield fact.toNode } @@ -261,7 +264,7 @@ class WoFactNodeRepositoryProxy(backend: NodeFactRepository) extends WoNodeRepos .setToIfDefined(agentKey) .modify(_.rudderSettings.keyStatus) .setToIfDefined(agentKeyStatus) - _ <- backend.save(NodeFact.fromMinimal(newNode))(ChangeContext(modId, actor, reason, None), SelectFacts.none) + _ <- backend.save(NodeFact.fromMinimal(newNode))(ChangeContext(modId, actor, DateTime.now(), reason, None), SelectFacts.none) } yield () } } diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala index 6f56df1a47e..82d3c63b32c 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala @@ -71,6 +71,7 @@ import com.normation.rudder.domain.workflows.ChangeRequestId import com.normation.rudder.domain.workflows.WorkflowStepChange import com.normation.rudder.services.eventlog.EventLogFactory import doobie._ +import org.joda.time.DateTime trait EventLogRepository { def eventLogFactory: EventLogFactory @@ -396,14 +397,16 @@ trait EventLogRepository { modId: ModificationId, principal: EventActor, modifyDiff: ModifyNodeDiff, - reason: Option[String] + reason: Option[String], + eventDate: DateTime ) = { saveEventLog( modId, eventLogFactory.getModifyNodeFromDiff( principal = principal, modifyDiff = modifyDiff, - reason = reason + reason = reason, + creationDate = eventDate ) ) } diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPNodeRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPNodeRepository.scala index 1337daaeaed..0d39c6258a2 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPNodeRepository.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPNodeRepository.scala @@ -37,6 +37,7 @@ package com.normation.rudder.repository.ldap import com.normation.NamedZioLogger + import com.normation.errors._ import com.normation.eventlog.EventActor import com.normation.eventlog.ModificationId @@ -57,6 +58,9 @@ import com.normation.rudder.repository.WoNodeRepository import com.normation.rudder.services.reports.CacheComplianceQueueAction import com.normation.rudder.services.reports.CacheExpectedReportAction import com.normation.rudder.services.reports.InvalidateCache + +import org.joda.time.DateTime + import zio._ import zio.syntax._ @@ -97,7 +101,7 @@ class WoLDAPNodeRepository( case LDIFNoopChangeRecord(_) => ZIO.unit case _ => val diff = ModifyNodeDiff(oldNode, node) - actionLogger.saveModifyNode(modId, actor, diff, reason) + actionLogger.saveModifyNode(modId, actor, diff, reason, DateTime.now()) } } yield { node @@ -164,7 +168,7 @@ class WoLDAPNodeRepository( case _ => val diff = ModifyNodeDiff.keyInfo(nodeId, agentsInfo._1.map(_.securityToken), agentsInfo._2, agentKey, agentKeyStatus) - actionLogger.saveModifyNode(modId, actor, diff, reason) + actionLogger.saveModifyNode(modId, actor, diff, reason, DateTime.now()) } } yield ()) } diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/RemoveNodeService.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/RemoveNodeService.scala index 41659012d32..47e1364236a 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/RemoveNodeService.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/RemoveNodeService.scala @@ -38,7 +38,6 @@ package com.normation.rudder.services.servers import com.normation.box._ import com.normation.errors._ -import com.normation.eventlog.EventActor import com.normation.eventlog.ModificationId import com.normation.inventory.domain.AcceptedInventory import com.normation.inventory.domain.AgentType @@ -132,7 +131,9 @@ trait PostNodeDeleteAction { // a node can have several status (if inventories already deleted, and now in pending again for ex) // or zero (if only some things remain) // and if can optionnally have a nodeInfo - def run(nodeId: NodeId, mode: DeleteMode, info: Option[NodeInfo], status: Set[InventoryStatus], actor: EventActor): UIO[Unit] + def run(nodeId: NodeId, mode: DeleteMode, info: Option[NodeInfo], status: Set[InventoryStatus])(implicit + cc: ChangeContext + ): UIO[Unit] } object PostNodeDeleteAction { @@ -158,17 +159,11 @@ trait RemoveNodeService { * - move the node */ - def removeNode(nodeId: NodeId, modId: ModificationId, actor: EventActor, actorIp: String): Box[DeletionResult] = { - removeNodePure(nodeId, DeleteMode.MoveToRemoved, modId, actor, actorIp).map(_ => Success).toBox + def removeNode(nodeId: NodeId)(implicit cc: ChangeContext): Box[DeletionResult] = { + removeNodePure(nodeId, DeleteMode.MoveToRemoved).map(_ => Success).toBox } - def removeNodePure( - nodeId: NodeId, - mode: DeleteMode, - modId: ModificationId, - actor: EventActor, - actorIp: String - ): IOResult[NodeInfo] + def removeNodePure(nodeId: NodeId, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[NodeInfo] } trait RemoveNodeBackend { @@ -177,10 +172,10 @@ trait RemoveNodeBackend { def findNodeStatuses(nodeId: NodeId): IOResult[Set[InventoryStatus]] // the abstract method that actually commit in backend repo the deletion from accepted nodes - def commitDeleteAccepted(nodeInfo: NodeInfo, mode: DeleteMode, modId: ModificationId, actor: EventActor): IOResult[Unit] + def commitDeleteAccepted(nodeInfo: NodeInfo, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[Unit] // the abstract method that actually commit in backend repo the deletion from accepted nodes - def commitPurgeRemoved(nodeId: NodeId, mode: DeleteMode, modId: ModificationId, actor: EventActor): IOResult[Unit] + def commitPurgeRemoved(nodeId: NodeId, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[Unit] } @@ -194,17 +189,12 @@ class FactRemoveNodeBackend(backend: NodeFactRepository) extends RemoveNodeBacke } } - override def commitDeleteAccepted( - nodeInfo: NodeInfo, - mode: DeleteMode, - modId: ModificationId, - actor: EventActor - ): IOResult[Unit] = { - backend.delete(nodeInfo.id)(ChangeContext(modId, actor, None, None)).unit + override def commitDeleteAccepted(nodeInfo: NodeInfo, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[Unit] = { + backend.delete(nodeInfo.id).unit } - override def commitPurgeRemoved(nodeId: NodeId, mode: DeleteMode, modId: ModificationId, actor: EventActor): IOResult[Unit] = { - backend.delete(nodeId)(ChangeContext(modId, actor, None, None)).unit + override def commitPurgeRemoved(nodeId: NodeId, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[Unit] = { + backend.delete(nodeId).unit } } @@ -235,13 +225,7 @@ class RemoveNodeServiceImpl( * The main goal is to separate the clear cache as it could fail while the node is correctly deleted. * A failing clear cache should not be considered an error when deleting a Node. */ - override def removeNodePure( - nodeId: NodeId, - mode: DeleteMode, - modId: ModificationId, - actor: EventActor, - actorIp: String - ): IOResult[NodeInfo] = { + override def removeNodePure(nodeId: NodeId, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[NodeInfo] = { // main logic, see help function below nodeId match { case Constants.ROOT_POLICY_SERVER_ID => Inconsistency("The root node cannot be deleted.").fail @@ -255,7 +239,7 @@ class RemoveNodeServiceImpl( res1 <- if (status.contains(PendingInventory)) { (for { i <- nodeInfoService.getPendingNodeInfo(nodeId) - r <- deletePendingNode(nodeId, mode, modId, actor, actorIp) + r <- deletePendingNode(nodeId, mode) _ <- info.set(i) } yield r).catchAll(err => Error(err).succeed) } else Success.succeed @@ -264,14 +248,14 @@ class RemoveNodeServiceImpl( i <- nodeInfoService.getNodeInfo(nodeId) r <- i match { case None => Success.succeed // perhaps deleted or something - case Some(x) => info.set(Some(x)) *> deleteAcceptedNode(x, mode, modId, actor) + case Some(x) => info.set(Some(x)) *> deleteAcceptedNode(x, mode) } } yield r).catchAll(err => Error(err).succeed) } else Success.succeed res3 <- if (status.contains(RemovedInventory)) { (for { i <- nodeInfoService.getDeletedNodeInfo(nodeId) - r <- deleteDeletedNode(nodeId, mode, modId, actor) + r <- deleteDeletedNode(nodeId, mode) // only update if nodeInfo is not already set, b/c accepted has more info _ <- info.update(opt => opt.orElse(i)) } yield r).catchAll(err => Error(err).succeed) @@ -282,7 +266,7 @@ class RemoveNodeServiceImpl( _ <- NodeLoggerPure.Delete.debug(s"-> execute clean-up actions for node '${nodeId.value}'") actions <- postNodeDeleteActions.get optInfo <- info.get - _ <- ZIO.foreachDiscard(actions)(_.run(nodeId, mode, optInfo, status, actor)) + _ <- ZIO.foreachDiscard(actions)(_.run(nodeId, mode, optInfo, status)) _ <- NodeLoggerPure.Delete.info( s"Node '${nodeId.value}' ${optInfo.map(_.hostname).getOrElse("")} was successfully deleted" ) @@ -329,25 +313,14 @@ class RemoveNodeServiceImpl( //////////////////////////////// // delete pending node is just refusing it - def deletePendingNode( - nodeId: NodeId, - mode: DeleteMode, - modId: ModificationId, - actor: EventActor, - actorIp: String - ): IOResult[DeletionResult] = { + def deletePendingNode(nodeId: NodeId, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[DeletionResult] = { NodeLoggerPure.Delete.debug(s"-> deleting node with ID '${nodeId.value}' from pending nodes (refuse)") *> - newNodeManager.refuse(nodeId)(ChangeContext(modId, actor, None, Some(actorIp))).map(_ => DeletionResult.Success) + newNodeManager.refuse(nodeId).map(_ => DeletionResult.Success) } // this is the core delete that is run on accepted node: pre hook, post hook, move to delete or erase // in that case, we do have a nodeInfo - def deleteAcceptedNode( - nodeInfo: NodeInfo, - mode: DeleteMode, - modId: ModificationId, - actor: EventActor - ): IOResult[DeletionResult] = { + def deleteAcceptedNode(nodeInfo: NodeInfo, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[DeletionResult] = { for { _ <- NodeLoggerPure.Delete.debug(s"-> deleting node with ID '${nodeInfo.id.value}' from accepted nodes") @@ -360,7 +333,7 @@ class RemoveNodeServiceImpl( case _ => for { _ <- NodeLoggerPure.Delete.debug(s" - delete '${nodeInfo.id.value}' in LDAP (mode='${mode.name}')") - _ <- backend.commitDeleteAccepted(nodeInfo, mode, modId, actor) + _ <- backend.commitDeleteAccepted(nodeInfo, mode) _ <- NodeLoggerPure.Delete.debug(s" - run node post hooks for '${nodeInfo.id.value}'") postRun <- runPostHooks(hookEnv) } yield { @@ -374,13 +347,13 @@ class RemoveNodeServiceImpl( } // delete a node for which we only have the inventory, so it's either deleted, or in accepted but somehow broken. - def deleteDeletedNode(nodeId: NodeId, mode: DeleteMode, modId: ModificationId, actor: EventActor): IOResult[DeletionResult] = { + def deleteDeletedNode(nodeId: NodeId, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[DeletionResult] = { // if mode is move, done if (mode == DeleteMode.MoveToRemoved) { Success.succeed } else { // erase NodeLoggerPure.Delete.debug(s"-> erase '${nodeId.value}' from removed nodes") *> - backend.commitPurgeRemoved(nodeId, mode, modId, actor).map(_ => Success) + backend.commitPurgeRemoved(nodeId, mode).map(_ => Success) } } @@ -484,29 +457,19 @@ class LdapRemoveNodeBackend( } } - override def commitPurgeRemoved(nodeId: NodeId, mode: DeleteMode, modId: ModificationId, actor: EventActor): IOResult[Unit] = { + override def commitPurgeRemoved(nodeId: NodeId, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[Unit] = { fullNodeRepo.delete(nodeId, RemovedInventory).unit } // the part that just move/delete node - override def commitDeleteAccepted( - nodeInfo: NodeInfo, - mode: DeleteMode, - modId: ModificationId, - actor: EventActor - ): IOResult[Unit] = { + override def commitDeleteAccepted(nodeInfo: NodeInfo, mode: DeleteMode)(implicit cc: ChangeContext): IOResult[Unit] = { for { _ <- - nodeLibMutex.writeLock(atomicDelete(nodeInfo.id, mode, modId, actor)).chainError("Error when deleting a node") + nodeLibMutex.writeLock(atomicDelete(nodeInfo.id, mode)).chainError("Error when deleting a node") } yield () } - def atomicDelete( - nodeId: NodeId, - mode: DeleteMode, - modId: ModificationId, - actor: EventActor - ): IOResult[Seq[LDIFChangeRecord]] = { + def atomicDelete(nodeId: NodeId, mode: DeleteMode): IOResult[Seq[LDIFChangeRecord]] = { for { cleanNode <- deleteFromNodes(nodeId).chainError(s"Could not remove the node '${nodeId.value}' from base") moveNodeInventory <- mode match { @@ -547,9 +510,8 @@ class RemoveNodeFromGroups( nodeId: NodeId, mode: DeleteMode, info: Option[NodeInfo], - status: Set[InventoryStatus], - actor: EventActor - ): UIO[Unit] = { + status: Set[InventoryStatus] + )(implicit cc: ChangeContext): UIO[Unit] = { (for { _ <- NodeLoggerPure.Delete.debug(s" - remove node ${nodeId.value} from his groups") nodeGroupIds <- roNodeGroupRepository.findGroupWithAnyMember(Seq(nodeId)) @@ -583,9 +545,8 @@ class CloseNodeConfiguration(expectedReportsRepository: UpdateExpectedReportsRep nodeId: NodeId, mode: DeleteMode, info: Option[NodeInfo], - status: Set[InventoryStatus], - actor: EventActor - ): UIO[Unit] = { + status: Set[InventoryStatus] + )(implicit cc: ChangeContext): UIO[Unit] = { for { _ <- NodeLoggerPure.Delete.debug(s" - close expected reports for '${nodeId.value}'") _ <- expectedReportsRepository @@ -603,9 +564,8 @@ class DeletePolicyServerPolicies(policyServerManagement: PolicyServerManagementS nodeId: NodeId, mode: DeleteMode, info: Option[NodeInfo], - status: Set[InventoryStatus], - actor: EventActor - ): UIO[Unit] = { + status: Set[InventoryStatus] + )(implicit cc: ChangeContext): UIO[Unit] = { // we can avoid to do LDAP requests if we are sure the node wasn't a policy server info.map(_.isPolicyServer) match { case Some(false) => @@ -629,9 +589,8 @@ class ResetKeyStatus(ldap: LDAPConnectionProvider[RwLDAPConnection], deletedDit: nodeId: NodeId, mode: DeleteMode, info: Option[NodeInfo], - status: Set[InventoryStatus], - actor: EventActor - ): UIO[Unit] = { + status: Set[InventoryStatus] + )(implicit cc: ChangeContext): UIO[Unit] = { if (mode == DeleteMode.MoveToRemoved) { NodeLoggerPure.Delete.debug(s" - reset node key certification status for '${nodeId.value}'") *> (for { @@ -655,9 +614,8 @@ class CleanUpCFKeys extends PostNodeDeleteAction { nodeId: NodeId, mode: DeleteMode, info: Option[NodeInfo], - status: Set[InventoryStatus], - actor: EventActor - ): UIO[Unit] = { + status: Set[InventoryStatus] + )(implicit cc: ChangeContext): UIO[Unit] = { info match { case Some(i) => val agentTypes = i.agentsName.map(_.agentType).toSet @@ -720,9 +678,8 @@ class CleanUpNodePolicyFiles(varRudderShare: String) extends PostNodeDeleteActio nodeId: NodeId, mode: DeleteMode, info: Option[NodeInfo], - status: Set[InventoryStatus], - actor: EventActor - ): UIO[Unit] = { + status: Set[InventoryStatus] + )(implicit cc: ChangeContext): UIO[Unit] = { NodeLoggerPure.Delete.debug(s" - clean-up node '${nodeId.value}' policy files in /var/rudder/share") *> cleanPoliciesRec(nodeId, File(varRudderShare)).runDrain.catchAll(err => { NodeLoggerPure.Delete.info( diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/HistoryLogRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/HistoryLogRepository.scala index 8355834ffe9..8497f541b56 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/HistoryLogRepository.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/HistoryLogRepository.scala @@ -31,7 +31,7 @@ trait WriteOnlyHistoryLogRepository[ID, V, T, HLog <: HistoryLog[ID, V, T]] { * @param historyLog * @return */ - def save(id: ID, data: T, datetime: DateTime = DateTime.now): IOResult[HLog] + def save(id: ID, data: T, datetime: DateTime): IOResult[HLog] } @@ -50,7 +50,7 @@ trait ReadOnlyHistoryLogRepository[ID, V, T, HLog <: HistoryLog[ID, V, T]] { * recorded history * Full(hlog) the recorded version of hlog */ - def get(id: ID, version: V): IOResult[HLog] + def get(id: ID, version: V): IOResult[Option[HLog]] /** * Return the list of version for ID. diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/impl/FileHistoryLogRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/impl/FileHistoryLogRepository.scala index 4c4643f7812..4e720588188 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/impl/FileHistoryLogRepository.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/impl/FileHistoryLogRepository.scala @@ -151,15 +151,14 @@ class FileHistoryLogRepository[ID: ClassTag, T]( } /** - * Get the list of record for the given UUID and version. - * If no version is specified, get the last. + * Get the record for the given UUID and version if exists */ - def get(id: ID, version: DateTime): IOResult[HLog] = { + def get(id: ID, version: DateTime): IOResult[Option[HLog]] = { for { i <- idDir(id) file <- ZIO.succeed(new File(i, vToS(version))) - data <- parser.fromFile(file) - } yield DefaultHLog(id, version, data) + data <- ZIO.whenZIO(IOResult.attempt(file.exists())) { parser.fromFile(file) } + } yield data.map(d => DefaultHLog(id, version, d)) } // we don't want to catch exception here diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/impl/InventoryHistoryJdbcRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/impl/InventoryHistoryJdbcRepository.scala index a0d8c3536d2..5a3199a600e 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/impl/InventoryHistoryJdbcRepository.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/services/nodes/history/impl/InventoryHistoryJdbcRepository.scala @@ -118,7 +118,7 @@ class InventoryHistoryJdbcRepository( * Save an inventory and return the ID of the saved inventory, and * its version */ - override def save(id: NodeId, data: FactLogData, datetime: DateTime = DateTime.now): IOResult[FactLog] = { + override def save(id: NodeId, data: FactLogData, datetime: DateTime): IOResult[FactLog] = { val event = NodeAcceptRefuseEvent(datetime, data.actor.name, data.status.name) val q = sql"""insert into nodefacts (nodeId, acceptRefuseEvent, acceptRefuseFact) values (${id}, ${event}, ${data.fact}) on conflict (nodeId) do update set (acceptRefuseEvent, acceptRefuseFact) = (EXCLUDED.acceptRefuseEvent, EXCLUDED.acceptRefuseFact)""" @@ -141,13 +141,13 @@ class InventoryHistoryJdbcRepository( /** * Get the record for the given UUID and version. */ - override def get(id: NodeId, version: DateTime): IOResult[FactLog] = { + override def get(id: NodeId, version: DateTime): IOResult[Option[FactLog]] = { val q = { sql"select nodeId, acceptRefuseEvent, acceptRefuseFact from nodefacts where nodeId = ${id.value} and acceptRefuseEvent ->>'date' = ${DateFormaterService .serialize(version)}" } - transactIOResult(s"error when getting node '${id.value}' accept/refuse fact")(xa => q.query[FactLog].unique.transact(xa)) + transactIOResult(s"error when getting node '${id.value}' accept/refuse fact")(xa => q.query[FactLog].option.transact(xa)) } /** diff --git a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/facts/nodes/TestCoreNodeFactInventory.scala b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/facts/nodes/TestCoreNodeFactInventory.scala index ece9e8b944c..5941172096a 100644 --- a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/facts/nodes/TestCoreNodeFactInventory.scala +++ b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/facts/nodes/TestCoreNodeFactInventory.scala @@ -236,7 +236,7 @@ class TestCoreNodeFactInventory extends Specification with BeforeAfterAll { n.vms ) - implicit val testChangeContext: ChangeContext = ChangeContext(ModificationId("test-mod-id"), EventActor("test"), None, None) + implicit val testChangeContext: ChangeContext = ChangeContext(ModificationId("test-mod-id"), EventActor("test"), DateTime.now(), None, None) "query action" should { diff --git a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/servers/TestRemoveNodeService.scala b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/servers/TestRemoveNodeService.scala index f04281bd0b6..b817b37e85f 100644 --- a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/servers/TestRemoveNodeService.scala +++ b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/servers/TestRemoveNodeService.scala @@ -38,7 +38,10 @@ package com.normation.rudder.services.servers import better.files._ import com.normation.eventlog.EventActor +import com.normation.eventlog.ModificationId import com.normation.inventory.domain.NodeId +import com.normation.rudder.facts.nodes.ChangeContext + import com.normation.zio._ import org.joda.time.DateTime import org.joda.time.format.ISODateTimeFormat @@ -91,13 +94,14 @@ class TestRemoveNodeService extends Specification with AfterAll { ) val cleanUp = new CleanUpNodePolicyFiles(varRudderShare.pathAsString) + implicit val testChangeContext: ChangeContext = ChangeContext(ModificationId("test-mod-id"), EventActor("test"), DateTime.now(), None, None) /* * */ "Policy directory should be cleaned" >> { startFS.foreach(_.createDirectories()) - cleanUp.run(NodeId("nodeXX"), DeleteMode.Erase, None, Set(), EventActor("test")).runNow + cleanUp.run(NodeId("nodeXX"), DeleteMode.Erase, None, Set()).runNow val files = varRudderShare.collectChildren(_ => true).toList.map(_.pathAsString) files must containTheSameElementsAs(expected.map(_.pathAsString)) diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala index e8811da3120..2d023cd5c22 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala @@ -773,7 +773,7 @@ class NodeApiService( template match { case AcceptedNodeTemplate(_, properties, policyMode, state) => newNodeManager - .accept(id)(ChangeContext(ModificationId(uuidGen.newUuid), eventActor, None, Some(actorIp))) + .accept(id)(ChangeContext(ModificationId(uuidGen.newUuid), eventActor, DateTime.now(), None, Some(actorIp))) .mapError(err => CreationError.OnAcceptation((s"Can not accept node '${id.value}': ${err.fullMsg}"))) *> NodeSetup(properties, policyMode, state).succeed case PendingNodeTemplate(_, properties) => @@ -1147,34 +1147,31 @@ class NodeApiService( def modifyStatusFromAction( ids: Seq[NodeId], action: NodeStatusAction, - modId: ModificationId, - actor: EventActor, - actorIp: String - ): Box[List[JValue]] = { - def actualNodeDeletion(id: NodeId, modId: ModificationId, actor: EventActor, actorIp: String) = { + )(implicit cc: ChangeContext): Box[List[JValue]] = { + def actualNodeDeletion(id: NodeId)(implicit cc: ChangeContext) = { for { optInfo <- nodeInfoService.getNodeInfo(id).toBox info <- optInfo match { case None => Failure(s"Can not removed the node with id '${id.value}' because it was not found") case Some(x) => Full(x) } - remove <- removeNodeService.removeNode(info.id, modId, actor, actorIp) + remove <- removeNodeService.removeNode(info.id) } yield { serializeNodeInfo(info, "deleted") } } (action match { case AcceptNode => newNodeManager - .acceptAll(ids)(ChangeContext(modId, actor, None, Some(actorIp))) + .acceptAll(ids) .map(_.map(cnf => serializeInventory(NodeFact.fromMinimal(cnf).toFullInventory, "accepted"))) case RefuseNode => newNodeManager - .refuseAll(ids)(ChangeContext(modId, actor, None, Some(actorIp))) + .refuseAll(ids) .map(_.map(cnf => serializeServerInfo(cnf.toSrv, "refused"))) case DeleteNode => - ZIO.foreach(ids)(actualNodeDeletion(_, modId, actor, actorIp).toIO) + ZIO.foreach(ids)(actualNodeDeletion(_).toIO) }).toBox.map(_.toList) } @@ -1193,7 +1190,7 @@ class NodeApiService( NodeLogger.PendingNode.debug(s" Nodes to change Status : ${ids.mkString("[ ", ", ", " ]")}") nodeStatusAction match { case Full(nodeStatusAction) => - modifyStatusFromAction(ids, nodeStatusAction, modId, actor, actorIp) match { + modifyStatusFromAction(ids, nodeStatusAction)(ChangeContext(modId, actor, DateTime.now(), None, Some(actorIp))) match { case Full(result) => toJsonResponse(None, ("nodes" -> JArray(result))) case eb: EmptyBox => @@ -1585,7 +1582,7 @@ class NodeApiService( implicit val action = "deleteNode" val modId = ModificationId(uuidGen.newUuid) - removeNodeService.removeNodePure(id, mode, modId, actor, actorIp).toBox match { + removeNodeService.removeNodePure(id, mode)(ChangeContext(modId, actor, DateTime.now(), None, Some(actorIp))).toBox match { case Full(info) => toJsonResponse(None, ("nodes" -> JArray(restSerializer.serializeNodeInfo(info, "deleted") :: Nil))) diff --git a/webapp/sources/rudder/rudder-rest/src/test/scala/com/normation/rudder/rest/TraitTestApiFromYamlFiles.scala b/webapp/sources/rudder/rudder-rest/src/test/scala/com/normation/rudder/rest/TraitTestApiFromYamlFiles.scala index 88fa986ed96..589f574c37c 100644 --- a/webapp/sources/rudder/rudder-rest/src/test/scala/com/normation/rudder/rest/TraitTestApiFromYamlFiles.scala +++ b/webapp/sources/rudder/rudder-rest/src/test/scala/com/normation/rudder/rest/TraitTestApiFromYamlFiles.scala @@ -350,8 +350,6 @@ trait TraitTestApiFromYamlFiles extends Specification with BoxSpecMatcher with J mockReq.contentType = mockReq.headers.get("Content-Type").flatMap(_.headOption).getOrElse("text/plain") // authorize space in response formatting - val expected = cleanBreakline(test.responseContent) - restTest.execRequestResponse(mockReq)(response => response.map(cleanResponse(_)) must equalsResponseCodeAndContent((test.responseCode, test.responseContent)) ) diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/checks/migration/MigrateNodeAcceptationInventories.scala b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/checks/migration/MigrateNodeAcceptationInventories.scala index b1771059191..7ff0938ba73 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/checks/migration/MigrateNodeAcceptationInventories.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/checks/migration/MigrateNodeAcceptationInventories.scala @@ -151,13 +151,16 @@ class MigrateNodeAcceptationInventories( _.headOption match { case None => ZIO.unit case Some(v) => - for { - last <- fileLogRepository.get(nodeId, v) - opt <- nodeInfoService.getNodeInfo(nodeId) - _ <- ZIO.when(opt.isDefined || last.datetime.plus(MAX_KEEP_REFUSED.toMillis).isAfter(now)) { - saveInDB(nodeId, last.datetime, last.data, !opt.isDefined) - } - } yield () + fileLogRepository.get(nodeId, v).flatMap { + case None => ZIO.unit + case Some(l) => + for { + opt <- nodeInfoService.getNodeInfo(nodeId) + _ <- ZIO.when(opt.isDefined || l.datetime.plus(MAX_KEEP_REFUSED.toMillis).isAfter(now)) { + saveInDB(nodeId, l.datetime, l.data, !opt.isDefined) + } + } yield () + } } } *> purgeLogFile(nodeId) } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala index 7aa6caa3e0f..cf48a3b0cac 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala @@ -52,6 +52,7 @@ import com.normation.rudder.domain.policies.PolicyModeOverrides._ import com.normation.rudder.domain.reports.ComplianceLevel import com.normation.rudder.domain.reports.ComplianceLevelSerialisation import com.normation.rudder.domain.reports.NodeStatusReport +import com.normation.rudder.facts.nodes.ChangeContext import com.normation.rudder.facts.nodes.SelectFacts import com.normation.rudder.hooks.HookReturnCode import com.normation.rudder.services.reports.NoReportInInterval @@ -1157,18 +1158,18 @@ object DisplayNode extends Loggable { } private[this] def removeNode(node: NodeSummary): JsCmd = { - val modId = ModificationId(uuidGen.newUuid) - removeNodeService - .removeNodePure( - node.id, - DeleteMode.Erase, - modId, - CurrentUser.actor, - S.request.map(_.remoteAddr).getOrElse("") - ) // only erase for Rudder 8.0 - .toBox match { + implicit val cc: ChangeContext = ChangeContext( + ModificationId(uuidGen.newUuid), + CurrentUser.actor, + DateTime.now(), + None, + S.request.map(_.remoteAddr).toOption + ) + + // only erase for Rudder 8.0 + removeNodeService.removeNodePure(node.id, DeleteMode.Erase).toBox match { case Full(_) => - asyncDeploymentAgent ! AutomaticStartDeployment(modId, CurrentUser.actor) + asyncDeploymentAgent ! AutomaticStartDeployment(cc.modId, cc.actor) onSuccess(node) case eb: EmptyBox => val message = s"There was an error while deleting node '${node.hostname}' [${node.id.value}]" diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/AcceptNode.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/AcceptNode.scala index ce4edab123c..152aa794fbf 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/AcceptNode.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/AcceptNode.scala @@ -144,9 +144,18 @@ class AcceptNode extends Loggable { // TODO : manage error message S.clearCurrentNotices listNode.foreach { id => + implicit val cc: ChangeContext = { + ChangeContext( + modId, + CurrentUser.actor, + DateTime.now(), + None, + S.request.map(_.remoteAddr).toOption + ) + } val now = System.currentTimeMillis val accept = - newNodeManager.accept(id)(ChangeContext(modId, CurrentUser.actor, None, S.request.map(_.remoteAddr).toOption)).toBox + newNodeManager.accept(id).toBox if (TimingDebugLogger.isDebugEnabled) { TimingDebugLogger.debug(s"Accepting node ${id.value}: ${System.currentTimeMillis - now}ms") } @@ -172,7 +181,9 @@ class AcceptNode extends Loggable { S.clearCurrentNotices val modId = ModificationId(uuidGen.newUuid) listNode.foreach { id => - newNodeManager.refuse(id)(ChangeContext(modId, CurrentUser.actor, None, S.request.map(_.remoteAddr).toOption)).toBox match { + newNodeManager + .refuse(id)(ChangeContext(modId, CurrentUser.actor, DateTime.now(), None, S.request.map(_.remoteAddr).toOption)) + .toBox match { case e: EmptyBox => logger.error(s"Refuse node '${id.value}' lead to Failure.", e) S.error(Error while refusing node(s).) diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/NodeHistoryViewer.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/NodeHistoryViewer.scala index f7826aa1442..bc2d8da23f2 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/NodeHistoryViewer.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/NodeHistoryViewer.scala @@ -75,9 +75,9 @@ class NodeHistoryViewer extends StatefulSnippet {

{SHtml.ajaxSelectObj[DateTime](dates, Full(selectedDate), onSelect _)}

{ historyRepos.get(uuid, selectedDate).toBox match { - case Failure(m, _, _) =>
Error while trying to display node history. Error message: {m}
- case Empty =>
No history was retrieved for the chosen date
- case Full(sm) => + case Failure(m, _, _) =>
Error while trying to display node history. Error message: {m}
+ case Empty | Full(None) =>
No history was retrieved for the chosen date
+ case Full(Some(sm)) =>
{ DisplayNode.showPannedContent(None, sm.data.fact.toFullInventory, sm.data.status, "hist") ++ Script(DisplayNode.jsInit(sm.id, sm.data.fact.toFullInventory.node.softwareIds, "hist")) @@ -117,9 +117,9 @@ class NodeHistoryViewer extends StatefulSnippet { private def onSelect(date: DateTime): JsCmd = { historyRepos.get(uuid, date).toBox match { - case Failure(m, _, _) => Alert("Error while trying to display node history. Error message:" + m) - case Empty => Alert("No history was retrieved for the chosen date") - case Full(sm) => + case Failure(m, _, _) => Alert("Error while trying to display node history. Error message:" + m) + case Empty | Full(None) => Alert("No history was retrieved for the chosen date") + case Full(Some(sm)) => SetHtml(hid, DisplayNode.showPannedContent(None, sm.data.fact.toFullInventory, sm.data.status, "hist")) & DisplayNode.jsInit(sm.id, sm.data.fact.toFullInventory.node.softwareIds, "hist") } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/PendingHistoryGrid.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/PendingHistoryGrid.scala index 01e5f54b2cc..584c9d57868 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/PendingHistoryGrid.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/node/PendingHistoryGrid.scala @@ -136,7 +136,7 @@ object PendingHistoryGrid extends Loggable { ("tr [jsuuid]" #> jsuuid & "tr [serveruuid]" #> details.nodeId.value & "tr [kind]" #> status.toLowerCase & - "tr [inventory]" #> details.inventoryVersion.toString() & + "tr [inventory]" #> DateFormaterService.serialize(event.creationDate) & ".date *" #> DateFormaterService.getDisplayDate(event.creationDate) & ".name *" #> details.hostname & ".os *" #> details.fullOsName & @@ -220,9 +220,9 @@ object PendingHistoryGrid extends Loggable { val version = ISODateTimeFormat.dateTimeParser.parseDateTime(arr(2)) val isAcceptLine = arr(3) == "accepted" history.get(id, version).toBox match { - case Failure(m, _, _) => Alert("Error while trying to display node history. Error message:" + m) - case Empty => Alert("No history was retrieved for the chosen date") - case Full(sm) => + case Failure(m, _, _) => Alert("Error while trying to display node history. Error message:" + m) + case Empty | Full(None) => Alert("No history was retrieved for the chosen date") + case Full(Some(sm)) => SetHtml( jsuuid, (if (isAcceptLine) diff --git a/webapp/sources/rudder/rudder-web/src/test/scala/bootstrap/liftweb/checks/migration/TestMigrateNodeAcceptationInventories.scala b/webapp/sources/rudder/rudder-web/src/test/scala/bootstrap/liftweb/checks/migration/TestMigrateNodeAcceptationInventories.scala index 4008681ac4f..886f69c4047 100644 --- a/webapp/sources/rudder/rudder-web/src/test/scala/bootstrap/liftweb/checks/migration/TestMigrateNodeAcceptationInventories.scala +++ b/webapp/sources/rudder/rudder-web/src/test/scala/bootstrap/liftweb/checks/migration/TestMigrateNodeAcceptationInventories.scala @@ -185,11 +185,20 @@ trait TestMigrateNodeAcceptationInventories extends Specification with AfterAll } yield files.toSeq.map(f => NodeId(f.name)) } - override def get(id: NodeId, version: DateTime): IOResult[FactLog] = { - for { - json <- IOResult.attempt(s"Read json for ${id.value}}")(factFile(id, version).contentAsString) - fact <- json.fromJson[NodeFact].toIO - } yield FactLog(id, version, FactLogData(fact, EventActor("rudder-migration"), AcceptedInventory)) + override def get(id: NodeId, version: DateTime): IOResult[Option[FactLog]] = { + val file = factFile(id, version) + ZIO + .whenZIO(IOResult.attempt(file.exists)) { + IOResult.attempt(s"Read json for ${id.value}}")(file.contentAsString) + } + .flatMap { + case Some(json) => + for { + fact <- json.fromJson[NodeFact].toIO + } yield Some(FactLog(id, version, FactLogData(fact, EventActor("rudder-migration"), AcceptedInventory))) + case None => + None.succeed + } } override def versions(id: NodeId): IOResult[Seq[DateTime]] = {