From c2f771c6e426f7695a123806e715f410b34fd8cb Mon Sep 17 00:00:00 2001 From: Bill Berry Date: Sat, 23 May 2026 11:23:19 -0700 Subject: [PATCH 1/4] ci(workflows): add scoped bicep lint validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit โœ… - Generated by Copilot --- .github/workflows/bicep-lint.yml | 71 ++- .github/workflows/matrix-folder-check.yml | 16 +- .github/workflows/pr-validation.yml | 5 + scripts/build/Detect-Folder-Changes.Tests.ps1 | 77 +++ scripts/build/Detect-Folder-Changes.ps1 | 173 +++--- scripts/build/Invoke-BicepLint.Tests.ps1 | 145 +++++ scripts/build/Invoke-BicepLint.ps1 | 510 ++++++++++++++++++ 7 files changed, 894 insertions(+), 103 deletions(-) create mode 100644 scripts/build/Invoke-BicepLint.Tests.ps1 create mode 100644 scripts/build/Invoke-BicepLint.ps1 diff --git a/.github/workflows/bicep-lint.yml b/.github/workflows/bicep-lint.yml index 97034fd5f..4dc12a496 100644 --- a/.github/workflows/bicep-lint.yml +++ b/.github/workflows/bicep-lint.yml @@ -6,6 +6,8 @@ # # Parameters: # - soft-fail: When true, lint violations are reported but do not fail the workflow (default: false) +# - full-validation: When true, validates every Bicep file in the repository (default: true) +# - bicep-folders-json: JSON object emitted by matrix-folder-check for scoped validation # # Usage Examples: # ```yaml @@ -30,6 +32,16 @@ on: # yamllint disable-line rule:truthy required: false type: boolean default: false + full-validation: + description: 'Whether to validate every Bicep file in the repository' + required: false + type: boolean + default: true + bicep-folders-json: + description: 'JSON object of Bicep folders to validate when full-validation is false' + required: false + type: string + default: '{}' permissions: contents: read @@ -70,41 +82,17 @@ jobs: - name: Find and validate Bicep files id: bicep-build - shell: bash + shell: pwsh + env: + BICEP_FOLDERS_JSON: ${{ inputs.bicep-folders-json }} + FULL_VALIDATION: ${{ inputs.full-validation }} run: | - set -euo pipefail - FAILED=0 - FILES_CHECKED=0 - FAILURES="" - - while IFS= read -r -d '' file; do - if [[ "$file" == *node_modules* ]] || [[ "$file" == *.copilot-tracking* ]]; then - continue - fi - FILES_CHECKED=$((FILES_CHECKED + 1)) - echo "::group::Validating: $file" - if ! az bicep build --file "$file" --stdout > /dev/null 2>&1; then - FAILED=$((FAILED + 1)) - FAILURES="${FAILURES}\n- ${file}" - echo "::error file=${file}::Bicep build failed for ${file}" - az bicep build --file "$file" --stdout 2>&1 || true - fi - echo "::endgroup::" - done < <(find . -name '*.bicep' -print0) - - echo "files-checked=${FILES_CHECKED}" >> "$GITHUB_OUTPUT" - echo "failures=${FAILED}" >> "$GITHUB_OUTPUT" - - SUMMARY="## Bicep Lint Results\n\n- **Files checked:** ${FILES_CHECKED}\n- **Failures:** ${FAILED}" - if [ "$FAILED" -gt 0 ]; then - SUMMARY="${SUMMARY}\n\n### Failed files\n${FAILURES}" - fi - echo -e "$SUMMARY" >> "$GITHUB_STEP_SUMMARY" - echo -e "$SUMMARY" > bicep-lint-output.txt - - if [ "$FAILED" -gt 0 ]; then - echo "BICEP_LINT_FAILED=true" >> "$GITHUB_ENV" - fi + ./scripts/build/Invoke-BicepLint.ps1 ` + -Platform github ` + -FullValidation $env:FULL_VALIDATION ` + -BicepFoldersJson $env:BICEP_FOLDERS_JSON ` + -ResultsDir bicep-lint-results ` + -OutputFile bicep-lint-output.txt - name: Upload lint results if: always() @@ -116,14 +104,15 @@ jobs: if-no-files-found: ignore - name: Fail on violations - if: env.BICEP_LINT_FAILED == 'true' - shell: bash + if: steps.bicep-build.outputs.failures != '0' + shell: pwsh env: SOFT_FAIL: ${{ inputs.soft-fail }} run: | - if [ "$SOFT_FAIL" = "true" ]; then - echo "::warning::Bicep lint found violations (soft-fail enabled)" - else - echo "::error::Bicep lint found violations" + if ($env:SOFT_FAIL -eq 'true') { + Write-Output '::warning::Bicep lint found violations (soft-fail enabled)' + } + else { + Write-Output '::error::Bicep lint found violations' exit 1 - fi + } diff --git a/.github/workflows/matrix-folder-check.yml b/.github/workflows/matrix-folder-check.yml index 3683a9bd4..0c8a4054f 100644 --- a/.github/workflows/matrix-folder-check.yml +++ b/.github/workflows/matrix-folder-check.yml @@ -123,6 +123,12 @@ on: # yamllint disable-line rule:truthy changedBicepFolders: description: 'JSON matrix of Bicep folders that have changed' value: ${{ jobs.map-outputs.outputs.changedBicepFolders }} + bicepFullValidationRequired: + description: 'Whether Bicep validation must run across all folders' + value: ${{ jobs.map-outputs.outputs.bicepFullValidationRequired }} + bicepFullValidationReasons: + description: 'JSON array of reasons that require full Bicep validation' + value: ${{ jobs.map-outputs.outputs.bicepFullValidationReasons }} changesInApplications: description: 'Whether any Application folders have changed' value: ${{ jobs.map-outputs.outputs.changesInApplications }} @@ -165,6 +171,8 @@ jobs: changedTfFolders: ${{ steps.detect.outputs.changedTfFolders }} changesInBicepInstall: ${{ steps.detect.outputs.changesInBicepInstall }} changedBicepFolders: ${{ steps.detect.outputs.changedBicepFolders }} + bicepFullValidationRequired: ${{ steps.detect.outputs.bicepFullValidationRequired }} + bicepFullValidationReasons: ${{ steps.detect.outputs.bicepFullValidationReasons }} changesInApplications: ${{ steps.detect.outputs.changesInApplications }} changedApplicationFolders: ${{ steps.detect.outputs.changedApplicationFolders }} changesInFuzzRust: ${{ steps.detect.outputs.changesInFuzzRust }} @@ -213,8 +221,10 @@ jobs: "powershellChanges=$($jsonData.subscription.powershell_changes)" >> $env:GITHUB_OUTPUT "changesInTerraformInstall=$($jsonData.terraform.has_changes)" >> $env:GITHUB_OUTPUT "changedTfFolders=$($jsonData.terraform.folders | ConvertTo-Json -Compress)" >> $env:GITHUB_OUTPUT - "changesInBicepInstall=$($jsonData.bicep.has_changes)" >> $env:GITHUB_OUTPUT + "changesInBicepInstall=$($jsonData.bicep.has_changes.ToString().ToLower())" >> $env:GITHUB_OUTPUT "changedBicepFolders=$($jsonData.bicep.folders | ConvertTo-Json -Compress)" >> $env:GITHUB_OUTPUT + "bicepFullValidationRequired=$($jsonData.bicep.full_validation_required.ToString().ToLower())" >> $env:GITHUB_OUTPUT + "bicepFullValidationReasons=$($jsonData.bicep.full_validation_reasons | ConvertTo-Json -Compress)" >> $env:GITHUB_OUTPUT "changesInApplications=$($jsonData.applications.has_changes)" >> $env:GITHUB_OUTPUT "changedApplicationFolders=$($jsonData.applications.folders | ConvertTo-Json -Compress)" >> $env:GITHUB_OUTPUT if ($jsonData.PSObject.Properties.Name -contains 'fuzz') { @@ -242,6 +252,8 @@ jobs: Write-Host "Terraform folders: $($jsonData.terraform.folders | ConvertTo-Json)" Write-Host "Bicep changes: $($jsonData.bicep.has_changes)" Write-Host "Bicep folders: $($jsonData.bicep.folders | ConvertTo-Json)" + Write-Host "Bicep full validation required: $($jsonData.bicep.full_validation_required)" + Write-Host "Bicep full validation reasons: $($jsonData.bicep.full_validation_reasons | ConvertTo-Json)" Write-Host "Application changes: $($jsonData.applications.has_changes)" Write-Host "Application folders: $($jsonData.applications.folders | ConvertTo-Json)" if ($jsonData.PSObject.Properties.Name -contains 'fuzz') { @@ -262,6 +274,8 @@ jobs: changedTfFolders: ${{ needs.detect-changes.outputs.changedTfFolders }} changesInBicepInstall: ${{ needs.detect-changes.outputs.changesInBicepInstall }} changedBicepFolders: ${{ needs.detect-changes.outputs.changedBicepFolders }} + bicepFullValidationRequired: ${{ needs.detect-changes.outputs.bicepFullValidationRequired }} + bicepFullValidationReasons: ${{ needs.detect-changes.outputs.bicepFullValidationReasons }} changesInApplications: ${{ needs.detect-changes.outputs.changesInApplications }} changedApplicationFolders: ${{ needs.detect-changes.outputs.changedApplicationFolders }} changesInFuzzRust: ${{ needs.detect-changes.outputs.changesInFuzzRust }} diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 23793522a..0e7ad909c 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -122,9 +122,14 @@ jobs: # Bicep linting bicep-lint: name: Bicep Lint + needs: [matrix-changes] + if: github.event_name != 'pull_request' || needs.matrix-changes.outputs.changesInBicepInstall == 'true' || needs.matrix-changes.outputs.bicepFullValidationRequired == 'true' permissions: contents: read uses: ./.github/workflows/bicep-lint.yml + with: + full-validation: ${{ github.event_name != 'pull_request' || needs.matrix-changes.outputs.bicepFullValidationRequired == 'true' }} + bicep-folders-json: ${{ needs.matrix-changes.outputs.changedBicepFolders }} # Documentation linting docs-lint: diff --git a/scripts/build/Detect-Folder-Changes.Tests.ps1 b/scripts/build/Detect-Folder-Changes.Tests.ps1 index 9db557697..08d887dd9 100644 --- a/scripts/build/Detect-Folder-Changes.Tests.ps1 +++ b/scripts/build/Detect-Folder-Changes.Tests.ps1 @@ -1,3 +1,4 @@ +#Requires -Modules Pester # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: MIT @@ -97,3 +98,79 @@ Describe 'Test-RustHasChange' -Tag 'Unit' { Test-RustHasChange -ChangedFiles @('docs/readme.md', 'src/020-iac/main.tf') | Should -BeFalse } } + +Describe 'Get-BicepFullValidationReason' -Tag 'Unit' { + It 'returns bicepconfig for root Bicep configuration changes' { + @(Get-BicepFullValidationReason -Files @('bicepconfig.json')) | Should -Contain 'bicepconfig' + } + + It 'returns shared-blueprint-modules for shared blueprint module changes' { + @(Get-BicepFullValidationReason -Files @('blueprints/modules/core/main.bicep')) | + Should -Contain 'shared-blueprint-modules' + } + + It 'returns no reason for unrelated Bicep workflow template changes' { + @(Get-BicepFullValidationReason -Files @('.azdo/templates/bicep-lint-template.yml')) | Should -BeNullOrEmpty + } +} + +Describe 'Get-FilePathData for Bicep paths' -Tag 'Unit' { + It 'returns the component folder for changed source Bicep files' { + @(Get-FilePathData -Paths @('src/000-cloud/000-resource-group/bicep/main.bicep')) | + Should -Contain 'src/000-cloud/000-resource-group' + } + + It 'returns the blueprint folder for changed blueprint Bicep files' { + @(Get-FilePathData -Paths @('blueprints/full-single-node-cluster/bicep/main.bicep')) | + Should -Contain 'blueprints/full-single-node-cluster' + } + + It 'excludes shared blueprint modules from scoped folder output' { + @(Get-FilePathData -Paths @('blueprints/modules/core/types.bicep')) | Should -BeNullOrEmpty + } +} + +Describe 'Get-DependentBicepBlueprintPath' -Tag 'Unit' { + BeforeEach { + $script:RepoRoot = Join-Path $TestDrive 'repo' + $script:BlueprintRoot = Join-Path $script:RepoRoot 'blueprints' + $dependentBlueprint = Join-Path $script:BlueprintRoot 'full-single-node-cluster/bicep' + $unrelatedBlueprint = Join-Path $script:BlueprintRoot 'fabric/bicep' + + New-Item -ItemType Directory -Path $dependentBlueprint, $unrelatedBlueprint -Force | Out-Null + + @" +module rg '../../../src/000-cloud/000-resource-group/bicep/main.bicep' = { + name: 'resourceGroup' +} +"@ | Set-Content -Path (Join-Path $dependentBlueprint 'main.bicep') + + @" +module fabric '../../../src/000-cloud/031-fabric/bicep/main.bicep' = { + name: 'fabric' +} +"@ | Set-Content -Path (Join-Path $unrelatedBlueprint 'main.bicep') + } + + It 'returns blueprints that reference a changed Bicep component' { + $result = @(Get-DependentBicepBlueprintPath ` + -ChangedPaths @('src/000-cloud/000-resource-group') ` + -BlueprintRoot $script:BlueprintRoot) + + $result | Should -Contain 'blueprints/full-single-node-cluster' + $result | Should -Not -Contain 'blueprints/fabric' + $result | Should -HaveCount 1 + } + + It 'deduplicates dependent blueprints for repeated component paths' { + $result = @(Get-DependentBicepBlueprintPath ` + -ChangedPaths @( + 'src/000-cloud/000-resource-group', + 'src/000-cloud/000-resource-group/bicep' ` + ) ` + -BlueprintRoot $script:BlueprintRoot) + + $result | Should -Contain 'blueprints/full-single-node-cluster' + $result | Should -HaveCount 1 + } +} diff --git a/scripts/build/Detect-Folder-Changes.ps1 b/scripts/build/Detect-Folder-Changes.ps1 index eeaa20208..b7082be85 100644 --- a/scripts/build/Detect-Folder-Changes.ps1 +++ b/scripts/build/Detect-Folder-Changes.ps1 @@ -1,24 +1,11 @@ +#!/usr/bin/env pwsh +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +#Requires -Version 7.0 + <# .SYNOPSIS - Detects changes in repository.EXAMPLE - # Include all Terraform and Bicep folders for security scanning: - .\Detect-Folder-Changes.ps1 -IncludeAllIaC - -.EXAMPLE - # Compare against a different branch with application detection: - .\Detect-Folder-Changes.ps1 -BaseBranch origi if (-not [string]::IsNullOrWhiteSpace($repoRoot) -and $resolvedApplicationPath.StartsWith($repoRoot, [System.StringComparison]::OrdinalIgnoreCase)) { - $relativeApplicationPath = $resolvedApplicationPath.Substring($repoRoot.Length) -replace '^[/\\]+', '' - } - else { - $relativeApplicationPath = ($ApplicationPath -replace '^[/\\]+', '') - } -} -else { - $relativeApplicationPath = ($ApplicationPath -replace '^[/\\]+', '') -IncludeAllApplications - -.EXAMPLE - # Write output to a file with application detection: - .\Detect-Folder-Changes.ps1 -OutputFile "changes.json" -IncludeAllApplications + Detects changed repository folders and files for CI validation. .DESCRIPTION This script detects changes in the repository's folders and files, providing a structured @@ -53,9 +40,6 @@ else { .PARAMETER ApplicationPath Path to applications directory (default: src/500-application) -.PARAMETER DependencyFile - Path to application dependency configuration (default: src/500-application/dependency-graph.json) - .EXAMPLE # Default: Check both infrastructure and application changes: .\Detect-Folder-Changes.ps1 @@ -80,6 +64,9 @@ else { # Write output to a file with default detection: .\Detect-Folder-Changes.ps1 -OutputFile "changes.json" +.NOTES + Used by matrix-folder-check workflows to scope CI validation. + .OUTPUTS A JSON object with the following structure: { @@ -330,6 +317,8 @@ $terraformHasChanges = $false $terraformFolders = @{} $bicepHasChanges = $false $bicepFolders = @{} +$bicepFullValidationRequired = $false +$bicepFullValidationReasons = @() $fuzzRustHasChanges = $false $fuzzRustFolders = [PSCustomObject]@{ folderName = @() } $fuzzPythonHasChanges = $false @@ -544,6 +533,91 @@ function Test-ProviderRegChange { return @{ Shell = $shellChanges; PowerShell = $pwshChanges } } +function Get-BicepFullValidationReason { + param ( + [string[]]$Files + ) + + $reasons = [System.Collections.Generic.List[string]]::new() + + foreach ($file in $Files) { + $normalizedFile = $file -replace '\\', '/' + + if ($normalizedFile -eq 'bicepconfig.json') { + $reasons.Add('bicepconfig') + } + elseif ($normalizedFile -match '^blueprints/modules/') { + $reasons.Add('shared-blueprint-modules') + } + elseif ($normalizedFile -match '^scripts/build/Detect-Folder-Changes\.ps1$') { + $reasons.Add('change-detector') + } + elseif ($normalizedFile -match '^\.github/workflows/(bicep-lint|matrix-folder-check|pr-validation|main)\.yml$') { + $reasons.Add('github-workflow') + } + } + + return $reasons | Select-Object -Unique +} + +function Get-AllBicepFolderPath { + $bicepFiles = git ls-files 'src/**/*.bicep' 'blueprints/**/*.bicep' 2>$null + + if (-not $bicepFiles) { + return @() + } + + return Get-FilePathData -Paths $bicepFiles +} + +function Get-DependentBicepBlueprintPath { + param ( + [string[]]$ChangedPaths, + [string]$BlueprintRoot = (Join-Path $PSScriptRoot '../../blueprints') + ) + + if (-not $ChangedPaths -or $ChangedPaths.Count -eq 0) { + return @() + } + + $dependentBlueprints = @() + $blueprintMainFiles = Get-ChildItem -Path "$BlueprintRoot/*/bicep/main.bicep" -ErrorAction SilentlyContinue + + if (-not $blueprintMainFiles) { + return @() + } + + foreach ($changedPath in $ChangedPaths) { + if ($changedPath -notmatch '^src/') { + continue + } + + $componentPath = $changedPath -replace '/bicep(/.*)?$', '' + Write-Verbose "Checking for blueprints that depend on component: $componentPath" + + foreach ($blueprintFile in $blueprintMainFiles) { + $content = Get-Content $blueprintFile.FullName -Raw -ErrorAction SilentlyContinue + + if (-not $content) { + continue + } + + $escapedComponentPath = [regex]::Escape($componentPath) + $pattern = "module\s+\w+\s+'(\.\./)+$escapedComponentPath/bicep/main\.bicep'" + + if ($content -match $pattern) { + $blueprintPath = $blueprintFile.Directory.Parent.FullName -replace '\\', '/' + $blueprintRelativePath = $blueprintPath -replace '^.*?/blueprints/', 'blueprints/' + + Write-Verbose " Found dependent blueprint: $blueprintRelativePath" + $dependentBlueprints += $blueprintRelativePath + } + } + } + + return $dependentBlueprints | Select-Object -Unique +} + # Function to convert paths to JSON object structure function Convert-PathsToJson { param ( @@ -566,6 +640,9 @@ function Convert-PathsToJson { # Get changed files for IaC detection $changedFiles = Get-ChangedFileData -IncludeAll:$IncludeAllIaC -BaseBranch $BaseBranch +$bicepFullValidationReasons = @(Get-BicepFullValidationReason -Files $changedFiles) +$bicepFullValidationRequired = $bicepFullValidationReasons.Count -gt 0 + # Get all changed files for application detection if needed $allChangedFiles = $changedFiles if ($IncludeAllApplications -and $IncludeAllIaC) { @@ -689,47 +766,19 @@ if ($tfFiles) { } } -if ($bicepFiles) { +if ($bicepFullValidationRequired) { + $bicepHasChanges = $true + $bicepPaths = Get-AllBicepFolderPath + Write-Verbose "Full Bicep validation required: $($bicepFullValidationReasons -join ', ')" + Write-Verbose "Total Bicep folders to validate: $($bicepPaths.Count)" + $bicepFolders = Convert-PathsToJson -Paths $bicepPaths +} +elseif ($bicepFiles) { $bicepPaths = Get-FilePathData -Paths $bicepFiles if ($bicepPaths.Count -gt 0) { $bicepHasChanges = $true - # Detect dependent blueprints when components change - $dependentBlueprints = @() - - foreach ($changedPath in $bicepPaths) { - # Check if changed file is in src/ (a component) - if ($changedPath -match '^src/') { - # Extract component path (e.g., "src/000-cloud/010-security-identity") - $componentPath = $changedPath -replace '/bicep(/.*)?$', '' - - Write-Verbose "Checking for blueprints that depend on component: $componentPath" - - # Search all blueprint main.bicep files for this component - $blueprintMainFiles = Get-ChildItem -Path "$PSScriptRoot/../../blueprints/*/bicep/main.bicep" -ErrorAction SilentlyContinue - - foreach ($blueprintFile in $blueprintMainFiles) { - $content = Get-Content $blueprintFile.FullName -Raw -ErrorAction SilentlyContinue - - if ($content) { - # Check if blueprint references this component - # Pattern matches: module ... '../../../src/000-cloud/010-security-identity/bicep/main.bicep' - $escapedComponentPath = [regex]::Escape($componentPath) - $pattern = "module\s+\w+\s+'(\.\./)+$escapedComponentPath/bicep/main\.bicep'" - - if ($content -match $pattern) { - # Extract blueprint path (e.g., "blueprints/full-single-node-cluster") - $blueprintPath = $blueprintFile.Directory.Parent.FullName - $blueprintRelativePath = $blueprintPath -replace '.*[/\\]blueprints[/\\]', 'blueprints/' - $blueprintRelativePath = $blueprintRelativePath -replace '\\', '/' - - Write-Verbose " Found dependent blueprint: $blueprintRelativePath" - $dependentBlueprints += $blueprintRelativePath - } - } - } - } - } + $dependentBlueprints = Get-DependentBicepBlueprintPath -ChangedPaths $bicepPaths # Combine direct changes and dependent blueprints, removing duplicates $allPaths = @($bicepPaths) + @($dependentBlueprints) | Select-Object -Unique @@ -784,8 +833,10 @@ $jsonOutput | Add-Member -MemberType NoteProperty -Name "terraform" -Value ([PSC }) $jsonOutput | Add-Member -MemberType NoteProperty -Name "bicep" -Value ([PSCustomObject]@{ - has_changes = [bool]$bicepHasChanges - folders = $bicepFolders + has_changes = [bool]$bicepHasChanges + folders = $bicepFolders + full_validation_required = [bool]$bicepFullValidationRequired + full_validation_reasons = @($bicepFullValidationReasons) }) $jsonOutput | Add-Member -MemberType NoteProperty -Name "applications" -Value ([PSCustomObject]@{ diff --git a/scripts/build/Invoke-BicepLint.Tests.ps1 b/scripts/build/Invoke-BicepLint.Tests.ps1 new file mode 100644 index 000000000..ad86f1520 --- /dev/null +++ b/scripts/build/Invoke-BicepLint.Tests.ps1 @@ -0,0 +1,145 @@ +#Requires -Modules Pester +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT + +BeforeAll { + . (Join-Path $PSScriptRoot 'Invoke-BicepLint.ps1') +} + +Describe 'ConvertTo-BicepRunnerBoolean' -Tag 'Unit' { + It 'returns true for true values' { + ConvertTo-BicepRunnerBoolean -Value 'true' | Should -BeTrue + } + + It 'returns false for false values' { + ConvertTo-BicepRunnerBoolean -Value 'FALSE' | Should -BeFalse + } + + It 'throws for invalid values' { + { ConvertTo-BicepRunnerBoolean -Value 'maybe' } | Should -Throw -ExpectedMessage '*Expected boolean value*' + } +} + +Describe 'Get-BicepFolderPathFromJson' -Tag 'Unit' { + It 'returns folderName values from detector JSON' { + $json = @{ + 'src/000-cloud/010-security-identity/bicep' = @{ + folderName = 'src/000-cloud/010-security-identity/bicep' + } + 'blueprints/full-single-node-cluster/bicep' = @{ + folderName = 'blueprints/full-single-node-cluster/bicep' + } + } | ConvertTo-Json -Depth 4 -Compress + + $result = @(Get-BicepFolderPathFromJson -BicepFoldersJson $json) + + $result | Should -Contain 'src/000-cloud/010-security-identity/bicep' + $result | Should -Contain 'blueprints/full-single-node-cluster/bicep' + $result | Should -HaveCount 2 + } + + It 'deduplicates folderName arrays' { + $json = @{ + folders = @{ + folderName = @('src/a/bicep', 'src/a/bicep', 'blueprints/b/bicep') + } + } | ConvertTo-Json -Depth 5 -Compress + + $result = @(Get-BicepFolderPathFromJson -BicepFoldersJson $json) + + $result | Should -Contain 'src/a/bicep' + $result | Should -Contain 'blueprints/b/bicep' + $result | Should -HaveCount 2 + } + + It 'throws for invalid JSON' { + { Get-BicepFolderPathFromJson -BicepFoldersJson '{not-json}' } | + Should -Throw -ExpectedMessage '*Failed to parse Bicep folders JSON*' + } +} + +Describe 'Get-BicepValidationFile' -Tag 'Unit' { + BeforeEach { + $script:RepoRoot = Join-Path $TestDrive 'repo' + New-Item -ItemType Directory -Path $script:RepoRoot -Force | Out-Null + + $componentFolder = Join-Path $script:RepoRoot 'src/000-cloud/010-security-identity/bicep' + $blueprintFolder = Join-Path $script:RepoRoot 'blueprints/full-single-node-cluster/bicep' + $nodeFolder = Join-Path $script:RepoRoot 'node_modules/package/bicep' + $trackingFolder = Join-Path $script:RepoRoot '.copilot-tracking/generated/bicep' + + New-Item -ItemType Directory -Path $componentFolder, $blueprintFolder, $nodeFolder, $trackingFolder -Force | Out-Null + 'param name string' | Set-Content -Path (Join-Path $componentFolder 'main.bicep') + 'param location string' | Set-Content -Path (Join-Path $blueprintFolder 'main.bicep') + 'param ignored string' | Set-Content -Path (Join-Path $nodeFolder 'ignored.bicep') + 'param ignored string' | Set-Content -Path (Join-Path $trackingFolder 'ignored.bicep') + } + + It 'finds all repository Bicep files for full validation and excludes ignored paths' { + $result = @(Get-BicepValidationFile -RepoRoot $script:RepoRoot -FullValidation $true) + + $result | Should -Contain 'src/000-cloud/010-security-identity/bicep/main.bicep' + $result | Should -Contain 'blueprints/full-single-node-cluster/bicep/main.bicep' + $result | Should -Not -Contain 'node_modules/package/bicep/ignored.bicep' + $result | Should -Not -Contain '.copilot-tracking/generated/bicep/ignored.bicep' + $result | Should -HaveCount 2 + } + + It 'finds files only under scoped detector folders' { + $json = @{ + component = @{ folderName = 'src/000-cloud/010-security-identity/bicep' } + } | ConvertTo-Json -Depth 4 -Compress + + $result = @(Get-BicepValidationFile -RepoRoot $script:RepoRoot -FullValidation $false -BicepFoldersJson $json) + + $result | Should -Contain 'src/000-cloud/010-security-identity/bicep/main.bicep' + $result | Should -Not -Contain 'blueprints/full-single-node-cluster/bicep/main.bicep' + $result | Should -HaveCount 1 + } +} + +Describe 'Get-BicepValidationCommand' -Tag 'Unit' { + It 'uses lint when Azure CLI supports Bicep lint' { + Mock Test-BicepLintSupport { return $true } + + Get-BicepValidationCommand | Should -Be 'lint' + } + + It 'uses build when Azure CLI does not support Bicep lint' { + Mock Test-BicepLintSupport { return $false } + + Get-BicepValidationCommand | Should -Be 'build' + } +} + +Describe 'Write-BicepSummary' -Tag 'Unit' { + It 'writes a markdown summary with zero failures' { + $outputFile = Join-Path $TestDrive 'summary-success.md' + + $summary = Write-BicepSummary ` + -FilesChecked 2 ` + -Failures 0 ` + -FailedFiles @() ` + -OutputFile $outputFile + + $summary | Should -Match 'Files checked:.+2' + $summary | Should -Match 'Failures:.+0' + $summary | Should -Not -Match 'Failed files' + Get-Content -Path $outputFile -Raw | Should -Match '## Bicep Lint Results' + } + + It 'writes a markdown summary with failed files' { + $outputFile = Join-Path $TestDrive 'summary.md' + + $summary = Write-BicepSummary ` + -FilesChecked 2 ` + -Failures 1 ` + -FailedFiles @('src/app/bicep/main.bicep') ` + -OutputFile $outputFile + + $summary | Should -Match 'Files checked:.+2' + $summary | Should -Match 'Failures:.+1' + $summary | Should -Match 'src/app/bicep/main.bicep' + Get-Content -Path $outputFile -Raw | Should -Match '## Bicep Lint Results' + } +} diff --git a/scripts/build/Invoke-BicepLint.ps1 b/scripts/build/Invoke-BicepLint.ps1 new file mode 100644 index 000000000..aa1d2ffb2 --- /dev/null +++ b/scripts/build/Invoke-BicepLint.ps1 @@ -0,0 +1,510 @@ +#!/usr/bin/env pwsh +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT +#Requires -Version 7.0 + +<# +.SYNOPSIS + Runs Bicep validation for scoped or full repository inputs. +.DESCRIPTION + Validates Bicep files with az bicep lint when available and falls back to + az bicep build for Azure CLI versions that do not support lint. +.PARAMETER Platform + Reporting platform for annotations and outputs. +.PARAMETER FullValidation + Boolean string that selects full repository validation when true. +.PARAMETER BicepFoldersJson + JSON object emitted by Detect-Folder-Changes.ps1 for scoped validation. +.PARAMETER ResultsDir + Directory where command output artifacts are written. +.PARAMETER OutputFile + Markdown summary output path. +.PARAMETER RepoRoot + Repository root used to resolve relative folders and files. +.EXAMPLE + ./scripts/build/Invoke-BicepLint.ps1 -Platform github -FullValidation true +.NOTES + Used by .github/workflows/bicep-lint.yml. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $false)] + [ValidateSet('github', 'generic')] + [string]$Platform = 'generic', + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$FullValidation = 'true', + + [Parameter(Mandatory = $false)] + [string]$BicepFoldersJson = '{}', + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$ResultsDir = 'bicep-lint-results', + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$OutputFile = 'bicep-lint-output.txt', + + [Parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [string]$RepoRoot = (git rev-parse --show-toplevel 2>$null) +) + +$ErrorActionPreference = 'Stop' + +#region Functions +function ConvertTo-BicepRunnerBoolean { + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)] + [AllowEmptyString()] + [string]$Value + ) + + switch ($Value.ToLowerInvariant()) { + 'true' { return $true } + 'false' { return $false } + default { throw "Expected boolean value, got: $Value" } + } +} + +function Resolve-BicepRepoRoot { + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $false)] + [AllowEmptyString()] + [string]$Path + ) + + if (-not [string]::IsNullOrWhiteSpace($Path)) { + return [System.IO.Path]::GetFullPath($Path) + } + + $gitRoot = git rev-parse --show-toplevel 2>$null + if (-not [string]::IsNullOrWhiteSpace($gitRoot)) { + return [System.IO.Path]::GetFullPath($gitRoot.Trim()) + } + + return (Get-Location).Path +} + +function Test-ExcludedBicepPath { + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)] + [string]$RelativePath + ) + + $normalizedPath = $RelativePath -replace '\\', '/' + return $normalizedPath -match '(^|/)(node_modules|\.copilot-tracking)(/|$)' +} + +function ConvertTo-RepoRelativePath { + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $true)] + [string]$RepoRoot, + + [Parameter(Mandatory = $true)] + [string]$Path + ) + + $fullPath = [System.IO.Path]::GetFullPath($Path) + return [System.IO.Path]::GetRelativePath($RepoRoot, $fullPath) -replace '\\', '/' +} + +function Get-BicepFolderPathFromJson { + [CmdletBinding()] + [OutputType([string[]])] + param( + [Parameter(Mandatory = $false)] + [string]$BicepFoldersJson = '{}' + ) + + if ([string]::IsNullOrWhiteSpace($BicepFoldersJson)) { + return @() + } + + try { + $folderData = $BicepFoldersJson | ConvertFrom-Json + } + catch { + throw "Failed to parse Bicep folders JSON: $($_.Exception.Message)" + } + + if ($null -eq $folderData) { + return @() + } + + $folderPaths = [System.Collections.Generic.List[string]]::new() + $entries = if ($folderData -is [array]) { $folderData } else { @($folderData.PSObject.Properties.Value) } + + foreach ($entry in $entries) { + if ($null -eq $entry) { + continue + } + + $folderNameProperty = $entry.PSObject.Properties['folderName'] + if ($null -eq $folderNameProperty) { + continue + } + + foreach ($folderName in @($folderNameProperty.Value)) { + if (-not [string]::IsNullOrWhiteSpace([string]$folderName)) { + $folderPaths.Add(([string]$folderName -replace '\\', '/')) + } + } + } + + return @($folderPaths | Select-Object -Unique) +} + +function Get-BicepValidationFile { + [CmdletBinding()] + [OutputType([string[]])] + param( + [Parameter(Mandatory = $true)] + [string]$RepoRoot, + + [Parameter(Mandatory = $true)] + [bool]$FullValidation, + + [Parameter(Mandatory = $false)] + [string]$BicepFoldersJson = '{}' + ) + + $resolvedRepoRoot = Resolve-BicepRepoRoot -Path $RepoRoot + $filePaths = [System.Collections.Generic.List[string]]::new() + + if ($FullValidation) { + $candidateFiles = Get-ChildItem -Path $resolvedRepoRoot -Filter '*.bicep' -File -Recurse -ErrorAction SilentlyContinue + } + else { + $candidateFiles = @() + foreach ($folderPath in Get-BicepFolderPathFromJson -BicepFoldersJson $BicepFoldersJson) { + $resolvedFolderPath = if ([System.IO.Path]::IsPathRooted($folderPath)) { + $folderPath + } + else { + Join-Path $resolvedRepoRoot $folderPath + } + + if (-not (Test-Path -Path $resolvedFolderPath -PathType Container)) { + Write-BicepWarning -Platform 'generic' -Message "Bicep folder does not exist: $folderPath" + continue + } + + $candidateFiles += Get-ChildItem -Path $resolvedFolderPath -Filter '*.bicep' -File -Recurse -ErrorAction SilentlyContinue + } + } + + foreach ($candidateFile in $candidateFiles) { + $relativePath = ConvertTo-RepoRelativePath -RepoRoot $resolvedRepoRoot -Path $candidateFile.FullName + if (-not (Test-ExcludedBicepPath -RelativePath $relativePath)) { + $filePaths.Add($relativePath) + } + } + + return @($filePaths | Select-Object -Unique | Sort-Object) +} + +function Test-BicepLintSupport { + [CmdletBinding()] + [OutputType([bool])] + param() + + $null = & az bicep lint --help 2>$null + return $LASTEXITCODE -eq 0 +} + +function Get-BicepValidationCommand { + [CmdletBinding()] + [OutputType([string])] + param() + + if (Test-BicepLintSupport) { + return 'lint' + } + + return 'build' +} + +function Write-BicepWarning { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateSet('github', 'generic')] + [string]$Platform, + + [Parameter(Mandatory = $true)] + [string]$Message + ) + + if ($Platform -eq 'github') { + Write-Output "::warning::$Message" + return + } + + Write-Warning $Message +} + +function Write-BicepFailure { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateSet('github', 'generic')] + [string]$Platform, + + [Parameter(Mandatory = $true)] + [string]$FilePath, + + [Parameter(Mandatory = $true)] + [string]$CommandName + ) + + if ($Platform -eq 'github') { + Write-Output "::error file=$FilePath::Bicep $CommandName failed for $FilePath" + return + } + + Write-Error -ErrorAction Continue "Bicep $CommandName failed for $FilePath" +} + +function Start-BicepLogGroup { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateSet('github', 'generic')] + [string]$Platform, + + [Parameter(Mandatory = $true)] + [string]$FilePath + ) + + if ($Platform -eq 'github') { + Write-Output "::group::Validating: $FilePath" + return + } + + Write-Host "Validating: $FilePath" +} + +function Stop-BicepLogGroup { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateSet('github', 'generic')] + [string]$Platform + ) + + if ($Platform -eq 'github') { + Write-Output '::endgroup::' + } +} + +function Invoke-BicepFileValidation { + [CmdletBinding()] + [OutputType([bool])] + param( + [Parameter(Mandatory = $true)] + [ValidateSet('lint', 'build')] + [string]$CommandName, + + [Parameter(Mandatory = $true)] + [string]$FilePath, + + [Parameter(Mandatory = $true)] + [string]$ResultsFile + ) + + if ($CommandName -eq 'lint') { + $commandOutput = & az bicep lint --file $FilePath 2>&1 + } + else { + $commandOutput = & az bicep build --file $FilePath --stdout 2>&1 + } + + $exitCode = $LASTEXITCODE + if ($commandOutput) { + $commandOutput | Add-Content -Path $ResultsFile -Encoding UTF8 + if ($exitCode -ne 0) { + $commandOutput | ForEach-Object { Write-Output $_ } + } + } + + return $exitCode -eq 0 +} + +function Write-BicepSummary { + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Mandatory = $true)] + [int]$FilesChecked, + + [Parameter(Mandatory = $true)] + [int]$Failures, + + [Parameter(Mandatory = $true)] + [AllowEmptyCollection()] + [string[]]$FailedFiles, + + [Parameter(Mandatory = $true)] + [string]$OutputFile + ) + + $summaryLines = [System.Collections.Generic.List[string]]::new() + $summaryLines.Add('## Bicep Lint Results') + $summaryLines.Add('') + $summaryLines.Add("- **Files checked:** $FilesChecked") + $summaryLines.Add("- **Failures:** $Failures") + + if ($Failures -gt 0) { + $summaryLines.Add('') + $summaryLines.Add('### Failed files') + foreach ($failedFile in $FailedFiles) { + $summaryLines.Add("- $failedFile") + } + } + + $summary = $summaryLines -join [Environment]::NewLine + $summary | Set-Content -Path $OutputFile -Encoding UTF8 + return $summary +} + +function Write-GitHubBicepOutput { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [int]$FilesChecked, + + [Parameter(Mandatory = $true)] + [int]$Failures, + + [Parameter(Mandatory = $true)] + [string]$Summary + ) + + if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_OUTPUT)) { + "files-checked=$FilesChecked" | Add-Content -Path $env:GITHUB_OUTPUT -Encoding UTF8 + "failures=$Failures" | Add-Content -Path $env:GITHUB_OUTPUT -Encoding UTF8 + } + + if ($Failures -gt 0 -and -not [string]::IsNullOrWhiteSpace($env:GITHUB_ENV)) { + 'BICEP_LINT_FAILED=true' | Add-Content -Path $env:GITHUB_ENV -Encoding UTF8 + } + + if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_STEP_SUMMARY)) { + $Summary | Add-Content -Path $env:GITHUB_STEP_SUMMARY -Encoding UTF8 + } +} + +function Invoke-BicepLint { + [CmdletBinding()] + [OutputType([pscustomobject])] + param( + [Parameter(Mandatory = $true)] + [ValidateSet('github', 'generic')] + [string]$Platform, + + [Parameter(Mandatory = $true)] + [string]$FullValidation, + + [Parameter(Mandatory = $false)] + [string]$BicepFoldersJson = '{}', + + [Parameter(Mandatory = $true)] + [string]$ResultsDir, + + [Parameter(Mandatory = $true)] + [string]$OutputFile, + + [Parameter(Mandatory = $true)] + [string]$RepoRoot + ) + + if (-not (Get-Command az -ErrorAction SilentlyContinue)) { + throw "'az' command is required but not installed" + } + + $resolvedRepoRoot = Resolve-BicepRepoRoot -Path $RepoRoot + $fullValidationEnabled = ConvertTo-BicepRunnerBoolean -Value $FullValidation + $bicepFiles = @(Get-BicepValidationFile -RepoRoot $resolvedRepoRoot -FullValidation $fullValidationEnabled -BicepFoldersJson $BicepFoldersJson) + $validationCommand = Get-BicepValidationCommand + $failedFiles = [System.Collections.Generic.List[string]]::new() + $filesChecked = 0 + + New-Item -ItemType Directory -Path $ResultsDir -Force | Out-Null + $resultsFile = Join-Path $ResultsDir "bicep-$validationCommand-results.txt" + + Write-Host "Bicep validation mode: $fullValidationEnabled" + Write-Host "Bicep command: az bicep $validationCommand --file" + + if ($bicepFiles.Count -eq 0) { + Write-Host 'No Bicep files found to validate' + } + + Push-Location $resolvedRepoRoot + try { + foreach ($bicepFile in $bicepFiles) { + $filesChecked++ + Start-BicepLogGroup -Platform $Platform -FilePath $bicepFile + $passed = Invoke-BicepFileValidation -CommandName $validationCommand -FilePath $bicepFile -ResultsFile $resultsFile + + if (-not $passed) { + $failedFiles.Add($bicepFile) + Write-BicepFailure -Platform $Platform -FilePath $bicepFile -CommandName $validationCommand + } + + Stop-BicepLogGroup -Platform $Platform + } + } + finally { + Pop-Location + } + + $summary = Write-BicepSummary -FilesChecked $filesChecked -Failures $failedFiles.Count -FailedFiles @($failedFiles) -OutputFile $OutputFile + + if ($Platform -eq 'github') { + Write-GitHubBicepOutput -FilesChecked $filesChecked -Failures $failedFiles.Count -Summary $summary + } + + return [PSCustomObject]@{ + FilesChecked = $filesChecked + Failures = $failedFiles.Count + FailedFiles = @($failedFiles) + Command = $validationCommand + } +} +#endregion Functions + +#region Main Execution +if ($MyInvocation.InvocationName -ne '.') { + try { + $result = Invoke-BicepLint ` + -Platform $Platform ` + -FullValidation $FullValidation ` + -BicepFoldersJson $BicepFoldersJson ` + -ResultsDir $ResultsDir ` + -OutputFile $OutputFile ` + -RepoRoot (Resolve-BicepRepoRoot -Path $RepoRoot) + + if ($Platform -eq 'generic' -and $result.Failures -gt 0) { + exit 1 + } + + exit 0 + } + catch { + Write-Error -ErrorAction Continue "Invoke-BicepLint failed: $($_.Exception.Message)" + exit 1 + } +} +#endregion Main Execution From a78d8d0fda20e78ffd69d094195892e50c6e5223 Mon Sep 17 00:00:00 2001 From: Bill Berry Date: Sun, 24 May 2026 10:05:10 -0700 Subject: [PATCH 2/4] fix(scripts): resolve PowerShell lint violations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿงน - Generated by Copilot --- scripts/build/Invoke-BicepLint.ps1 | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/build/Invoke-BicepLint.ps1 b/scripts/build/Invoke-BicepLint.ps1 index aa1d2ffb2..065e347e9 100644 --- a/scripts/build/Invoke-BicepLint.ps1 +++ b/scripts/build/Invoke-BicepLint.ps1 @@ -122,7 +122,7 @@ function ConvertTo-RepoRelativePath { function Get-BicepFolderPathFromJson { [CmdletBinding()] - [OutputType([string[]])] + [OutputType([object[]])] param( [Parameter(Mandatory = $false)] [string]$BicepFoldersJson = '{}' @@ -168,7 +168,7 @@ function Get-BicepFolderPathFromJson { function Get-BicepValidationFile { [CmdletBinding()] - [OutputType([string[]])] + [OutputType([object[]])] param( [Parameter(Mandatory = $true)] [string]$RepoRoot, @@ -278,7 +278,7 @@ function Write-BicepFailure { } function Start-BicepLogGroup { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true)] [ValidateSet('github', 'generic')] @@ -288,6 +288,10 @@ function Start-BicepLogGroup { [string]$FilePath ) + if (-not $PSCmdlet.ShouldProcess($FilePath, 'Start Bicep log group')) { + return + } + if ($Platform -eq 'github') { Write-Output "::group::Validating: $FilePath" return @@ -297,13 +301,17 @@ function Start-BicepLogGroup { } function Stop-BicepLogGroup { - [CmdletBinding()] + [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true)] [ValidateSet('github', 'generic')] [string]$Platform ) + if (-not $PSCmdlet.ShouldProcess($Platform, 'Stop Bicep log group')) { + return + } + if ($Platform -eq 'github') { Write-Output '::endgroup::' } From 1365c6bdd1f7a290f010cd3b7d53c0aefe32b627 Mon Sep 17 00:00:00 2001 From: Bill Berry Date: Tue, 9 Jun 2026 20:43:43 -0700 Subject: [PATCH 3/4] build(build): optimize validation workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ”ง - Generated by Copilot --- .github/dependabot.yml | 30 ++++++++++++- .github/workflows/docs-automation.yml | 35 +++++++++------ .github/workflows/docs-check-bicep.yml | 44 ++++++++----------- .github/workflows/docs-check-terraform.yml | 44 +++++++++---------- .github/workflows/docs-lint.yml | 1 + .github/workflows/pr-validation.yml | 1 + .github/workflows/security-scan.yml | 42 +++++++++++++++++- .../workflows/workflow-permissions-scan.yml | 1 + .../full-single-node-cluster/tests/go.mod | 14 +++--- .../full-single-node-cluster/tests/go.sum | 28 ++++++------ .../904-test-utilities/go.mod | 12 ++--- .../904-test-utilities/go.sum | 28 ++++++------ 12 files changed, 174 insertions(+), 106 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e3f40febc..c9fba2e0b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,9 @@ +--- # Dependabot configuration # # Grouping policy (aligned with microsoft/hve-core, optimized for fewer PRs): # * One grouped PR per ecosystem per week (Monday 09:00 US/Pacific). -# Floor = 7 PRs/week (one per ecosystem) since Dependabot cannot group +# Floor = 8 PRs/week (one per ecosystem) since Dependabot cannot group # across ecosystems. # * Each ecosystem uses a single group named after the ecosystem itself, # pattern "*", and update-types ["major", "minor", "patch"] so every @@ -95,6 +96,33 @@ updates: - "patch" open-pull-requests-limit: 5 + # Go modules + - package-ecosystem: "gomod" + directories: + - "/blueprints/full-single-node-cluster/tests" + - "/src/900-tools-utilities/904-test-utilities" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + timezone: "US/Pacific" + commit-message: + prefix: "chore" + include: "scope" + labels: + - "dependencies" + - "security" + - "gomod" + groups: + gomod: + patterns: + - "*" + update-types: + - "major" + - "minor" + - "patch" + open-pull-requests-limit: 5 + # pip - package-ecosystem: "pip" directories: diff --git a/.github/workflows/docs-automation.yml b/.github/workflows/docs-automation.yml index 4c9fa3c2f..0e33e19c0 100644 --- a/.github/workflows/docs-automation.yml +++ b/.github/workflows/docs-automation.yml @@ -71,20 +71,28 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Run docs health check - uses: Azure/powershell@f5b8adcfff1904872c7b98d4012d4914d74b1a82 # v3.0.0 - with: - inlineScript: | - Write-Host "PowerShell version: $($PSVersionTable.PSVersion)" - azPSVersion: 'latest' + - name: Fetch base ref for changed-files scoping + if: github.event_name == 'pull_request' + shell: bash + run: | + set -euo pipefail + git fetch --no-tags --depth=1 origin "$GITHUB_BASE_REF" - name: Validate frontmatter consistency shell: pwsh run: | Write-Host "๐Ÿ” Validating frontmatter across all markdown files..." - # Use the dedicated validation script (same as Azure DevOps pipeline) - & "./scripts/Validate-MarkdownFrontmatter.ps1" -Paths @('docs', 'src', 'blueprints') -Verbose + $frontmatterArgs = @{ + Paths = @('docs', 'src', 'blueprints') + Verbose = $true + } + if ($env:GITHUB_EVENT_NAME -eq 'pull_request') { + $frontmatterArgs['ChangedFilesOnly'] = $true + $frontmatterArgs['BaseBranch'] = "origin/$($env:GITHUB_BASE_REF)" + } + + & "./scripts/Validate-MarkdownFrontmatter.ps1" @frontmatterArgs if ($LASTEXITCODE -ne 0) { Write-Host "โŒ Frontmatter validation failed" -ForegroundColor Red @@ -96,7 +104,6 @@ jobs: validate-links: name: ๐Ÿ”— Validate Documentation Links runs-on: ubuntu-latest - needs: validate-frontmatter permissions: contents: read steps: @@ -107,6 +114,8 @@ jobs: uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' + cache: 'npm' + cache-dependency-path: '.github/scripts/markdown-link-check/package-lock.json' - name: Install markdown-link-check # Pinned via .github/scripts/markdown-link-check/package-lock.json so the @@ -142,10 +151,10 @@ jobs: # Check all markdown files MLC=".github/scripts/markdown-link-check/node_modules/.bin/markdown-link-check" find docs src blueprints .github copilot \ - -name "*.md" -type f | \ - grep -v node_modules | \ - xargs -I {} sh -c 'echo "Checking: {}" && - "'"$MLC"'" {} --config .markdown-link-check.json' + -type f -name '*.md' \ + -not -path '*/node_modules/*' \ + -print0 \ + | xargs -0 -P "$(nproc)" -n 25 "$MLC" --config .markdown-link-check.json report-documentation-health: name: ๐Ÿ“Š Documentation Health Report diff --git a/.github/workflows/docs-check-bicep.yml b/.github/workflows/docs-check-bicep.yml index 0c840d270..e020eb3e5 100644 --- a/.github/workflows/docs-check-bicep.yml +++ b/.github/workflows/docs-check-bicep.yml @@ -67,14 +67,6 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 # Full git history for comparison - - - name: Setup PowerShell - run: | - echo "PowerShell Version: $($PSVersionTable.PSVersion)" - echo "PowerShell Edition: $($PSVersionTable.PSEdition)" - shell: pwsh - name: Setup Azure CLI with Bicep run: | @@ -104,27 +96,29 @@ jobs: az bicep version shell: bash - - name: Check for changes in Bicep documentation - id: docs-check-bicep + - name: Check Bicep docs drift + id: bicep_docs_drift + shell: bash run: | - # Run the bicep-docs-check.sh script and capture its output - docs_changed=$(tail -n 1 $(pwd)/scripts/bicep-docs-check.sh) - - # Check if there are any changes in the Bicep documentation - if [ "$docs_changed" = true ]; then - echo "Updates are required for Bicep documentation." - echo "Please go into the project's scripts directory, run the update-all-bicep-docs.sh script and commit changes." + set -euo pipefail + # bicep-docs-check.sh writes progress to stdout and ends with `echo $docs_changed` (true|false) + docs_changed="$(./scripts/bicep-docs-check.sh | tail -n 1)" + echo "changed=${docs_changed}" >> "$GITHUB_OUTPUT" - echo "::warning::Bicep auto-gen documentation needs to be updated. Please run the scripts/update-all-bicep-docs.sh script and commit the changes." + - name: Report Bicep docs drift + if: steps.bicep_docs_drift.outputs.changed == 'true' + shell: bash + run: | + echo "Updates are required for Bicep documentation." + echo "Please go into the project's scripts directory, run the update-all-bicep-docs.sh script and commit changes." + echo "::warning::Bicep auto-gen documentation needs to be updated. Please run the scripts/update-all-bicep-docs.sh script and commit the changes." - if [[ "${{ env.BREAK_BUILD }}" == "true" ]]; then - echo "::error::Bicep auto-gen documentation needs to be updated. Please run the scripts/update-all-bicep-docs.sh script and commit the changes." - exit 1 - fi - else - echo "No updates detected in the Bicep documentation." - fi + - name: Fail when Bicep docs are out of date + if: steps.bicep_docs_drift.outputs.changed == 'true' && env.BREAK_BUILD == 'true' shell: bash + run: | + echo "::error::Bicep auto-gen documentation needs to be updated. Please run the scripts/update-all-bicep-docs.sh script and commit the changes." + exit 1 - name: Check for language path segments in links id: link-lang-check diff --git a/.github/workflows/docs-check-terraform.yml b/.github/workflows/docs-check-terraform.yml index 8c4e260a7..cd2866d06 100644 --- a/.github/workflows/docs-check-terraform.yml +++ b/.github/workflows/docs-check-terraform.yml @@ -78,39 +78,35 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 # Full git history for comparison - - - name: Setup PowerShell - run: | - echo "PowerShell Version: $($PSVersionTable.PSVersion)" - echo "PowerShell Edition: $($PSVersionTable.PSEdition)" - shell: pwsh - name: Install terraform-docs run: | ./scripts/install-terraform-docs.sh -v "${{ inputs.terraformDocsVersion }}" shell: bash - - name: Check for changes in documentation + - name: Check Terraform docs drift + id: tf_docs_drift + shell: bash run: | - # Call tf-docs-check.sh script to check for changes - ./scripts/tf-docs-check.sh - readme_changed=$(tail -n 1 $(pwd)/scripts/tf-docs-check.sh) + set -euo pipefail + # tf-docs-check.sh writes progress to stdout and ends with `echo $readme_changed` (true|false) + readme_changed="$(./scripts/tf-docs-check.sh | tail -n 1)" + echo "changed=${readme_changed}" >> "$GITHUB_OUTPUT" - # Check if there are any changes in the documentation - if [ "$readme_changed" = true ]; then - echo "Updates are required for documentation." - echo "Please go into the project's src directory, run the update-all-terraform-docs.sh script, and commit changes." - echo "::warning::Documentation needs to be updated. Please run the update-all-terraform-docs.sh script and commit the changes." - if [[ "${{ inputs.break_build }}" == "true" ]]; then - echo "::error::Documentation needs to be updated. Please run the update-all-terraform-docs.sh script and commit the changes." - exit 1 - fi - else - echo "No updates detected in the documentation." - fi + - name: Report Terraform docs drift + if: steps.tf_docs_drift.outputs.changed == 'true' shell: bash + run: | + echo "Updates are required for documentation." + echo "Please go into the project's src directory, run the update-all-terraform-docs.sh script, and commit changes." + echo "::warning::Documentation needs to be updated. Please run the update-all-terraform-docs.sh script and commit the changes." + + - name: Fail when Terraform docs are out of date + if: steps.tf_docs_drift.outputs.changed == 'true' && inputs.break_build == true + shell: bash + run: | + echo "::error::Documentation needs to be updated. Please run the update-all-terraform-docs.sh script and commit the changes." + exit 1 - name: Check for language path segments in links id: link-lang-check diff --git a/.github/workflows/docs-lint.yml b/.github/workflows/docs-lint.yml index 9d43080d3..b2efda198 100644 --- a/.github/workflows/docs-lint.yml +++ b/.github/workflows/docs-lint.yml @@ -49,6 +49,7 @@ jobs: uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24' + cache: 'npm' - name: Install dependencies run: npm ci diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index f79828047..c0423923e 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -399,6 +399,7 @@ jobs: - code-quality-lint - powershell-lint - security-scan + - permissions-scan - yaml-lint - docs-automation - docusaurus-tests diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 8cd966407..369764a2c 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -105,6 +105,16 @@ jobs: set -euo pipefail if ! grype dir:. --config .grype.yaml 2>&1 | tee security-results/grype-results.txt; then echo "GRYPE_FAILED=true" >> "$GITHUB_ENV" + + first_finding=$(awk ' + / fixed in / || /GO-[0-9]{4}-[0-9]+/ || /(Critical|High)/ {print; exit} + ' security-results/grype-results.txt) + + if [ -n "${first_finding}" ]; then + echo "::error title=Grype vulnerability threshold failed::${first_finding}" + else + echo "::error title=Grype vulnerability threshold failed::Grype found vulnerabilities at or above the .grype.yaml fail-on-severity threshold" + fi fi - name: Setup Node.js @@ -156,6 +166,22 @@ jobs: fi if [ "${GRYPE_FAILED:-}" = "true" ]; then echo "- :x: Grype: vulnerabilities found" + echo "" + echo "### Grype failure details" + echo "" + echo "Grype failed because at least one discovered vulnerability met or exceeded the .grype.yaml fail-on-severity threshold." + echo "" + if [ -s security-results/grype-results.txt ]; then + echo '```text' + awk ' + /^NAME[[:space:]]+/ {print; next} + / fixed in / || /GO-[0-9]{4}-[0-9]+/ || /(Critical|High)/ {print} + ' security-results/grype-results.txt | head -n 40 + echo '```' + echo "Full output is available in the security-scan-results artifact at security-results/grype-results.txt." + else + echo "No Grype output file was captured." + fi else echo "- :white_check_mark: Grype: passed" fi @@ -167,7 +193,7 @@ jobs: } >> "$GITHUB_STEP_SUMMARY" - name: Fail on Security Violations - if: always() && (env.GITLEAKS_FAILED == 'true' || env.GRYPE_FAILED == 'true' || env.SECRETLINT_FAILED == 'true') + if: always() shell: bash env: SOFT_FAIL: ${{ inputs.soft-fail }} @@ -182,13 +208,25 @@ jobs: echo "::warning::Grype vulnerabilities found (grype-soft-fail enabled, Gitleaks/Secretlint remain enforced)" else HARD_FAILURE="true" + echo "::group::Grype failure details" + echo "Grype found vulnerabilities at or above the .grype.yaml fail-on-severity threshold." + echo "Top Grype findings:" + if [ -s security-results/grype-results.txt ]; then + awk ' + /^NAME[[:space:]]+/ {print; next} + / fixed in / || /GO-[0-9]{4}-[0-9]+/ || /(Critical|High)/ {print} + ' security-results/grype-results.txt | head -n 40 + else + echo "No Grype output file was captured." + fi + echo "::endgroup::" fi fi if [ "$HARD_FAILURE" = "true" ]; then if [ "$SOFT_FAIL" = "true" ]; then echo "::warning::Security scan violations found (soft-fail enabled)" else - echo "::error::Security scan violations found" + echo "::error title=Security scan violations found::Review the failing scanner details above and the security scan job summary." exit 1 fi fi diff --git a/.github/workflows/workflow-permissions-scan.yml b/.github/workflows/workflow-permissions-scan.yml index afc77ba3a..53fe4fc14 100644 --- a/.github/workflows/workflow-permissions-scan.yml +++ b/.github/workflows/workflow-permissions-scan.yml @@ -1,3 +1,4 @@ +--- # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # Vendored from microsoft/hve-core@e158d88237e6b5e0fb57cb707dfc82410aa86702 diff --git a/blueprints/full-single-node-cluster/tests/go.mod b/blueprints/full-single-node-cluster/tests/go.mod index 1eab507c3..6ba611723 100644 --- a/blueprints/full-single-node-cluster/tests/go.mod +++ b/blueprints/full-single-node-cluster/tests/go.mod @@ -148,16 +148,16 @@ require ( go.opentelemetry.io/otel/trace v1.41.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/term v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/blueprints/full-single-node-cluster/tests/go.sum b/blueprints/full-single-node-cluster/tests/go.sum index cbe699889..1394c7e89 100644 --- a/blueprints/full-single-node-cluster/tests/go.sum +++ b/blueprints/full-single-node-cluster/tests/go.sum @@ -383,19 +383,19 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -411,23 +411,23 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/src/900-tools-utilities/904-test-utilities/go.mod b/src/900-tools-utilities/904-test-utilities/go.mod index dabd2242a..b79e9215d 100644 --- a/src/900-tools-utilities/904-test-utilities/go.mod +++ b/src/900-tools-utilities/904-test-utilities/go.mod @@ -30,12 +30,12 @@ require ( github.com/tmccombs/hcl2json v0.6.4 // indirect github.com/ulikunitz/xz v0.5.15 // indirect github.com/zclconf/go-cty v1.15.0 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/tools v0.44.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/src/900-tools-utilities/904-test-utilities/go.sum b/src/900-tools-utilities/904-test-utilities/go.sum index bd6e3507a..ac0f7c00e 100644 --- a/src/900-tools-utilities/904-test-utilities/go.sum +++ b/src/900-tools-utilities/904-test-utilities/go.sum @@ -52,22 +52,22 @@ github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 07f630935a414850a0bc49dd11486e87d1b4546b Mon Sep 17 00:00:00 2001 From: Bill Berry Date: Wed, 10 Jun 2026 09:20:27 -0700 Subject: [PATCH 4/4] fix(build): remove overlapping gomod dependabot config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ”ง - Generated by Copilot --- .github/dependabot.yml | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 39edfc6e1..2f00cc5d6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -96,33 +96,6 @@ updates: - "patch" open-pull-requests-limit: 5 - # Go modules - - package-ecosystem: "gomod" - directories: - - "/blueprints/full-single-node-cluster/tests" - - "/src/900-tools-utilities/904-test-utilities" - schedule: - interval: "weekly" - day: "monday" - time: "09:00" - timezone: "US/Pacific" - commit-message: - prefix: "chore" - include: "scope" - labels: - - "dependencies" - - "security" - - "gomod" - groups: - gomod: - patterns: - - "*" - update-types: - - "major" - - "minor" - - "patch" - open-pull-requests-limit: 5 - # pip - package-ecosystem: "pip" directories: