Resolve-CloudFormationTemplate.ps1

#requires -Version 5.0

<#PSScriptInfo
 
.VERSION 1.0
 
.GUID 3ef0cacf-ac76-487a-8bec-dbf437d67f42
 
.AUTHOR Alex Sarafian
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS Github
 
.LICENSEURI https://github.com/Sarafian/PowerShellScripts/blob/master/LICENSE
 
.PROJECTURI https://github.com/Sarafian/PowerShellScripts
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
#>


<#
.Synopsis
   Resolves the conditions of a AWS cloud formation template
.DESCRIPTION
   Resolves the conditions of a AWS cloud formation template
   1. Flip to json first when input is yaml
   1. Execute cft-simulate from Dome9/cft-simulator
   1. Flip response to yaml if the input is yaml
.EXAMPLE
   Resolve-CloudFormationTemplate
.LINK
   https://github.com/awslabs/aws-cfn-template-flip
.LINK
   https://nodejs.org/en/
.LINK
   https://github.com/Dome9/cft-simulator
.LINK
   https://github.com/Sarafian/PowerShellScripts
.LINK
   https://www.powershellgallery.com/packages/Get-Github/
.EXAMPLE
    $splat=@{
        CFTPath="cft.json"
        CFTParameters='{"env":"prod","a":123}'
        CFTResolvedPath="cft.resolved.json"
    }
    Resolve-CloudFormationTemplate @splat -JSON
.EXAMPLE
    $splat=@{
        CFTPath="cft.yaml"
        CFTParameters='{"env":"prod","a":123}'
        CFTResolvedPath="cft.resolved.yaml"
    }
    Resolve-CloudFormationTemplate @splat -YAML
 
#>

param(
    [Parameter(Mandatory=$true,ParameterSetName="JSON")]
    [Parameter(Mandatory=$true,ParameterSetName="YAML")]
    [string]$CFTPath,
    [Parameter(Mandatory=$true,ParameterSetName="JSON")]
    [Parameter(Mandatory=$true,ParameterSetName="YAML")]
    [string]$CFTParameters,
    [Parameter(Mandatory=$true,ParameterSetName="JSON")]
    [Parameter(Mandatory=$true,ParameterSetName="YAML")]
    [string]$CFTResolvedPath,
    [Parameter(Mandatory=$true,ParameterSetName="JSON")]
    [switch]$JSON,
    [Parameter(Mandatory=$true,ParameterSetName="YAML")]
    [switch]$YAML
)

begin {
    Write-Debug "PSCmdlet.ParameterSetName=$($PSCmdlet.ParameterSetName)"
    foreach($psbp in $PSBoundParameters.GetEnumerator()){Write-Debug "$($psbp.Key)=$($psbp.Value)"}

    $CFTItem=Get-Item -Path $CFTPath
}
process {
    #region Initialize JSON. Flip YAML if necessary
    switch($PSCmdlet.ParameterSetName)
    {
        'JSON' {
            $JSONPath=$CFTItem
        }
        'YAML' {
            Write-Warning "cft-simulator supports only JSON only JSON. Using cfn-flip to convert $($CFTItem.FullName) to JSON format"
            $cfnFlipPath=& C:\Windows\System32\where.exe cfn-flip
            $isCFNFlipInstalled=$LASTEXITCODE -eq 0

            if(-not $isCFNFlipInstalled)
            {
                throw "cfn-flip.exe not available. Install with pip install pip install cfn_flip"
            }
            $JSONPath=Join-Path -Path $env:TEMP -ChildPath ($CFTItem.Name.Replace(".yaml",".json"))
            $cfnFlipArgs=@(
                $CFTItem.FullName
                $JSONPath
            )
            & cfn-flip $cfnFlipArgs 2>&1
        }
    } 
    #endregion

    #region Make sure NodeJS is installed
    $nodePath=& C:\Windows\System32\where.exe node
    $isNodeInstalled=$LASTEXITCODE -eq 0
    $npmPath=& C:\Windows\System32\where.exe npm
    $isNPMInstalled=$LASTEXITCODE -eq 0

    if(-not ($isNodeInstalled -and $isNPMInstalled))
    {
        throw "node.exe or npm.cmd not available. Install NodeJS"
    }
    $npmPath=$npmPath|Select-Object -First 1 -Skip 1
    #endregion

    #region Bootstrap cft-simulator
    # To avoid delays do this only on daily basis
    # 1. Download the code on a daily basis. Expected location C:\Users\myusername\AppData\Local\Temp\20170830-Resolve-CloudFormationTemplate\cft-simulator-master
    # 2. Do npm install

    $tempFolder=Join-Path -Path $env:TEMP -ChildPath "$(Get-Date -Format "yyyyMMdd")-Resolve-CloudFormationTemplate"
    if(-not (Test-Path -Path $tempFolder))
    {
        Write-Warning "Today's cft-simulator cached not found. Initializing"

        Write-Progress -Activity "Bootstrap cft-simulator" -Status "Downloading"
        $null=New-Item -Path $tempFolder -ItemType Directory
        $cftSimulatorUri="https://github.com/Dome9/cft-simulator/archive/master.zip"
        $downloadFileName="cft-simulator-master.zip"
        $downloadPath=Join-Path -Path $tempFolder -ChildPath $downloadFileName
        Invoke-WebRequest -Uri $cftSimulatorUri -UseBasicParsing -OutFile $downloadPath
        Write-Progress -Activity "Bootstrap cft-simulator" -Status "Expanding"
        Expand-Archive -Path $downloadPath -DestinationPath $tempFolder
        Push-Location -Path "$tempFolder\cft-simulator-master" -StackName npm

        Write-Progress -Activity "Bootstrap cft-simulator" -Status "NPM Installing"
        try{
            $npmOutputPath=Join-Path -Path $tempFolder -ChildPath "npm-install.out"
            $npmErrorPath=Join-Path -Path $tempFolder -ChildPath "npm-install.err"
            $process=Start-Process -FilePath $npmPath -ArgumentList @("install") -Wait -NoNewWindow -PassThru -RedirectStandardOutput $npmOutputPath -RedirectStandardError $npmErrorPath
            if($process.ExitCode -ne 0)
            {
                Get-Content -Path $npmOutputPath -Raw
                Get-Content -Path $npmErrorPath -Raw
                throw "Could not initialize cft-simulator"
            }
        }
        finally
        {
            Pop-Location -StackName npm
            Write-Progress -Activity "Bootstrap cft-simulator" -Completed
        }

    }
    else
    {
        Write-Verbose "Using today's cft-simulator cached download"
    }
    $cftSimulatorJSPath=Join-Path -Path $tempFolder -ChildPath "cft-simulator-master\cft-simulator.js"
    
    #endregion

    #region Run cft-simulator
    $nodeArgs=@(
        "cft-simulator.js"
        "-p"
        $CFTParameters.Replace('"', '\"')
        $JSONPath
    )
    Write-Verbose "$nodePath $($nodeArgs -join ' ')"

    $nodeOutputPath=Join-Path -Path $tempFolder -ChildPath "npm-install.out"
    $nodeErrorPath=Join-Path -Path $tempFolder -ChildPath "npm-install.err"

    $process=Start-Process -FilePath $nodePath -ArgumentList $nodeArgs -PassThru -NoNewWindow -Wait -WorkingDirectory "$tempFolder\cft-simulator-master" -RedirectStandardOutput $nodeOutputPath -RedirectStandardError $nodeErrorPath
    #endregion

    #region Process result
    if($process.ExitCode -eq 0)
    {
        switch($PSCmdlet.ParameterSetName)
        {
            'JSON' {
                Get-Content -Path $nodeOutputPath -Raw|ConvertFrom-Json|ConvertTo-Json |Out-File -FilePath $CFTResolvedPath -NoNewline
            }
            'YAML' {
                $cfnFlipArgs=@(
                    $nodeOutputPath
                    $CFTResolvedPath
                )
                & cfn-flip $cfnFlipArgs 2>&1
            }
        } 

        $result=$true
    }
    else
    {
        Write-Warning "cft-simulator executed with errors"
        Copy-Item -Path $nodeErrorPath -Destination $CFTResolvedPath -Force
        $result=$false
    }
    $result
    #endregion
}

end {

}