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 #5225: allows to use ${rudder.node.env.VAR} syntax to get env variable in directives #572

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.normation.rudder.domain.parameters.ParameterName
import com.normation.utils.HashcodeCaching
import net.liftweb.common.Box
import scala.collection.immutable.TreeMap
import com.normation.inventory.domain.NodeInventory

/*
* Immutable bridge between cfclerk and rudder
Expand All @@ -59,6 +60,7 @@ import scala.collection.immutable.TreeMap
case class InterpolationContext(
nodeInfo : NodeInfo
, policyServerInfo: NodeInfo
, inventory : NodeInventory
//environment variable for that server
//must be a case insensitive Map !!!!
, nodeContext : TreeMap[String, Variable]
Expand All @@ -80,6 +82,7 @@ object InterpolationContext {
def apply(
nodeInfo : NodeInfo
, policyServerInfo: NodeInfo
, inventory : NodeInventory
//environment variable for that server
//must be a case insensitive Map !!!!
, nodeContext : Map[String, Variable]
Expand All @@ -91,7 +94,7 @@ object InterpolationContext {
//for ex: param a => param b => param c => ..... => param a
//should not be evaluated
, depth : Int = 0
) = new InterpolationContext(nodeInfo, policyServerInfo, TreeMap(nodeContext.toSeq:_*), parameters, depth)
) = new InterpolationContext(nodeInfo, policyServerInfo, inventory, TreeMap(nodeContext.toSeq:_*), parameters, depth)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ import com.normation.utils.HashcodeCaching
import net.liftweb.common._
import com.normation.rudder.domain.parameters.GlobalParameter
import scala.collection.immutable.TreeMap
import com.normation.inventory.services.core.ReadOnlyFullInventoryRepository
import com.normation.inventory.domain.NodeInventory
import com.normation.inventory.domain.AcceptedInventory



Expand All @@ -85,9 +88,10 @@ trait DeploymentService extends Loggable {
val rootNodeId = Constants.ROOT_POLICY_SERVER_ID

val result = for {

//fetch all - yep, memory is cheap... (TODO: size of that for 1000 nodes, 100 rules, 100 directives, 100 groups ?)
allRules <- findDependantRules() ?~! "Could not find dependant rules"
allNodeInfos <- getAllNodeInfos ?~! "Could not get Node Infos"
allInventories <- getAllInventories ?~! "Could not get Node inventories"
directiveLib <- getDirectiveLibrary() ?~! "Could not get the directive library"
groupLib <- getGroupLibrary() ?~! "Could not get the group library"
allParameters <- getAllGlobalParameters ?~! "Could not get global parameters"
Expand All @@ -110,7 +114,7 @@ trait DeploymentService extends Loggable {
_ = logger.debug(s"Global system variables built in ${timeGlobalSystemVar}ms, start to build new node configurations.")

buildConfigTime = System.currentTimeMillis
config <- buildNodeConfigurations(ruleVals, allNodeInfos, groupLib, allParameters, globalSystemVariables) ?~! "Cannot build target configuration node"
config <- buildNodeConfigurations(ruleVals, allNodeInfos, allInventories, groupLib, allParameters, globalSystemVariables) ?~! "Cannot build target configuration node"
timeBuildConfig = (System.currentTimeMillis - buildConfigTime)
_ = logger.debug(s"Node's target configuration built in ${timeBuildConfig}, start to update rule values.")

Expand Down Expand Up @@ -173,6 +177,7 @@ trait DeploymentService extends Loggable {
def getDirectiveLibrary(): Box[FullActiveTechniqueCategory]
def getGroupLibrary(): Box[FullNodeGroupCategory]
def getAllGlobalParameters: Box[Seq[GlobalParameter]]
def getAllInventories(): Box[Map[NodeId, NodeInventory]]

/**
* Find all modified rules.
Expand Down Expand Up @@ -209,6 +214,7 @@ trait DeploymentService extends Loggable {
def buildNodeConfigurations(
ruleVals : Seq[RuleVal]
, allNodeInfos : Map[NodeId, NodeInfo]
, allInventories : Map[NodeId, NodeInventory]
, groupLib : FullNodeGroupCategory
, parameters : Seq[GlobalParameter]
, globalSystemVariable: Map[String, Variable]
Expand Down Expand Up @@ -287,6 +293,7 @@ class DeploymentServiceImpl (
, override val ruleApplicationStatusService: RuleApplicationStatusService
, override val parameterService : RoParameterService
, override val interpolatedValueCompiler:InterpolatedValueCompiler
, override val roInventoryRepository: ReadOnlyFullInventoryRepository
) extends DeploymentService with
DeploymentService_findDependantRules_bruteForce with
DeploymentService_buildRuleVals with
Expand Down Expand Up @@ -317,12 +324,14 @@ trait DeploymentService_findDependantRules_bruteForce extends DeploymentService
def roNodeGroupRepository: RoNodeGroupRepository
def roDirectiveRepository: RoDirectiveRepository
def parameterService : RoParameterService
def roInventoryRepository: ReadOnlyFullInventoryRepository

override def findDependantRules() : Box[Seq[Rule]] = roRuleRepo.getAll(true)
override def getAllNodeInfos(): Box[Map[NodeId, NodeInfo]] = nodeInfoService.getAll
override def getDirectiveLibrary(): Box[FullActiveTechniqueCategory] = roDirectiveRepository.getFullDirectiveLibrary()
override def getGroupLibrary(): Box[FullNodeGroupCategory] = roNodeGroupRepository.getFullGroupLibrary()
override def getAllGlobalParameters: Box[Seq[GlobalParameter]] = parameterService.getAllGlobalParameters()
override def getAllInventories(): Box[Map[NodeId, NodeInventory]] = roInventoryRepository.getAllNodeInventories(AcceptedInventory)
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -410,19 +419,22 @@ trait DeploymentService_buildNodeConfigurations extends DeploymentService with L
private[this] def buildInterpolationContext(
nodeIds : Set[NodeId]
, allNodeInfos : Map[NodeId, NodeInfo]
, allInventories : Map[NodeId, NodeInventory]
, parameters : Map[ParameterName, InterpolationContext => Box[String]]
, globalSystemVariables: Map[String, Variable]
): Map[NodeId, InterpolationContext] = {

(nodeIds.flatMap { nodeId:NodeId =>
(for {
nodeInfo <- Box(allNodeInfos.get(nodeId)) ?~! s"Node with ID ${nodeId.value} was not found"
nodeInfo <- Box(allNodeInfos.get(nodeId)) ?~! s"Node with ID ${nodeId.value} was not found"
inventory <- Box(allInventories.get(nodeId)) ?~! s"Inventory for node with ID ${nodeId.value} was not found"
policyServer <- Box(allNodeInfos.get(nodeInfo.policyServerId)) ?~! s"Node with ID ${nodeId.value} was not found"
nodeContext <- systemVarService.getSystemVariables(nodeInfo, allNodeInfos, globalSystemVariables)
nodeContext <- systemVarService.getSystemVariables(nodeInfo, allNodeInfos, globalSystemVariables)
} yield {
(nodeId, InterpolationContext(
nodeInfo
, policyServer
, inventory
, nodeContext
, parameters
)
Expand Down Expand Up @@ -456,6 +468,7 @@ trait DeploymentService_buildNodeConfigurations extends DeploymentService with L
override def buildNodeConfigurations(
ruleVals : Seq[RuleVal]
, allNodeInfos : Map[NodeId, NodeInfo]
, allInventories : Map[NodeId, NodeInventory]
, groupLib : FullNodeGroupCategory
, parameters : Seq[GlobalParameter]
, globalSystemVariables: Map[String, Variable]
Expand Down Expand Up @@ -535,7 +548,7 @@ trait DeploymentService_buildNodeConfigurations extends DeploymentService with L
//1.2: for each node, build the interpolation context
//this also give us the list of actual node to consider

val interpolationContexts = buildInterpolationContext(policyDraftByNode.keySet, allNodeInfos, interpolatedParameters, globalSystemVariables)
val interpolationContexts = buildInterpolationContext(policyDraftByNode.keySet, allNodeInfos, allInventories, interpolatedParameters, globalSystemVariables)

//1.3: build node config, binding ${rudder.parameters} parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import com.normation.rudder.domain.policies.InterpolationContext
import com.normation.utils.Control._
import net.liftweb.common.{Failure => FailedBox, _}
import com.normation.rudder.domain.parameters.ParameterName
import com.normation.inventory.domain.NodeInventory

/**
* A parser that handle parameterized value of
Expand Down Expand Up @@ -83,7 +84,9 @@ import com.normation.rudder.domain.parameters.ParameterName
* ${rudder.node.policyserver.ACCESSOR} : information about the policyserver of the node.
* ACCESSORs are the same than for ${rudder.node}
*
*
* ${rudder.node.env.ENVIRONMENT_VARIABLE_NAME}: the value of the environment variable "ENVIRONMENT_VARIABLE_NAME"
* (name case sensitive) as given in the last inventory for that node, or "" if there is no variable
* with that name was defined for that node when the inventory was done.
*/
trait InterpolatedValueCompiler {

Expand Down Expand Up @@ -206,15 +209,29 @@ class InterpolatedValueCompilerImpl extends RegexParsers with InterpolatedValueC
}

def checkNodeAccessor(context: InterpolationContext, path: List[String]): Box[String] = {
def environmentVariable(node: NodeInventory, envVarName: String): String = {
node.environmentVariables.find( _.name == envVarName) match {
case None => ""
case Some(v) => v.value.getOrElse("")
}
}

val error = FailedBox(s"Unknow interpolated variable $${node.${path.mkString(".")}}" )
path match {
case Nil => FailedBox("In node interpolated variable, at least one accessor must be provided")
case "id" :: Nil => Full(context.nodeInfo.id.value)
case "hostname" :: Nil => Full(context.nodeInfo.hostname)
case "admin" :: Nil => Full(context.nodeInfo.localAdministratorAccountName)
case "policyserver" ::"id" :: Nil => Full(context.policyServerInfo.id.value)
case "policyserver" ::"hostname" :: Nil => Full(context.policyServerInfo.hostname)
case "policyserver" ::"admin" :: Nil => Full(context.policyServerInfo.localAdministratorAccountName)
case seq => FailedBox(s"Unknow interpolated variable $${node.${seq.mkString(".")}}" )
case access :: tail => access.toLowerCase :: tail match {
case "id" :: Nil => Full(context.nodeInfo.id.value)
case "hostname" :: Nil => Full(context.nodeInfo.hostname)
case "admin" :: Nil => Full(context.nodeInfo.localAdministratorAccountName)
case "policyserver" :: tail2 => tail2 match {
case "id" :: Nil => Full(context.policyServerInfo.id.value)
case "hostname" :: Nil => Full(context.policyServerInfo.hostname)
case "admin" :: Nil => Full(context.policyServerInfo.localAdministratorAccountName)
case _ => error
}
case "env" :: x :: Nil => Full(environmentVariable(context.inventory, x))
case seq => error
}
}
}

Expand Down Expand Up @@ -245,7 +262,7 @@ class InterpolatedValueCompilerImpl extends RegexParsers with InterpolatedValueC

//a node path looks like: ${rudder.node.HERE.PATH}
def nodeProp: Parser[Interpolation] = {
id("node") ~> "." ~> repsep(propId, ".") ^^ { seq => NodeAccessor(seq.map(_.toLowerCase)) }
id("node") ~> "." ~> repsep(propId, ".") ^^ { seq => NodeAccessor(seq) }
}

//a parameter looks like: ${rudder.PARAM_NAME}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,14 @@ import org.specs2.mutable._
import org.specs2.specification._
import com.normation.cfclerk.domain.InputVariableSpec
import com.normation.cfclerk.domain.Variable
import com.normation.inventory.domain.COMMUNITY_AGENT
import com.normation.inventory.domain.NodeId
import com.normation.inventory.domain._
import com.normation.rudder.domain.nodes.NodeInfo
import com.normation.rudder.domain.parameters.ParameterName
import com.normation.rudder.domain.policies.InterpolationContext
import com.normation.rudder.services.policies.nodeconfig.ParameterForConfiguration
import net.liftweb.common._
import com.normation.utils.Control.sequence
import com.normation.rudder.domain.policies.InterpolationContext
import com.normation.rudder.domain.policies.InterpolationContext
import com.normation.rudder.domain.policies.InterpolationContext
import scala.collection.immutable.TreeMap
//for treemap ordering
import com.normation.rudder.domain.policies.InterpolationContext
import InterpolationContext._


Expand Down Expand Up @@ -101,6 +96,7 @@ class TestNodeAndParameterLookup extends Specification {
, isPolicyServer= true
, serverRoles = Set()
)

val root = NodeInfo(
id = rootId
, name = "root"
Expand All @@ -123,9 +119,42 @@ class TestNodeAndParameterLookup extends Specification {
, serverRoles = Set()
)

val nodeInventory1: NodeInventory = NodeInventory(
NodeSummary(
node1.id
, AcceptedInventory
, node1.localAdministratorAccountName
, node1.hostname
, Linux(Debian, "test machine", new Version("1.0"), None, new Version("3.42"))
, root.id
)
, name = None
, description = None
, ram = None
, swap = None
, inventoryDate = None
, receiveDate = None
, archDescription = None
, lastLoggedUser = None
, lastLoggedUserTime = None
, agentNames = Seq()
, publicKeys = Seq()
, serverIps = Seq()
, machineId = None //if we want several ids, we would have to ass an "alternate machine" field
, softwareIds = Seq()
, accounts = Seq()
, environmentVariables = Seq(EnvironmentVariable("THE_VAR", Some("THE_VAR value!")))
, processes = Seq()
, vms = Seq()
, networks = Seq()
, fileSystems = Seq()
, serverRoles = Set()
)

val context = InterpolationContext(
parameters = Map()
, nodeInfo = node1
, inventory = nodeInventory1
, policyServerInfo= root
//environment variable for that server
, nodeContext = Map()
Expand Down Expand Up @@ -239,14 +268,14 @@ class TestNodeAndParameterLookup extends Specification {
}
}

def compileAndGet(s:String) = compiler.compile(s).openOrThrowException("Initialisation test error")

/**
* Test that the interpretation of an AST is
* correctly done (with forged interpretation contexts)
*/
"Interpretation of a parsed interpolated string" should {

def compileAndGet(s:String) = compiler.compile(s).openOrThrowException("Initialisation test error")

val nodeId = compileAndGet("${rudder.node.uuid}")
val policyServerId = compileAndGet("${rudder.node.id}")
Expand Down Expand Up @@ -300,25 +329,6 @@ class TestNodeAndParameterLookup extends Specification {
i(c) must beEqualTo(Full(res))
}

"no care of case in nodes names" in {
val i = compileAndGet("${rudder.node.HoStNaMe}")
i(context) must beEqualTo(Full("node1.localhost"))
}

"DO care of case in param names" in {
val i = compileAndGet("${rudder.param.xX}")
val c = context.copy(parameters = Map(
//test all combination
(ParameterName("XX"), (i:InterpolationContext) => Full("bad"))
, (ParameterName("Xx"), (i:InterpolationContext) => Full("bad"))
, (ParameterName("xx"), (i:InterpolationContext) => Full("bad"))
))
i(c) match {
case Full(_) => ko("No, case must matter!")
case Empty => ko("No, we should have a failure")
case Failure(m,_,_) => m must beEqualTo("Error when trying to interpolate a variable: Rudder parameter not found: 'xX'")
}
}

"fails on missing param in context" in {
val res = "p1 replaced"
Expand Down Expand Up @@ -497,6 +507,33 @@ class TestNodeAndParameterLookup extends Specification {
)
)
}

"not matter in nodes path accessor" in {
val i = compileAndGet("${rudder.node.HoStNaMe}")
i(context) must beEqualTo(Full("node1.localhost"))
}

"matter in environement variable name" in {
val i = compileAndGet("${rudder.node.env.THE_VAR}")
val j = compileAndGet("${rudder.node.env.THE_var}")
(i(context) must beEqualTo(Full("THE_VAR value!"))) and (j(context) must beEqualTo(Full("")))
}

"matter in param names" in {
val i = compileAndGet("${rudder.param.xX}")
val c = context.copy(parameters = Map(
//test all combination
(ParameterName("XX"), (i:InterpolationContext) => Full("bad"))
, (ParameterName("Xx"), (i:InterpolationContext) => Full("bad"))
, (ParameterName("xx"), (i:InterpolationContext) => Full("bad"))
))
i(c) match {
case Full(_) => ko("No, case must matter!")
case Empty => ko("No, we should have a failure")
case Failure(m,_,_) => m must beEqualTo("Error when trying to interpolate a variable: Rudder parameter not found: 'xX'")
}
}

}

}
4 changes: 2 additions & 2 deletions rudder-web/src/main/scala/bootstrap/liftweb/AppConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1193,8 +1193,8 @@ object RudderConfig extends Loggable {
roDirectiveRepository,
ruleApplicationStatusImpl,
roParameterServiceImpl,
interpolationCompiler

interpolationCompiler,
ldapFullInventoryRepository
)
, eventLogDeploymentServiceImpl
, deploymentStatusSerialisation)
Expand Down