зеркало из https://github.com/microsoft/AL-Go.git
317 строки
12 KiB
PowerShell
317 строки
12 KiB
PowerShell
$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0
|
|
|
|
$env:GITHUB_OUTPUT = [System.IO.Path]::GetTempFileName()
|
|
|
|
function iReplace {
|
|
Param(
|
|
[string] $string,
|
|
[string] $source,
|
|
[string] $replace
|
|
)
|
|
|
|
if ("$source" -eq "") {
|
|
throw "source is empty"
|
|
}
|
|
do {
|
|
$idx = $string.IndexOf($source, [System.StringComparison]::InvariantCultureIgnoreCase)
|
|
if ($idx -ge 0) {
|
|
$string = "$($string.SubString(0,$idx))$replace$($string.SubString($idx+$source.Length))"
|
|
}
|
|
} while ($idx -ge 0)
|
|
$string
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Get the action script from a script file
|
|
#>
|
|
function GetActionScript {
|
|
Param(
|
|
[string] $scriptRoot,
|
|
[string] $scriptName
|
|
)
|
|
|
|
$scriptPath = Join-Path $ScriptRoot $scriptName -Resolve
|
|
$actionname = [System.IO.Path]::GetFileNameWithoutExtension($scriptPath)
|
|
|
|
$actionScript = Get-Content -raw -path $scriptPath
|
|
$actionScript = "function $actionName {`n$actionScript`n}"
|
|
|
|
# resolve psscriptroot references
|
|
$actionScript = iReplace -string $actionScript -source '$psscriptroot' -replace "'$scriptRoot'"
|
|
$actionScript
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Test the yaml structure of an action
|
|
#>
|
|
function YamlTest {
|
|
Param(
|
|
[string] $scriptRoot,
|
|
[string] $actionName,
|
|
[string] $actionScript,
|
|
$permissions = @{},
|
|
$outputs = @{}
|
|
)
|
|
|
|
$emptyActionScript = "function emptyAction {`n[CmdletBinding()]`nParam()`n}`n"
|
|
Invoke-Expression $emptyActionScript
|
|
$emptyCmd = get-command emptyAction
|
|
$systemParameters = @($emptyCmd.Parameters.Keys.GetEnumerator() | ForEach-Object { $_ })
|
|
|
|
Invoke-Expression $actionScript
|
|
|
|
$yaml = [System.Text.StringBuilder]::new()
|
|
$yaml.AppendLine("name: *") | Out-Null
|
|
$yaml.AppendLine("author: *") | Out-Null
|
|
if ($permissions -and $permissions.Count -gt 0) {
|
|
$yaml.AppendLine("permissions:") | Out-Null
|
|
$permissions.Keys | ForEach-Object {
|
|
$yaml.AppendLine(" $($_): $($permissions."$_")") | Out-Null
|
|
}
|
|
}
|
|
$cmd = get-command $actionname
|
|
$yaml.AppendLine("inputs:") | Out-Null
|
|
$yaml.AppendLine(" shell:") | Out-Null
|
|
$yaml.AppendLine(" description: Shell in which you want to run the action (powershell or pwsh)") | Out-Null
|
|
$yaml.AppendLine(" required: false") | Out-Null
|
|
$yaml.AppendLine(" default: powershell") | Out-Null
|
|
$parameterString = ""
|
|
$warningLines = @()
|
|
$envLines = [System.Text.StringBuilder]::new()
|
|
if ($cmd.Parameters.Count -gt 0) {
|
|
$cmd.Parameters.GetEnumerator() | ForEach-Object {
|
|
$name = $_.Key
|
|
if ($name -notin $systemParameters) {
|
|
$value = $_.Value
|
|
$description = $value.ParameterSets.__allParameterSets.HelpMessage
|
|
if (!($description)) { $description = "*" }
|
|
$required = $value.ParameterSets.__allParameterSets.IsMandatory
|
|
$type = $value.ParameterType.ToString()
|
|
$yaml.AppendLine(" $($name):") | Out-Null
|
|
$yaml.AppendLine(" description: $description") | Out-Null
|
|
if ($name -ne 'GitHubSecrets') {
|
|
$envLines.AppendLine(" _$($name): `${{ inputs.$($name) }}")
|
|
}
|
|
$yaml.AppendLine(" required: $($required.ToString().ToLowerInvariant())") | Out-Null
|
|
if ($type -eq "System.String" -or $type -eq "System.Int32") {
|
|
if ($name -eq 'GitHubSecrets') {
|
|
$parameterString += ' -gitHubSecrets ''${{ inputs.gitHubSecrets }}'''
|
|
}
|
|
else {
|
|
$parameterString += " -$($name) `$ENV:_$($name)"
|
|
}
|
|
if (!$required) {
|
|
$yaml.AppendLine(" default: *") | Out-Null
|
|
}
|
|
}
|
|
elseif ($type -eq "System.Boolean") {
|
|
$parameterString += " -$($name) (`$ENV:_$($name) -eq 'true')"
|
|
if (!$required) {
|
|
$yaml.AppendLine(" default: 'false'") | Out-Null
|
|
}
|
|
}
|
|
else {
|
|
throw "Unknown parameter type: $type. Only String, Int and Bool allowed"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($outputs -and $outputs.Count -gt 0) {
|
|
$yaml.AppendLine("outputs:") | Out-Null
|
|
$outputs.Keys | ForEach-Object {
|
|
$yaml.AppendLine(" $($_):") | Out-Null
|
|
$yaml.AppendLine(" description: $($outputs."$_")") | Out-Null
|
|
$yaml.AppendLine(" value: `${{ steps.$($actionname.ToLowerInvariant()).outputs.$($_) }}") | Out-Null
|
|
}
|
|
}
|
|
$yaml.AppendLine("runs:") | Out-Null
|
|
$yaml.AppendLine(" using: composite") | Out-Null
|
|
$yaml.AppendLine(" steps:") | Out-Null
|
|
$yaml.AppendLine(" - name: run") | Out-Null
|
|
$yaml.AppendLine(' shell: ${{ inputs.shell }}') | Out-Null
|
|
if ($outputs -and $outputs.Count -gt 0) {
|
|
$yaml.AppendLine(" id: $($actionname.ToLowerInvariant())") | Out-Null
|
|
}
|
|
if ($envLines.Length -gt 0) {
|
|
$yaml.AppendLine(" env:") | Out-Null
|
|
$yaml.Append($envLines.ToString())
|
|
}
|
|
if ($warningLines) {
|
|
$yaml.AppendLine(" run: |") | Out-Null
|
|
# Add the warning lines
|
|
$warningLines | ForEach-Object {
|
|
$yaml.AppendLine(" $_") | Out-Null
|
|
}
|
|
}
|
|
else {
|
|
$yaml.AppendLine(" run: |") | Out-Null
|
|
}
|
|
$yaml.AppendLine(" `${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName `"$actionName`" -Action {") | Out-Null
|
|
$yaml.AppendLine(" `${{ github.action_path }}/$actionName.ps1$parameterString") | Out-Null
|
|
$yaml.AppendLine(" }") | Out-Null
|
|
$yaml.AppendLine("branding:") | Out-Null
|
|
$yaml.AppendLine(" icon: terminal") | Out-Null
|
|
$yaml.Append(" color: blue") | Out-Null
|
|
|
|
$yamlLines = $yaml.ToString().Replace("`r","").Split("`n")
|
|
$actualYaml = @(Get-Content -path (Join-Path $scriptRoot "action.yaml"))
|
|
|
|
$i = 0
|
|
while ($i -lt $yamlLines.Count -and $i -lt $actualYaml.count) {
|
|
if ($yamlLines[$i] -ne $actualYaml[$i]) {
|
|
$actualYaml[$i] | Should -BeLike $yamlLines[$i]
|
|
}
|
|
$i++
|
|
}
|
|
|
|
$yamlLines.Count | Should -be $actualYaml.Count
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Test that all actions are referenced from microsoft/AL-Go-Actions@<main|preview> or actions/ (by GitHub)
|
|
#>
|
|
function TestActionsReferences {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$YamlPath
|
|
)
|
|
|
|
$yaml = Get-Content -Path $YamlPath -Raw
|
|
|
|
# Test all referenced actions are coming from microsoft/AL-Go-Actions@<main|preview> or actions/ (by GitHub)
|
|
$actionReferencePattern = 'uses:\s*(.*)@(.*)'
|
|
|
|
$actionReferences = [regex]::matches($yaml, $actionReferencePattern)
|
|
|
|
$alGoActionPattern = "^microsoft/AL-Go-Actions/*"
|
|
$gitHubActionPattern = "^actions/*"
|
|
|
|
$actionReferences | ForEach-Object {
|
|
$origin = $_.Groups[1].Value
|
|
$version = $_.Groups[2].Value
|
|
|
|
$origin | Should -Match "($alGoActionPattern|$gitHubActionPattern)"
|
|
|
|
if($origin -match $alGoActionPattern) {
|
|
$version | Should -Match "main"
|
|
}
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Test that all referenced workflows are coming from .github/workflows/
|
|
#>
|
|
function TestWorkflowReferences {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$YamlPath
|
|
)
|
|
|
|
$yaml = Get-Content -Path $YamlPath -Raw
|
|
|
|
# Test all referenced workflows are coming from .github/workflows/
|
|
$alGoWorkflowReferencePatterns = 'uses:\s*(.*).(yaml|yml)'
|
|
|
|
$workflowReferences = [regex]::matches($yaml, $alGoWorkflowReferencePatterns)
|
|
|
|
$localWorkflowsPattern = '^./.github/workflows/*'
|
|
|
|
$workflowReferences | ForEach-Object {
|
|
$workflowPath = $_.Groups[1].Value
|
|
$workflowPath | Should -Match "$localWorkflowsPattern"
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Get all workflows in a path
|
|
#>
|
|
function GetWorkflowsInPath {
|
|
param(
|
|
[Parameter(Mandatory, ValueFromPipeline = $true)]
|
|
[string]$Path
|
|
)
|
|
Process {
|
|
return (Get-ChildItem -Path $Path -File -Recurse -Include ('*.yaml', '*.yml'))
|
|
}
|
|
}
|
|
|
|
function PesterMatchHashtable($ActualValue, $ExpectedValue, [switch] $Negate) {
|
|
$message = FindMismatchedHashtableValue -Prefix "" -ActualValue $ActualValue -ExpectedValue $ExpectedValue
|
|
$success = $null -eq $message
|
|
|
|
if ($success) {
|
|
if ($Negate) {
|
|
#expecting failure
|
|
$success = $false
|
|
$message = "Expected: '$ActualValue' to not match the expression '$ExpectedValue'"
|
|
}
|
|
# else - we can just return success
|
|
}
|
|
else {
|
|
if ($Negate) {
|
|
# expecting failure
|
|
$success = $true
|
|
$message = ""
|
|
}
|
|
# else - we can just return failure
|
|
}
|
|
return New-Object psobject -Property @{
|
|
Succeeded = $success
|
|
FailureMessage = $message
|
|
}
|
|
}
|
|
|
|
function FindMismatchedHashtableValue($Prefix, $ActualValue, $ExpectedValue) {
|
|
foreach($expectedKey in $ExpectedValue.Keys) {
|
|
if (-not($ActualValue.Keys -contains $expectedKey)){
|
|
return "Expected key: '$Prefix$expectedKey', but missing in actual"
|
|
}
|
|
$expectedItem = $ExpectedValue[$expectedKey]
|
|
$actualItem = $ActualValue[$expectedKey]
|
|
if ($actualItem -is [array] -and $expectedItem -is [array]) {
|
|
if ($actualItem.Count -ne $expectedItem.Count) {
|
|
return "Value differs for array at key '$Prefix$expectedKey'. Expected count: $($expectedItem.Count), actual count: $($actualItem.Count)"
|
|
}
|
|
0..($actualItem.Count-1) | ForEach-Object {
|
|
if ($actualItem[$_] -is [hashtable] -and $expectedItem[$_] -is [hashtable]) {
|
|
$message = FindMismatchedHashtableValue -Prefix "$($Prefix)$($expectedKey)[$_]." -ActualValue $actualItem[$_] -ExpectedValue $expectedItem[$_]
|
|
if ($message) {
|
|
return $message
|
|
}
|
|
}
|
|
elseif (-not ($actualItem[$_] -eq $expectedItem[$_])) {
|
|
return "Value differs for key '$($Prefix)$($expectedKey)[$_]'. Expected value: '$($expectedItem[$_])', actual value: '$($actualItem[$_])'"
|
|
}
|
|
}
|
|
}
|
|
elseif ($actualItem -is [hashtable] -and $expectedItem -is [hashtable]) {
|
|
$message = FindMismatchedHashtableValue -Prefix "$($Prefix)$($expectedKey)." -ActualValue $actualItem -ExpectedValue $expectedItem
|
|
if ($message) {
|
|
return $message
|
|
}
|
|
}
|
|
elseif (-not ($actualItem -eq $expectedItem)) {
|
|
return "Value differs for key '$Prefix$expectedKey'. Expected value: '$expectedItem', actual value: '$actualItem'"
|
|
}
|
|
}
|
|
|
|
foreach($actualKey in $ActualValue.Keys) {
|
|
if (-not($ExpectedValue.Keys -contains $actualKey)){
|
|
return "Actual key: '$prefix$actualKey', but missing in expected"
|
|
}
|
|
}
|
|
}
|
|
|
|
Add-AssertionOperator -Name MatchHashtable -Test $function:PesterMatchHashtable
|
|
|
|
Export-ModuleMember -Function GetActionScript
|
|
Export-ModuleMember -Function YamlTest
|
|
Export-ModuleMember -Function GetWorkflowsInPath
|
|
Export-ModuleMember -Function TestActionsReferences
|
|
Export-ModuleMember -Function TestWorkflowReferences
|