Created
July 17, 2018 04:02
-
-
Save GeoffWilliams/df7345a6cd440f825759460b844d4e39 to your computer and use it in GitHub Desktop.
configure an application by looking up stuff from a .json file and writing XMLs from it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #<# | |
| #.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 | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment