Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Armstrong Validation into the spec PR check. #28829

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
91781e3
add code
ms-zhenhua Apr 23, 2024
da9dda0
fix yml
ms-zhenhua Apr 26, 2024
0755ad3
fix yml
ms-zhenhua Apr 26, 2024
62347fa
update
ms-zhenhua Apr 28, 2024
bae3d8d
update
ms-zhenhua Apr 28, 2024
440c631
debug
ms-zhenhua Apr 28, 2024
7ae49e2
update
ms-zhenhua Apr 28, 2024
ca8ee98
update
ms-zhenhua Apr 29, 2024
63a6f5e
update
ms-zhenhua Apr 29, 2024
0a5623c
Merge branch 'main' into ms-zhenhua/armstrong-validation
mikeharder May 7, 2024
dd48b66
Use shared component get-suppressions
mikeharder May 7, 2024
0e38c65
Merge branch 'main' into ms-zhenhua/armstrong-validation
mikeharder May 7, 2024
66fed05
Merge branch 'main' into ms-zhenhua/armstrong-validation
mikeharder May 7, 2024
722f3f4
Install Node and deps before calling get-suppressions
mikeharder May 7, 2024
b7baad1
Check for errors after "npx get-suppressions"
mikeharder May 7, 2024
babe855
Merge branch 'main' of https://github.com/Azure/azure-rest-api-specs …
ms-zhenhua Nov 7, 2024
c270305
Convert "Armstrong Validation" to GitHub Action
mikeharder Nov 7, 2024
c1a948c
Update GITHUB_PATH
mikeharder Nov 7, 2024
68326bf
Update path to logging helpers
mikeharder Nov 8, 2024
dbf48ca
Merge branch 'main' into ms-zhenhua/armstrong-validation
ms-zhenhua Nov 8, 2024
eaa05ea
Merge branch 'main' into ms-zhenhua/armstrong-validation
ms-zhenhua Nov 11, 2024
3acdffe
remove pipeline task
ms-zhenhua Nov 11, 2024
5ffc3e4
fix armstrong cred scan error
ms-zhenhua Nov 12, 2024
f026987
update armstrong pipeline task
ms-zhenhua Nov 16, 2024
2ae09ae
update Armstrong-Validation.ps1
ms-zhenhua Nov 17, 2024
3775e7e
Merge branch 'main' into ms-zhenhua/armstrong-validation
ms-zhenhua Nov 29, 2024
26726e3
bug fix
ms-zhenhua Nov 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/armstrong-validation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Armstrong Validation

on:
pull_request:
types:
- opened
- reopened
- edited
- ready_for_review
- labeled
- unlabeled
- synchronize

jobs:
armstrong-validation:
name: Armstrong Validation
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.22.2

- name: Install Go Dependencies
run: |
go version
go install github.com/azure/armstrong@c27cb68ad5d83f254f921d594f02b618deb4ad7e
echo "$(HOME)/go/bin" >> $GITHUB_PATH

- name: Setup Node and run `npm ci`
uses: ./.github/actions/setup-node-npm-ci

- name: Armstrong Validation
run: |
# Keep processing when errors are written. Nonzero exit will mark the
# step as failed.

./eng/scripts/Armstrong-Validation.ps1 -Verbose
shell: pwsh
env:
GH_PR_NUMBER: ${{ github.event.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 changes: 21 additions & 0 deletions eng/common/scripts/Invoke-GitHubAPI.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,27 @@ function Add-GitHubIssueComment {
-MaximumRetryCount 3
}

function Get-GitHubIssueComments {
param (
[Parameter(Mandatory = $true)]
$RepoOwner,
[Parameter(Mandatory = $true)]
$RepoName,
[Parameter(Mandatory = $true)]
$IssueNumber,
[Parameter(Mandatory = $true)]
$AuthToken
)

$uri = "$GithubAPIBaseURI/$RepoOwner/$RepoName/issues/$IssueNumber/comments"

return Invoke-RestMethod `
-Method GET `
-Uri $uri `
-Headers (Get-GitHubApiHeaders -token $AuthToken) `
-MaximumRetryCount 3
}

# Will add labels to existing labels on the issue
function Add-GitHubIssueLabels {
param (
Expand Down
220 changes: 220 additions & 0 deletions eng/scripts/Armstrong-Validation.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
[CmdletBinding()]
param (
[Parameter(Position = 0)]
[string] $BaseCommitish = "HEAD^",

[Parameter(Position = 1)]
[string] $TargetCommitish = "HEAD"
)
Set-StrictMode -Version 3

. $PSScriptRoot/../common/scripts/Invoke-GitHubAPI.ps1
. $PSScriptRoot/../common/scripts/logging.ps1
. $PSScriptRoot/ChangedFiles-Functions.ps1
. $PSScriptRoot/Suppressions-Functions.ps1

function Get-ChangedTerraformFiles($changedFiles = (Get-ChangedFiles)) {
$changedFiles = Get-ChangedFilesUnderSpecification $changedFiles

$changedSwaggerFiles = $changedFiles.Where({
# since `git diff` returns paths with `/`, use the following code to match the `main.tf`
$_.EndsWith("/main.tf")
})

return $changedSwaggerFiles
}

$script:armstrongInstalled = $false
function Ensure-Armstrong-Installed {
if ($script:armstrongInstalled) {
# If already checked once in this script, don't log anything further
return;
}

$script:armstrongInstalled = $true

# install golang
if (!(Get-Command "go" -ErrorAction SilentlyContinue)) {
LogError "Golang is not installed"
exit 1
}

# install armstrong
if (!(Get-Command "armstrong" -ErrorAction SilentlyContinue)) {
LogError "Armstrong is not installed"
exit 1
}
}

function Validate-Terraform-Error($repoPath, $filePath) {
$fileDirectory = (Split-Path -Parent $filePath)
$outputDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
$result = @()

try {
if (!(Test-Path -Path $outputDirectory)) {
New-Item -Path $outputDirectory -ItemType Directory *> $null
Comment on lines +51 to +56
Copy link
Member

@mikeharder mikeharder May 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$outputDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
$result = @()
try {
if (!(Test-Path -Path $outputDirectory)) {
New-Item -Path $outputDirectory -ItemType Directory *> $null
$outputDirectory = [System.IO.Directory]::CreateTempSubdirectory()
$result = @()
try {

.NET 7 added an easier way to do this.

# run armstrong credscan
$specPath = Join-Path -Path $repoPath -ChildPath "specification"
LogInfo "armstrong credscan -working-dir $fileDirectory -swagger-repo $specPath -output-dir $outputDirectory"
armstrong credscan -working-dir $fileDirectory -swagger-repo $specPath -output-dir $outputDirectory
}

# error reports are stored in a directory named armstrong_credscan_<timestamp>
Get-ChildItem -Path $outputDirectory -Directory -Filter "armstrong_credscan_*" | ForEach-Object {
$errorJsonPath = Join-Path -Path $_.FullName -ChildPath "errors.json"
if (Test-Path -Path $errorJsonPath) {
Get-Content -Path $errorJsonPath -Raw | ConvertFrom-Json | ForEach-Object {
$properties = $_.PSObject.Properties
$item = "Credential Error:"
foreach ($property in $properties) {
$item += "`n $($property.Name): $($property.Value)"
}

$result += $item
}
}
}
}
finally {
Remove-Item -Path $outputDirectory -Recurse -Force
}

return $result
}

function Get-AddedSwaggerFiles() {
$addedFiles = git -c core.quotepath=off diff --name-status --diff-filter=d $BaseCommitish $TargetCommitish | Where-Object { $_ -match 'A\s' } | ForEach-Object { $_.Substring(2).Trim() }
$addedSwaggerFiles = $addedFiles.Where({
$_ -match "\d{4}-\d{2}-\d{2}(-preview)?/[^/]*\.json$"
})

return $addedSwaggerFiles
}

$repoPath = Resolve-Path "$PSScriptRoot/../.."
$filesToCheck = (Get-ChangedTerraformFiles (Get-ChangedFiles $BaseCommitish $TargetCommitish))

# Check whether new swagger files have Armstrong Configurations
$addedSwaggerFiles = Get-AddedSwaggerFiles
$swaggerFilesToBeTest = @()
foreach ($file in $addedSwaggerFiles) {
$directory = Split-Path -Path $file -Parent
$filePath = Join-Path $repoPath $file
LogInfo $filePath
$suppression = Get-Suppression ArmstrongValidation $filePath
if ($suppression) {
$reason = $suppression["reason"] ?? "<no reason specified>"
LogInfo "$file suppressed Armstrong Test: $reason"
continue
}

$swaggerFilesToBeTest += $file

$terraformFiles = $filesToCheck.Where({
# since `git diff` returns paths with `/`, use the following code to match the `main.tf`
$_.StartsWith($directory)
})

if ($terraformFiles.Count -eq 0) {
LogError "The new swagger file $file does not have Armstrong Configurations"
exit 1
}
}

# Check Armstrong Configurations
$terraformErrors = @()

if (!$filesToCheck) {
LogInfo "No Terraform files found to check"
}
else {
foreach ($file in $filesToCheck) {
LogInfo "Checking $file"

$filePath = (Join-Path $repoPath $file)

$suppression = Get-Suppression ArmstrongValidation $filePath
if ($suppression) {
$reason = $suppression["reason"] ?? "<no reason specified>"

LogInfo "$file suppressed Armstrong configuration validation: $reason"
continue
}

try {
Ensure-Armstrong-Installed
LogInfo " Validating errors from Terraform file: $filePath"
$terraformErrors += (Validate-Terraform-Error $repoPath $filePath)
}
catch {
$terraformErrors += "failed to validate errors from Terraform file $file : $_"
}
}
}

if ($terraformErrors.Count -gt 0) {
$errorString = "Armstrong Validation failed for some files. To fix, address the following errors. For false positive errors, please follow https://eng.ms/docs/products/azure-developer-experience/design/specs-pr-guides/pr-suppressions to suppress 'ArmstrongValidation'`n"
$errorString += $terraformErrors -join "`n"
LogInfo $errorString
exit 1
}

# Check the Armstrong Test Result
if ($swaggerFilesToBeTest.Count -ne 0) {
$repositoryId = [Environment]::GetEnvironmentVariable("GITHUB_REPOSITORY", [EnvironmentVariableTarget]::Process)
LogInfo "Repository ID: $repositoryId"
$repoOwner = $repositoryId.Split("/")[0]
$repoName = $repositoryId.Split("/")[1]
LogInfo "Repository Owner: $repoOwner"
LogInfo "Repository Name: $repoName"
$pullRequestNumber = [Environment]::GetEnvironmentVariable("GH_PR_NUMBER", [EnvironmentVariableTarget]::Process)
$authToken = [Environment]::GetEnvironmentVariable("GH_TOKEN", [EnvironmentVariableTarget]::Process)
LogInfo "Repository ID: $repositoryId"
LogInfo "Pull Request Number: $pullRequestNumber"

$hasArmstrongTestResult = $false
try {
$response = Get-GitHubIssueComments -RepoOwner $repoOwner -RepoName $repoName -IssueNumber $pullRequestNumber -AuthToken $AuthToken
for ($i = $response.Length - 1; $i -ge 0; $i--) {
$responseObject = $response[$i]

if ($responseObject.body.Contains("API TEST ERROR REPORT Approved-Suppression")) {
$hasArmstrongTestResult = $true
LogInfo "The API TEST ERROR REPORT is tagged Approved-Suppression"
break
}

if ($responseObject.body.Contains("API TEST ERROR REPORT")) {
LogInfo $responseObject.body
$hasArmstrongTestResult = $true

if ($responseObject.body.Contains("**message**:")) {
LogError "Please fix all errors in API TEST ERROR REPORT: $($responseObject.html_url)"
}

$coverages = [regex]::Matches($responseObject.body, '(\d+(\.\d+)?)(?=%)')
# Output the matches
foreach ($coverage in $coverages) {
if ($coverage.Value + "%" -ne "100.0%") {
LogError "Properties of some APIs are not 100% covered in API TEST ERROR REPORT: $($responseObject.html_url)"
}
}

LogInfo "Armstrong Test result is submitted in PR comments: $($responseObject.html_url)"
break
}
}
}
catch {
LogError "Failed with exception: $_"
exit 1
}

if (!$hasArmstrongTestResult) {
LogError "Armstrong Test result is not submitted in PR comments."
exit 1
}
}

exit 0
Loading