Skip to content

Instantly share code, notes, and snippets.

@JeffDarchuk
Created December 27, 2019 14:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JeffDarchuk/4aa455e4816210d24d09c69d964c7fb2 to your computer and use it in GitHub Desktop.
Save JeffDarchuk/4aa455e4816210d24d09c69d964c7fb2 to your computer and use it in GitHub Desktop.
Unicorn scanner to alert on potential errors in unicorn configurations
param(
[string] $path
)
function Expand-Tokens{
param(
[string] $configName,
[string] $path
)
$root = $configName.Split(".")
if ($root.Length -ge 1){
$path = $path.replace("`$(module)", $root[$root.Length-1])
}
if ($root.Length -ge 2){
$path = $path.replace("`$(layer)", $root[$root.Length-2])
}
if ($root.Length -ge 3){
$path = $path.replace("`$(tenant)", $root[$root.Length-3])
}
return $path
}
function Expand-ConfigToDescendants{
param(
[hashtable] $trie,
[string] $config,
[string] $dependencies,
[System.Xml.XmlElement] $include,
[int] $depth = -1
)
$s = new-object system.collections.stack
$s.Push($trie)
while ($s.Count -gt 0 -and $depth -ne 0){
$depth--
$tmp = $s.pop()
$null = $tmp.config[$config] = @{
dependencies = $dependencies
include = $include
includeChildren = $true
}
$tmp.parent.config.values | foreach-object {
if ($_.includeChildren -and -not $tmp.config.ContainsKey($_.config)){
$tmp[$_.config] = $_
}
}
$tmp.next.Keys | foreach-object{
$s.Push($tmp.next[$_])
}
}
return $trie
}
function Expand-Trie{
param(
[hashtable] $trie,
[string] $key,
[string] $dependencies,
[System.Xml.XmlElement] $include,
[string] $config = $null
)
if (-not $trie.next.ContainsKey($key)){
$trie.next[$key] = @{
node = $key
config = @{}
next = @{}
parent = $trie
path = "$($trie.path)/$key"
database = $trie.database
}
}
if ($null -ne $config -and $config -ne ""){
$null = $trie.next[$key].config[$config] = @{
config = $config
dependencies = $dependencies
include = $include
includeChildren = $true
}
$trie.parent.config.values | foreach-object {
if ($_.includeChildren -and -not $trie.config.ContainsKey($_.config)){
$trie.config[$_.config] = $_
}
}
$mainPath = Expand-Tokens -path $include.GetAttribute("path") -configName $config
$parent = $trie.next[$key]
if ($include.exclude.Count -ne 0){
$include.exclude | foreach-object{
if ($_.GetAttribute("children").ToLower() -eq "true"){
$trie.next[$key].config[$config].includeChildren = $false
}
$path = Expand-Tokens -path $_.GetAttribute("path").Replace($mainPath, "") -configName $config
if ($path.Length -eq 0){
$path = Expand-Tokens -path $_.GetAttribute("childrenOfPath").Replace($mainPath, "") -configName $config
}
if ($path.Length -ne 0){
$tmp = $parent
$path.Split("/") | foreach-object{
if (-not $tmp.next.ContainsKey($_)){
$tmp.next[$_] = @{
node = $_
config = @{}
next = @{}
parent = $tmp
path = "$($tmp.path)/$_"
database = $tmp.database
}
}
$null = $tmp.next[$_].config[$config] = @{
config = $config
dependencies = $dependencies
include = $include
includeChildren = $false
}
$tmp.parent.config.values | foreach-object {
if ($_.includeChildren -and -not $tmp.config.ContainsKey($_.config)){
$tmp.config[$_.config] = $_
}
}
$tmp = $tmp.next[$_]
}
}else{
$children = $_.GetAttribute("children")
if ($children.ToLower() -eq "true"){
$_.except | foreach-object{
if ($null -ne $_){
$exceptName = $_.GetAttribute("name")
$exceptIncludeChildren = $_.GetAttribute("includeChildren").ToLower() -eq "true"
$tmp = $parent
if ($exceptName -eq "*"){
Expand-ConfigToDescendants -trie $parent -config $config -depth 1 -dependencies $dependencies -include $include
}else{
$exceptName.Split("/") | foreach-object {
if (-not $tmp.next.ContainsKey($_)){
$tmp.next[$_] = @{
node = $_
config = @{}
next = @{}
parent = $tmp
path = "$($tmp.path)/$_"
database = $tmp.database
}
}
$null = $tmp.next[$_].config[$config] = @{
config = $config
dependencies = $dependencies
include = $include
includeChildren = $exceptIncludeChildren
}
$tmp.parent.config.values | foreach-object {
if ($_.includeChildren -and -not $tmp.config.ContainsKey($_.config)){
$tmp.config[$_.config] = $_
}
}
$tmp = $tmp.next[$_]
}
if ($exceptIncludeChildren){
Expand-ConfigToDescendants -trie $parent -config $config -dependencies $dependencies -include $include
}
}
}
}
}
}
}
}
}
$trie = $trie.next[$key]
return $trie
}
function Traverse-Path{
param(
[hashtable] $Trie,
[string] $database,
[string] $config,
[string] $path,
[string] $dependencies,
[System.Xml.XmlElement] $include
)
$isCore = $database -eq "core"
if ($isCore){
$Trie = $Trie.core
}else{
$Trie = $Trie.master
}
$paths = $path.Split("/")
for ($i = 0; $i -lt $paths.Length-1;$i++){
if ($paths[$i] -ne ""){
$Trie = Expand-Trie -trie $Trie -key $paths[$i] -dependencies $dependencies -include $include
}
}
#write-host "$($trie.path)/$($paths[$paths.Length - 1]) - $config" -ForegroundColor red
$Trie = Expand-Trie -trie $Trie -key $paths[$paths.Length - 1] -dependencies $dependencies -include $include -config $config
}
function Build-Trie{
param (
[hashtable] $Trie,
[System.Xml.XmlElement] $xml,
[hashtable] $Abstract
)
$config = $xml.GetAttribute("name")
$extends = $xml.GetAttribute("extends")
while($extends -ne ""){
if ($null -ne $Abstract[$extends].include){
$Abstract[$extends].include | ForEach-Object{
Traverse-Path -Trie $Trie -database $_.GetAttribute("database") -config $config -path (Expand-Tokens -path $_.GetAttribute("path") -configName $config) -dependencies $_.ParentNode.ParentNode.GetAttribute("dependencies") -include $_
}
$extends = $Abstract[$extends].extends
}else{
$extends = ""
}
}
$xml.predicate.include | ForEach-Object {
if ($null -ne $_){
Traverse-Path -Trie $Trie -database $_.GetAttribute("database") -config $config -path (Expand-Tokens -path $_.GetAttribute("path") -configName $config) -dependencies $_.ParentNode.ParentNode.GetAttribute("dependencies") -include $_
}
}
}
$mainTrie = @{
master = @{
node = "sitecore"
config = @{}
next = @{}
parent = $null
path = ""
database = "master"
}
core = @{
node = "sitecore"
config = @{}
next = @{}
parent = $null
path = ""
database = "core"
}
dependencies = @{}
}
$abstract = @{}
$xmls = New-Object System.Collections.Generic.List[System.Xml.XmlElement]
get-childitem -Path $path -Filter *.config -Recurse -Exclude node_modules | Foreach-Object{
$xml = $null
try{
$xml = [xml] (Get-Content $_.FullName)
}catch{}
if ($null -ne $xml -and $xml.configuration.sitecore.unicorn -ne $null -and -not $_.FullName.Contains("\obj\")){
$xml.configuration.sitecore.unicorn.configurations.configuration | foreach-object{
if ($null -ne $_){
$configuration = $_
$isAbstract = $null -ne $configuration.GetAttribute("abstract") -and $configuration.GetAttribute("abstract").ToLower() -eq "true"
if ($isAbstract){
$_.predicate.include | Foreach-Object {
if ($_ -ne $null){
if (-not $abstract.ContainsKey($_.ParentNode.ParentNode.GetAttribute("name"))){
$abstract.Add($_.ParentNode.ParentNode.GetAttribute("name"), @{
include = (New-Object System.Collections.Generic.List[[System.Xml.XmlElement]])
extends = $_.ParentNode.ParentNode.GetAttribute("extends")
})
}
$abstract[$_.ParentNode.ParentNode.GetAttribute("name")].include.Add($_)
}
}
}else{
$xmls.Add($configuration)
$mainTrie.dependencies[$configuration.GetAttribute("name")] = @{
text = $configuration.GetAttribute("dependencies")
}
}
}
}
}
}
$mainTrie.dependencies.Keys | foreach-object {
$dep = $mainTrie.dependencies[$_]
$dep.dependson = New-Object System.Collections.Generic.HashSet[string]
$s = new-object system.collections.stack
$s.Push($dep)
while ($s.Count){
$childDep = $s.Pop()
$individualDependencies = $childDep.text.Split(",")
$individualDependencies | foreach-object {
$dependency = $_.trim()
if ($dependency.EndsWith("*")){
$wildcard = $dependency.Replace("*", "")
$mainTrie.dependencies.Keys | foreach-object{
if ($_.startsWith($wildcard)){
$null = $dep.dependson.Add($_)
if ($mainTrie.dependencies.ContainsKey($_)){
$s.Push($mainTrie.dependencies[$_])
}
}
}
}else{
$null = $dep.dependson.Add($dependency)
if ($mainTrie.dependencies.ContainsKey($dependency)){
$s.Push($mainTrie.dependencies[$dependency])
}
}
}
}
}
$xmls | ForEach-Object {
Build-Trie -Trie $mainTrie -xml $_ -Abstract $abstract
}
return $mainTrie
param(
[hashtable] $Trie,
[switch] $ErrorOnOrphans,
[switch] $ErrorOnDependencyMismatch
)
write-host "`n=======================================================================================" -ForegroundColor Cyan
write-host "Beginning scan of unicorn configurations for potential errors" -ForegroundColor Cyan
write-host "=======================================================================================`n" -ForegroundColor Cyan
$s = new-object system.collections.stack
$s.Push($Trie.core)
$s.Push($Trie.master)
$PathRequirementsMaster = New-Object System.Collections.Generic.HashSet[string]
$PathRequirementsCore = New-Object System.Collections.Generic.HashSet[string]
$Orphans = New-Object System.Collections.Generic.HashSet[string]
while ($s.Count -ne 0){
$t = $s.Pop()
if ($t.config.Count -ne 0 -and $t.parent.config.Count -eq 0){
if ($t.database -eq "master"){
$null = $PathRequirementsMaster.Add($t.parent.path)
}elseif($t.database -eq "core"){
$null = $PathRequirementsCore.Add($t.parent.path)
}
}
if ($t.config.Count -eq 0 -and $t.parent.config.Count -ne 0){
$null = $Orphans.Add($t.path)
}
$parent = $t.parent
if ($t.config.Count -gt 0){
$t.config.Keys | foreach-object {
$config = $t.config[$_].config
$path = $t.path
while ($null -ne $parent){
$found = $false
if ($parent.config.Count -ne 0){
$parent.config.keys | foreach-object{
if (-not $found -and ($parent.config[$_].config -eq $config -or $Trie.dependencies[$config].dependson.Contains($parent.config[$_].config))){
$found = $true
}
}
}else{
$found = $true
}
if (-not $found){
if ($ErrorOnDependencyMismatch){
write-error "The following is a report on a dependency mismatch."
}else{
write-host "`nIssue found with configuration:" -ForegroundColor red
write-host "`n`tConfiguration " -NoNewline
write-host $config -ForegroundColor Magenta -NoNewline
write-host " has a path dependency on one of [" -NoNewline
write-host $parent.config.Keys -ForegroundColor Magenta -NoNewline
write-host "] that isn't explicitly defined"
write-host "`n`t$($t.database) => $($path)" -ForegroundColor Magenta
}
}
$parent = $parent.parent
}
}
}
$t.next.keys | Foreach-object {
$s.Push($t.next[$_])
}
}
if ($Orphans.Count -ne 0){
write-host "`n=======================================================================================" -ForegroundColor Cyan
write-host "Orphans detected, items which appear to be required that aren't tracked by any configs" -ForegroundColor Cyan
write-host "=======================================================================================`n" -ForegroundColor Cyan
if ($ErrorOnOrphans){
write-error "The following are orphans detected."
}
$Orphans | Sort-Object -Property Length | Foreach-Object {
write-host "`t$_" -ForegroundColor Magenta
}
}
write-host "`n=======================================================================================" -ForegroundColor Cyan
write-host "Configurations require that these paths exist as part of the default state of Sitecore" -ForegroundColor Cyan
write-host "=======================================================================================`n" -ForegroundColor Cyan
write-host "Core:" -ForegroundColor Cyan
$PathRequirementsCore | Sort-Object -Property Length | Foreach-Object {
write-host "`t$_"
}
write-host "Master:" -ForegroundColor Cyan
$PathRequirementsMaster | Sort-Object -Property Length | Foreach-Object {
write-host "`t$_"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment