initial commit

zachilde/minver
Chris Kuech 2018-10-11 18:26:56 -07:00
parent d5423e71f8
commit 564902483f
4 changed files with 379 additions and 0 deletions

181
Checklist.psm1 Normal file
View File

@ -0,0 +1,181 @@
enum Status {
NotRun
Pass
Fail
}
class Log {
# We originally used [Status] values as keys instead of casting [Status] values to strings.
# We cast values to strings for now due to a bug in PSScriptAnalyzer v1.16.1
static [hashtable] $Symbols = @{
Pass = [char]8730
Fail = "X"
NotRun = " "
}
static [hashtable] $Colors = @{
Pass = "Green"
NotRun = "Yellow"
Fail = "Red"
}
static [int] $LastLineLength
static [void] WriteLine([string] $message, [Status] $status) {
$message = [Log]::FormatMessage($message, $status)
[Log]::LastLineLength = $message.Length
$color = [Log]::Colors[ [string]$status ]
Write-Host $message -ForegroundColor $color -NoNewline
}
static [void] OverwriteLine([string] $message, [Status] $status) {
Write-Host "`r$(' ' * [Log]::LastLineLength)" -NoNewline
$message = [Log]::FormatMessage($message, $status)
[Log]::LastLineLength = $message.Length
$color = [Log]::Colors[ [string]$status ]
Write-Host "`r$message" -ForegroundColor $color
}
static [string] FormatMessage([string] $message, [Status] $status) {
$symbol = [Log]::Symbols[ [string]$status ]
return "$(Get-Date -Format 'hh:mm:ss') [ $symbol ] $message"
}
static [void] WriteError([string] $message) {
Write-Host "`n$message`n" -ForegroundColor Red
exit -1
}
}
<#
.SYNOPSIS
Ensures a requirement is met.
.DESCRIPTION
This cmdlet allows for declaratively defining requirements and implementing consistent logging and idempotency around the status of the requirements.
.PARAMETER Describe
A description of the requirement that is enforced.
.PARAMETER Test
If present, 'Test' is a scriptblock that returns 'true' if the requirement is already met and the 'Set' scriptblock should not run. If not present, 'Set' will always run.
.PARAMETER Set
A scriptblock that imposes the requirement when run. If a "Test' scriptblock is not provided, 'Set' must be idempotent.
.PARAMETER Message
An error message printed if an idempotent 'Set' scriptblock fails during execution.
.EXAMPLE
# A non-idempotent 'Set' scriptblock
Invoke-ChecklistRequirement `
-Describe "'Hello world' is logged" `
-Test {Get-Content $MyLogFilePath | ? {$_ -eq "Hello world"}} `
-Set {"Hello world" >> $MyLogFilePath}
# An idempotent 'Set' scriptblock
Invoke-ChecklistRequirement `
-Describe "'Hello world' is logged" `
-Set {"Hello world" > $MyLogFilePath} `
-Message "Could not log 'Hello World'"
#>
function Invoke-ChecklistRequirement {
Param(
[Parameter(Mandatory, ParameterSetName = "ApplyIfNeeded")]
[Parameter(Mandatory, ParameterSetName = "ApplyAlways")]
[Parameter(Mandatory, ParameterSetName = "Information")]
[ValidateNotNullOrEmpty()]
[string] $Describe,
[Parameter(Mandatory, ParameterSetName = "ApplyIfNeeded")]
[ValidateNotNullOrEmpty()]
[scriptblock] $Test,
[Parameter(Mandatory, ParameterSetName = "ApplyIfNeeded")]
[Parameter(Mandatory, ParameterSetName = "ApplyAlways")]
[ValidateNotNullOrEmpty()]
[scriptblock] $Set,
[Parameter(Mandatory, ParameterSetName = "ApplyAlways")]
[ValidateNotNullOrEmpty()]
[string] $Message,
[switch] $ListRequirement
)
try {
if ($ListRequirement) {
[Log]::WriteLine("$Describe`n", [Status]::NotRun)
return
}
switch ($PSCmdlet.ParameterSetName) {
"ApplyIfNeeded" {
[Log]::WriteLine($Describe, [Status]::NotRun)
if (&$Test) {
[Log]::OverwriteLine($Describe, [Status]::Pass)
}
else {
&$Set | Out-Null
if (&$Test) {
[Log]::OverwriteLine($Describe, [Status]::Pass)
}
else {
[Log]::OverwriteLine($Describe, [Status]::Fail)
[Log]::WriteError("Requirement validation failed")
}
}
}
"ApplyAlways" {
[Log]::WriteLine($Describe, [Status]::NotRun)
if (&$Set) {
[Log]::OverwriteLine($Describe, [Status]::Pass)
}
else {
[Log]::OverwriteLine($Describe, [Status]::Fail)
[Log]::WriteError($Message)
}
}
"Information" {
[Log]::WriteLine("$Describe`n", [Status]::NotRun)
}
}
}
catch {
[Log]::OverwriteLine($Describe, [Status]::Fail)
Write-Host ""
throw $_
}
}
function Invoke-ChecklistDscRequirement {
Param(
[string]$Describe,
[string]$ResourceName,
[string]$ModuleName,
[hashtable]$Property
)
$dscParams = @{
Name = $ResourceName
ModuleName = $ModuleName
Property = $Property
}
Invoke-ChecklistRequirement `
-Describe $Describe `
-Test {Invoke-DscResource -Method "Test" @dscParams} `
-Set {Invoke-DscResource -Method "Set" @dscParams}
}

101
Context.psm1 Normal file
View File

@ -0,0 +1,101 @@
# TODO: (MEDIUM) Implement cmdlets wrapping the class implementation
# TODO: (MEDIUM) Break out Configuration\ConfigurationManager into Modules\ConfigurationManager
# TODO: (MEDIUM) Ensure stack traces propogate from module functions
using namespace System.Collections.Generic
$ErrorActionPreference = "Stop"
$InformationPreference = "Continue"
$LogContext = [Stack[string]]::new()
Import-Module "Profile"
$DockerLinePrefix = " ----->"
function Write-Log {
Param(
[Parameter(Mandatory, Position=0, ParameterSetName="PushContext")]
[Parameter(Mandatory, Position=0, ParameterSetName="ExistingContext")]
[string] $Context,
[Parameter(Mandatory, Position=1, ParameterSetName="PushContext")]
[scriptblock] $ScriptBlock
)
switch ($PSCmdlet.ParameterSetName) {
"PushContext" {
$LogContext.Push($Context)
Write-Log "<begin>"
$result = &$ScriptBlock
Write-Log "<end>"
$LogContext.Pop() | Out-Null
if ($result -is [object]) {
return $result
} else {
return $null
}
}
"ExistingContext" {
$stack = $LogContext.ToArray()
[array]::Reverse($stack)
$prefix = "$DockerLinePrefix $(Get-Date -Format "hh:mm:ss") [$($stack -join " > ")]"
Write-Information "$prefix $Context"
}
}
}
function Invoke-ContextRequirement {
Param(
[string]$Name,
[scriptblock]$Test,
[scriptblock]$Set
)
try {
Write-Log $Name {
$requirementAlreadyMet = Write-Log "Test" {&$Test}
if (-not $requirementAlreadyMet) {
Write-Log "Set" {&$Set | Out-Null}
$requirementValidated = Write-Log "Test" {&$Test}
if (-not $requirementValidated) {
throw "Requirement validation failed"
}
}
}
}
catch {
Write-Error $_.Exception
}
}
function Invoke-ContextDscRequirement {
Param(
[string]$Name,
[string]$ResourceName,
[string]$ModuleName,
[hashtable]$Property
)
$dscParams = @{
Name = $ResourceName
ModuleName = $ModuleName
Property = $Property
}
Invoke-Requirement `
-Name $Name `
-Test {Invoke-DscResource -Method "Test" @dscParams} `
-Set {Invoke-DscResource -Method "Set" @dscParams}
}

BIN
Requirements.psd1 Normal file

Binary file not shown.

97
classes.ps1 Normal file
View File

@ -0,0 +1,97 @@
using namespace System.Collections.Generic
$ErrorActionPreference = "Stop"
$InformationPreference = "Continue"
#$VerbosePreference = "Continue"
Import-Module "Profile"
class Resource {
[bool] Test() {
throw "Abstract method"
return $false
}
[void] Set() {
throw "Abstract method"
}
[string] ToString() {
throw "Abstract method"
return ""
}
static [void] Run([resource[]]$resources) {
foreach ($resource in $resources) {
Write-Log $resource.Name {
$pass = [bool](Write-Log "Test" {$resource.Test()})
if (-not $pass) {
Write-Log "Set" {$resource.Set()}
$pass = [bool](Write-Log "Test" {$resource.Test()})
if (-not $pass) {
Write-Log "Failed"
throw "$($resource.Name) failed to install"
}
}
}
}
}
}
class Extension : Resource {
static [string] $ConfigurationFileName = "Extensions.json"
static [string] $Container = "C:\Extensions"
static [PSCustomObject] $Configuration
[ValidateNotNullOrEmpty()]
[string] $Name
[ValidateNotNullOrEmpty()]
[string] $Path
[ValidateNotNullOrEmpty()]
[hashtable] $BaseParameters
static Extension() {
$fileName = [Extension]::ConfigurationFileName
[Extension]::Configuration = Get-MergedConfig $fileName
}
Extension([string] $name) {
$this.Name = $name
$this.Path = [Extension]::Container + "\$name"
$this.BaseParameters = @{
Service = $env:Service
FlightingRing = $env:FlightingRing
Region = $env:Region
}
$this.BaseParameters[$this.Name] = [Extension]::Configuration.($this.Name)
}
[bool] Test() {
$script = $this.Path + "\test.ps1"
$params = $this.GetParameters($script)
return &$script @params
}
[void] Set() {
$script = $this.Path + "\set.ps1"
$params = $this.GetParameters($script)
&$script @params
}
[hashtable] GetParameters([string] $script) {
$params = @{}
$supportedParameters = (Get-Command $script).ScriptBlock.Ast.ParamBlock.Parameters.Name `
| % {$_ -replace "\$"}
$this.BaseParameters.Keys `
| ? {$_ -in $supportedParameters} `
| % {$params[$_] = $this.BaseParameters[$_]}
return $params
}
}