diff --git a/.github/workflows/check-pinned-versions.yml b/.github/workflows/check-pinned-versions.yml new file mode 100644 index 000000000..bda072158 --- /dev/null +++ b/.github/workflows/check-pinned-versions.yml @@ -0,0 +1,22 @@ +name: Check Outdated Version Pinning + +on: + schedule: + - cron: '0 12 * * 1' # Run at 12:00 UTC every Monday + +permissions: + issues: write + contents: read + +jobs: + check-pinning-dates: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Validate JSON Schema + shell: pwsh + run: ./helpers/CheckOutdatedVersionPinning.ps1 + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/validate-json-schema.yml b/.github/workflows/validate-json-schema.yml new file mode 100644 index 000000000..dbebc0f29 --- /dev/null +++ b/.github/workflows/validate-json-schema.yml @@ -0,0 +1,20 @@ +name: Validate JSON Schema + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + validate-json-schema: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Validate JSON Schema + shell: pwsh + run: ./helpers/CheckJsonSchema.ps1 diff --git a/.vscode/settings.json b/.vscode/settings.json index f0ce6b6ac..98a58c65f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,5 +21,14 @@ ], "shellcheck.customArgs": [ "-x" - ] + ], + "json.schemas": [ + { + "fileMatch": [ + "**/toolset-*.json" + ], + "url": "./schemas/toolset-schema.json" + } +] + } diff --git a/helpers/CheckJsonSchema.ps1 b/helpers/CheckJsonSchema.ps1 new file mode 100644 index 000000000..4155fb899 --- /dev/null +++ b/helpers/CheckJsonSchema.ps1 @@ -0,0 +1,42 @@ +$ErrorActionPreference = 'Stop' + +# A JSON schema validator which supports outputting line numbers for errors +# this allows us to put annotations on builds for errors in the JSON files +# `Test-Json` built in cmdline doesn't. No existing cli tool supports this +# that I could find either. See: https://github.com/lawrencegripper/gripdev-json-schema-validator +Install-Module -Name GripDevJsonSchemaValidator -Force -Scope CurrentUser + +# Find all toolset JSON files +$toolsetFiles = Get-ChildItem -Recurse -Filter "toolset-*.json" | Where-Object { $_.Name -notlike "*schema.json" } +$schemaFilePath = "./schemas/toolset-schema.json" + +$toolsetHasErrors = $false +foreach ($file in $toolsetFiles) { + Write-Host "" + Write-Host "šŸ” Validating $($file.FullName)" -ForegroundColor Cyan + + $validationResult = Test-JsonSchema -SchemaPath $schemaFilePath -JsonPath $file.FullName -PrettyPrint $false + + if ($validationResult.Valid) { + Write-Host "āœ… JSON is valid." -ForegroundColor Green + } else { + # File has been modified since the commit, enforce validation + $toolsetHasErrors = $true + Write-Host "`nāŒ JSON validation failed!" -ForegroundColor Red + Write-Host " Found the following errors:`n" -ForegroundColor Yellow + + $validationResult.Errors | ForEach-Object { + Write-Host $_.UserMessage + if ($env:GITHUB_ACTIONS -eq 'true') { + Write-Host "Adding annotation" + Write-Host "::error file=$($file.Name),line=$($_.LineNumber)::$($_.UserMessage.Replace("`n", '%0A'))" + } + } + } +} + +if ($toolsetHasErrors) { + Write-Error "One or more toolset JSON files failed schema validation. See the error output above for more details." +} else { + Write-Host "Schema validation completed successfully" +} diff --git a/helpers/CheckOutdatedVersionPinning.ps1 b/helpers/CheckOutdatedVersionPinning.ps1 new file mode 100644 index 000000000..2d98ad587 --- /dev/null +++ b/helpers/CheckOutdatedVersionPinning.ps1 @@ -0,0 +1,85 @@ +$ErrorActionPreference = 'Stop' + +# Find all toolset JSON files +$toolsetFiles = Get-ChildItem -Recurse -Filter "toolset-*.json" | Where-Object { $_.Name -notlike "*schema.json" } + +$expiringPins = @() +$now = Get-Date +$warningDays = 30 # Warn if expiring within 30 days + +foreach ($file in $toolsetFiles) { + Write-Host "Processing $($file.Name)" + $content = Get-Content $file.FullName | ConvertFrom-Json + + # Recursively search for pinnedDetails in the JSON + function Search-PinnedDetails { + param($obj, $path) + + $foundPins = @() + + if ($obj -is [System.Management.Automation.PSCustomObject]) { + foreach ($prop in $obj.PSObject.Properties) { + if ($prop.Name -eq "pinnedDetails") { + Write-Host "Found pinned version at $path" + $reviewAt = [DateTime]::Parse($prop.Value.'review-at') + $daysUntilExpiry = ($reviewAt - $now).Days + + if ($daysUntilExpiry -lt $warningDays) { + Write-Host "Adding to expiringPins array" + $foundPins += @{ + Path = $path + File = $file.Name + ReviewAt = $reviewAt + DaysUntilExpiry = $daysUntilExpiry + Reason = $prop.Value.reason + Link = $prop.Value.link + } + } + } else { + $foundPins += Search-PinnedDetails -obj $prop.Value -path "$path.$($prop.Name)" + } + } + } elseif ($obj -is [Array]) { + for ($i = 0; $i -lt $obj.Count; $i++) { + $foundPins += Search-PinnedDetails -obj $obj[$i] -path "$path[$i]" + } + } + + return $foundPins + } + + $expiringPins += Search-PinnedDetails -obj $content -path $file.Name +} + +if ($expiringPins) { + $issueBody = "# Version Pinning Review Required`n`n" + $issueBody += "The following pinned versions need review:`n`n" + + foreach ($pin in $expiringPins) { + $status = if ($pin.DaysUntilExpiry -lt 0) { "EXPIRED" } else { "Expiring Soon" } + $issueBody += "## $($status) - $($pin.Path)`n" + $issueBody += "- **File**: $($pin.File)`n" + $issueBody += "- **Review Date**: $($pin.ReviewAt.ToString('yyyy-MM-dd'))`n" + $issueBody += "- **Days until expiry**: $($pin.DaysUntilExpiry)`n" + $issueBody += "- **Reason**: $($pin.Reason)`n" + $issueBody += "- **Original PR**: $($pin.Link)`n`n" + } + + if ($env:GITHUB_ACTIONS -eq 'true') { + # In GitHub Actions, create an issue + Write-Host "Creating issue" + $tempFile = [System.IO.Path]::GetTempFileName() + Set-Content -Path $tempFile -Value $issueBody + gh issue create --title "Version Pinning Review Found Expired Pinned Versions" --body-file $tempFile + Remove-Item -Path $tempFile + } + + Write-Host "`nIssue Content:`n" + Write-Host $issueBody +} +else { + Write-Host "No expiring pins found." + if ($env:GITHUB_ACTIONS -eq 'true') { + "expired_pins=0" >> $env:GITHUB_OUTPUT + } +} diff --git a/images/ubuntu/toolsets/toolset-2004.json b/images/ubuntu/toolsets/toolset-2004.json index 1f872bd89..047044b6b 100644 --- a/images/ubuntu/toolsets/toolset-2004.json +++ b/images/ubuntu/toolsets/toolset-2004.json @@ -381,10 +381,21 @@ }, "aliyunCli": { "version": "3.0.174", - "sha256": "0c51028a7a32fc02c8de855f73e273556f957115eb5624565738f9b9f83a50ba" + "sha256": "0c51028a7a32fc02c8de855f73e273556f957115eb5624565738f9b9f83a50ba", + "pinnedDetails": { + "link": "https://github.com/actions/runner-images-internal/pull/6702", + "reason": "Meaningful reason must be added at next update.", + "review-at": "2025-06-01", + "type": "preexisting-pinned-version-without-reason" + } }, "ocCli": { - "version": "4.15.19" - } - + "version": "4.15.19", + "pinnedDetails": { + "link": "https://github.com/actions/runner-images-internal/pull/6702", + "reason": "Meaningful reason must be added at next update.", + "review-at": "2025-06-01", + "type": "preexisting-pinned-version-without-reason" + } + } } diff --git a/images/windows/toolsets/toolset-2019.json b/images/windows/toolsets/toolset-2019.json index 4ff1a44fc..d547586c2 100644 --- a/images/windows/toolsets/toolset-2019.json +++ b/images/windows/toolsets/toolset-2019.json @@ -474,7 +474,12 @@ "version": "latest" }, "openssl": { - "version": "1.1.1" + "version": "1.1.1", + "pinnedDetails": { + "link": "https://github.com/somelink", + "reason": "this was pinned due to a downstream issue with the installer", + "review-at": "2025-01-30" + } }, "pwsh": { "version": "7.4" diff --git a/images/windows/toolsets/toolset-2022.json b/images/windows/toolsets/toolset-2022.json index 4fbce97d2..6660ef26a 100644 --- a/images/windows/toolsets/toolset-2022.json +++ b/images/windows/toolsets/toolset-2022.json @@ -150,7 +150,13 @@ }, "mingw": { "version": "12.2.0", - "runtime": "ucrt" + "runtime": "ucrt", + "pinnedDetails": { + "link": "https://github.com/actions/runner-images-internal/pull/6702", + "reason": "Meaningful reason must be added at next update.", + "review-at": "2025-06-01", + "type": "preexisting-pinned-version-without-reason" + } }, "MsysPackages": { "msys2": [], @@ -384,7 +390,13 @@ "version": "latest" }, "openssl": { - "version": "1.1.1" + "version": "1.1.1", + "pinnedDetails": { + "link": "https://github.com/actions/runner-images-internal/pull/6702", + "reason": "Meaningful reason must be added at next update.", + "review-at": "2024-06-01", + "type": "preexisting-pinned-version-without-reason" + } }, "pwsh": { "version": "7.4" diff --git a/images/windows/toolsets/toolset-2025.json b/images/windows/toolsets/toolset-2025.json index 6cf6ffc93..22e3b5ef5 100644 --- a/images/windows/toolsets/toolset-2025.json +++ b/images/windows/toolsets/toolset-2025.json @@ -107,7 +107,13 @@ }, "mingw": { "version": "14.2.0", - "runtime": "ucrt" + "runtime": "ucrt", + "pinnedDetails": { + "link": "https://github.com/actions/runner-images-internal/pull/6702", + "reason": "Meaningful reason must be added at next update.", + "review-at": "2025-06-01", + "type": "preexisting-pinned-version-without-reason" + } }, "MsysPackages": { "msys2": [], @@ -318,7 +324,13 @@ "version": "latest" }, "openssl": { - "version": "3.4.0" + "version": "3.4.0", + "pinnedDetails": { + "link": "https://github.com/actions/runner-images-internal/pull/6702", + "reason": "Meaningful reason must be added at next update.", + "review-at": "2025-06-01", + "type": "preexisting-pinned-version-without-reason" + } }, "pwsh": { "version": "7.4" diff --git a/schemas/toolset-schema.json b/schemas/toolset-schema.json new file mode 100644 index 000000000..f9bf71b96 --- /dev/null +++ b/schemas/toolset-schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "patternProperties": { + "^.*$": { + "if": { + "type": "object", + "required": [ + "version" + ], + "properties": { + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+.*$" + } + } + }, + "then": { + "required": [ + "pinnedDetails" + ], + "properties": { + "pinnedDetails": { + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "link": { + "type": "string" + }, + "review-at": { + "type": "string", + "format": "date" + } + } + } + } + } + } + } +}