Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #<#
- #.Synopsis
- # Rewrite configuration files based on hiera data
- #.Description
- # ..
- #
- #.Parameter DataFile
- # Exported data from hiera (JSON)
- #.Parameter AppData
- # File to read configurable settings from
- #.Parameter DryRun
- # In dry run mode, we exit with status 100 to indicate changes needed but do not save the file
- #
- #.Example
- # # Reconfigure all files
- # configure_app
- #
- ##>
- param(
- [string] $DataFile = "C:\vagrant\mock.json",
- [string] $AppData = "C:\vagrant\appdata.txt",
- [switch] $DryRun = $false
- )
- function ChildKeyFromXpath {
- param(
- [string] $XPath,
- [switch] $IgnoreMissing = $false
- )
- $childKeyCapture = [regex]::Match($xPath, '@[^=]+=''([^'']+?)'']/[^/]+$').captures.groups
- if ($childKeyCapture.length -ne 2) {
- if (-not $IgnoreMissing) {
- write-error "unable to find childKey in $($xPath) - xPath needs to map to an element in hiera eg //foo[@key='bar']/@value to lookup bar"
- exit 1
- }
- $childKey = $null
- } else {
- $childKey = $childKeyCapture[1]
- }
- return $childKey
- }
- function ReadXml {
- param(
- [string] $FilePath
- )
- # Read all the xml and do the edit at the requested xPath
- if(-not [System.IO.File]::Exists($FilePath)){
- write-error "File not found reading $($FilePath)"
- exit 1
- }
- [xml] $xml = Get-Content($FilePath)
- if ($xml -eq $null -or $xml.ChildNodes.Count -eq 0 ) {
- write-error "Could not parse XML from $($FilePath) - invalid file content"
- exit 1
- }
- return $xml
- }
- # Ensure XML fragment exists at correct location
- function Ensure-XmlFragment {
- param(
- [string]$FilePath,
- [string]$XPath,
- [string]$ParentKey
- )
- # remove the `ENSURE:` from the start of the XPath
- $XPath = $XPath -replace "ENSURE:", ""
- $xml = ReadXml -FilePath $FilePath
- # XPath should look like this:
- # //configuration/system.serviceModel/client/endpoint[@name='SystemEventsService']/identity
- # which means we need to get a reference to:
- # 1. //configuration/system.serviceModel/client/endpoint[@name='SystemEventsService']
- # 2. the child `identity`
- $captures = [regex]::Match($xPath, '^(.*?)/([^/]+)$').captures.groups
- $xPathBase = $captures[1]
- $targetElement = $captures[2]
- $nodes = (Select-Xml -Xml $xml -XPath $xPathBase)
- if ($nodes.node.count -eq 0) {
- write-error "XPath expression $($xPathBase) matches no nodes"
- exit 1
- }
- $childKey = ChildKeyFromXpath -xPath $XPath -IgnoreMissing
- $data = DataLookup -parentKey $ParentKey -childKey $childKey
- $target = $nodes.node.$targetElement
- if ($target -eq $null) {
- # child (`identity` in above example) doesn't exist yet - create it
- $target = $xml.CreateElement($targetElement)
- $nodes.node.AppendChild($target) | Out-Null
- }
- # update the xml string inside the target (`identity`) to reflect the data from hiera
- if ($target.InnerXml -ne $data) {
- $target.InnerXml = $data
- if ($DryRun) {
- write-host "needs updating"
- exit 100
- } else {
- $xml.save($FilePath)
- }
- }
- }
- # Set an XML attribute to a particular values
- function Set-AttributeValue {
- Param(
- [string]$FilePath,
- [string]$XPath,
- [string]$ParentKey
- )
- $xPathSplit = $XPath -split("/@")
- if ($xPathSplit.Length -ne 2) {
- write-error "xpath input is not correct - needs to capture an element, eg //foo[@key='bar']/@value but you sent '$($XPath)'"
- exit 1
- }
- $xPathBase = $xPathSplit[0]
- $xPathAttrib = $xPathSplit[1]
- $childKey = ChildKeyFromXpath -xPath $xPath
- # lookup the value to use for this config item
- $value = DataLookup -parentKey $parentKey -childKey $childKey
- $xml = ReadXml -FilePath $FilePath
- $nodes = (Select-Xml -Xml $xml -XPath $xPathBase)
- if ($nodes -eq $null) {
- # There must be a "slot" for us to insert values in the XMLs - we are not in the business
- # of creating parent nodes any more
- write-error "missing parent - create $($xPathBase) first and be aware of namespaces in xpath expressions"
- exit 1
- } else {
- if ($nodes.Node.Attributes[$xPathAttrib] -ne $null) {
- if ($nodes.Node.Attributes[$xPathAttrib].Value -eq $value) {
- $changesNeeded = $false
- } else {
- $nodes.Node.Attributes[$xPathAttrib].Value = $value
- $changesNeeded = $true
- }
- } else {
- $nodes.Node.SetAttribute($xPathAttrib,$value)
- $changesNeeded = $true
- }
- if ($changesNeeded -and $DryRun) {
- write-host "Changes are required"
- exit 100
- }
- }
- if (-not $DryRun) {
- $xml.Save($FilePath)
- }
- }
- #.Synopsys
- # Lookup data (from JSON file simulating hiera lookup)
- #.Parameter parentKey
- # The key to ask hiera for, eg `profile::foo::bar`
- #.Parameter childKey
- # Assuming the data returned from looking up `parentKey` is a hash, return the contents of this child element. If omitted,
- # Return all data looked up from parentKey
- function DataLookup {
- param(
- [String] $parentKey,
- [String] $childKey
- )
- $json = Get-Content $DataFile | ConvertFrom-Json
- if ([string]::IsNullOrEmpty($childKey)) {
- $data = $json.$parentKey
- } else {
- $data = $json.$parentKey.$childKey
- }
- if ($data -eq $null) {
- write-error "No data in hiera for '$($parentKey)' element '$($childKey)' - fix this!"
- exit 25
- }
- return $data
- }
- $workingFile = $null
- # appdata.txt is a simple textfile listing each app setting that must be managed
- # Format:
- # [c:\file\to\edit.xml]
- # XPATH==PARENT KEY IN HIERA
- # ENSURE:XPATH==PARENT KEY IN HIERA
- # Real example:
- # [C:\vagrant\web_config.xml]
- # //configuration/appSettings/add[@key='smtpHostName']/@value==boards::ipadserver::settings::appSettings
- # ENSURE://configuration/system.serviceModel/client/endpoint[@name='SystemEventsService']/identity==boards::ipadserver::spn
- #
- # The XPATH *must* take the form above, since this tells the script:
- # 1. How to find existing settings (attribute predicate: `key=SmtpHostName`, `name=SystemEventService`)
- # 2. Which attribute or element to write to (at the end of the XPATH: `/@value`, `/identity`)
- # 3. The element under the parent key to find the data in hiera (the value the predicate searches for: `smtpHostName`, `SystemEventService`)
- # 4. How to perform the update (line starts `ENSURE` we are adding a blob of xmltext otherwise we are setting an attribute)
- foreach($line in Get-Content $AppData) {
- # skip blank lines and comments
- $line = $line.trim()
- if ((-not ($line -match '^\s*[\n\r]*$')) -and (-not ($line -match '^\s*#'))) {
- if ($line.StartsWith('[')) {
- # Specify the current working file
- $workingFile = $line -replace "[][]", ""
- write-host "Working file set to $($workingFile)"
- } else {
- # must be something to lookup
- $lineSplit = $line -split('==')
- $xPath = $lineSplit[0]
- $parentKey = $lineSplit[1]
- if ($lineSplit.Length -eq 2) {
- if ($workingFile -eq $null) {
- write-error "There is no working file! specify it in square brackets in appdata.txt before items to configure"
- exit 1
- }
- if($line.StartsWith("ENSURE:")) {
- Ensure-XmlFragment -FilePath $workingFile -XPath $xPath -ParentKey $parentKey
- } else {
- Set-AttributeValue -FilePath $workingFile -XPath $xPath -ParentKey $parentKey
- }
- } else {
- write-error "item to lookup is not in correct format - should be XPATH==HIERA KEY but got: \n $($line)"
- exit 1
- }
- }
- }
- }
Add Comment
Please, Sign In to add comment