Modules/StackDefinition.psm1


Import-Module (Join-Path (Split-Path $PSScriptRoot -Parent) "Helpers\GeneralHelper.psm1") -Force

function Get-StackDefinition
{
    [OutputType([String])]
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory=$True, 
            Position=0
        )]
        [ValidateScript({Test-Path $_})]
        [String]$Path
    )

    return ConvertFrom-YamlFile $Path
}

function Set-StackDefinition
{
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory=$True, 
            Position=0
        )]
        [ValidateScript({Test-ObjectValidYaml $_})]
        [Alias('sd', 'definition', 'def')]
        [HashTable]$StackDefinition,

        [Parameter(
            Mandatory=$True, 
            Position=1
        )]
        [String]$Path
    )

    $Parent = Split-Path $Path -Parent

    if (!([String]::Empty -eq $Parent) -And !(Test-Path $Parent)) {
        New-Item $Parent -ItemType Directory
    }

    (ConvertTo-Yaml $StackDefinition) | Set-Content $Path -Force
}

function Invoke-StackDefinitionInterpolation
{
    [CmdletBinding(DefaultParameterSetName='InPlace')]
    Param(
        [Parameter(
            Mandatory=$True, 
            Position=0
        )]
        [ValidateScript({Test-ObjectValidYaml $_})]
        [Alias('sd', 'definition', 'def')]
        [Hashtable]$StackDefinition,

        [Parameter(
            Mandatory=$True, 
            Position=1
        )]
        [ValidateScript({ Test-Path $_ })]
        [ValidateScript({ try { ConvertFrom-YamlFile $_; return $True } catch { return $False } })]
        [String]$Path,

        [Parameter(
            Mandatory=$False, 
            Position=2,
            ParameterSetName='NotInPlace'
        )]
        [Switch]$NotInPlace,

        [Parameter(
            Mandatory=$True, 
            Position=3,
            ParameterSetName='NotInPlace'
        )]
        [String]$Destination,

        [Switch]$Passthru
    )

    Write-Verbose "Starting Interpolation..."
    if ($NotInPlace) {
        $Parent = Split-Path $Destination -Parent
    }
    else {
        $Parent = Split-Path $Path -Parent
    }
    $NewDefinition = Invoke-Interpolation $StackDefinition (ConvertFrom-YamlFile $Path)
    $NewDefinition.Version = "'$($NewDefinition.Version)'"
    Write-Verbose "Finished Interpolation."

    if (!([String]::Empty -eq $Parent) -And !(Test-Path $Parent)) {
        Write-Verbose "Creating Directory: $Parent"
        New-Item $Parent -ItemType Directory
    }

    if ($Passthru) {
        Write-Verbose "Outputting Passthru..."
        return $NewDefinition
    }

    Write-Verbose "Setting $Path to interpolated YAML."
    if ($NotInPlace) {
        (ConvertTo-Yaml $NewDefinition) | Set-Content $Destination -Force
    }
    else {
        (ConvertTo-Yaml $NewDefinition) | Set-Content $Path -Force
    }
}

function Invoke-Interpolation
{
    [OutputType([Hashtable])]
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory=$True, 
            Position=0
        )]
        [Alias('sd', 'definition', 'def', 'sdef')]
        [Object] $StackDefinition,

        [Parameter(
            Mandatory=$True, 
            Position=1
        )]
        [Alias('nd', 'newdef', 'ndef')]
        [Object]$NewDefinition
    )

    Write-Verbose "Merging properties of source definition and new definition"
    $Extended = Add-PropertyRecurse $StackDefinition $NewDefinition
    $Clone    = $Extended.Clone()

    $Extended `
    | Get-Member -Type NoteProperty, Property `
    | ForEach-Object {
        Write-Verbose "Removing non-stack object $($_.Name) from new definition"
        $Clone.PSObject.Properties.Remove($_.Name)
    }
    $Extended.GetEnumerator() `
    | Where-Object { $_.Key -Like 'x-*' } `
    | ForEach-Object { $Clone.Remove($_.Key) }

    return $Clone
}

function Add-PropertyRecurse
{
    [OutputType([Hashtable])]
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory=$False, 
            Position=0
        )]
        [Alias('sd', 'definition', 'def', 'sdef')]
        [Object] $StackDefinition,

        [Parameter(
            Mandatory=$False, 
            Position=1
        )]
        [Alias('nd', 'newdef', 'ndef')]
        [Object]$NewDefinition
    )

    if ($StackDefinition -Is [Hashtable]) {
        $Clone = $StackDefinition.Clone()
        foreach ($Property in $Clone.GetEnumerator()) {
            Write-Verbose "Encountered $($Property.Key)"
            if ($Null -eq $NewDefinition.Item($Property.Key)) {
                $Type = $StackDefinition.Item($Property.Key).GetType()
                if ($Type.Name -eq "Object[]") {
                    Write-Verbose "$($Property.Key) is a mapping sequence -> declaring it as Object[]"
                    $NewDefinition.Item($Property.Key) = New-Object @(@{})
                }
                elseif ($Type.Name -ne "String") {
                    Write-Verbose "$($Property.Key) is not a primitive type -> declaring it as $($Type.Name)"
                    $NewDefinition.Item($Property.Key) = New-Object $Type
                }

                Write-Verbose "Setting the value of $($Property.Key) to $($StackDefinition.Item($Property.Key))"
                $NewDefinition.Item($Property.Key) = $StackDefinition.Item($Property.Key)
            }
            else {
                Write-Verbose "Recursing Add-PropertyRecurse on $($Property.Key)"
               $NewDefinition.Item($Property.Key) = Add-PropertyRecurse $StackDefinition.Item($Property.Key) $NewDefinition.Item($Property.Key)
            }
        }
    }
    else {
        Write-Verbose "Setting the value of new definition to $($StackDefinition)"
        $NewDefinition = $StackDefinition
    }

    return $NewDefinition
}