diff --git a/.github/workflows/ci-cd-pipeline.yml b/.github/workflows/ci-cd-pipeline.yml deleted file mode 100644 index 66f5f9b..0000000 --- a/.github/workflows/ci-cd-pipeline.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: PowerShell CI/CD Pipeline - -on: - push: - branches: - - main - - develop - -permissions: - contents: read - -jobs: - lint-and-test: - runs-on: windows-latest - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1 - with: - egress-policy: audit - - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - # Install PSScriptAnalyzer - - name: Install PSScriptAnalyzer - run: Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck - - # Run PSScriptAnalyzer - - name: Run PSScriptAnalyzer - run: Invoke-ScriptAnalyzer -Path . -Recurse - - # Install Pester for testing - - name: Install Pester - run: Install-Module -Name Pester -Force -SkipPublisherCheck - - # Run Pester tests - - name: Run Pester Tests - run: Invoke-Pester -Path ./tests - - # Uncomment this section when you're ready to add packaging and deployment - # package-and-deploy: - # runs-on: windows-latest - # needs: lint-and-test - # if: github.ref == 'refs/heads/main' # Only run for the main branch - # steps: - # - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - # - # # Package Scripts - # - name: Package Scripts - # run: Compress-Archive -Path ./scripts/* -DestinationPath ./scripts.zip - # - # # Upload Release Asset to GitHub Releases - # - name: Upload Release Asset - # uses: softprops/action-gh-release@v1 - # with: - # files: scripts.zip - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # - # # Deployment steps (uncomment and modify when you're ready) - # - name: Deploy to Remote Server - # run: | - # # Deployment steps here diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5957b37 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,257 @@ +name: CI + +on: + pull_request: + branches: [develop, main] + paths-ignore: + - "docs/**" + - "**/*.md" + - ".vscode/**" + push: + branches: [develop, main] + paths-ignore: + - "docs/**" + - "**/*.md" + - ".vscode/**" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + PSSA_VERSION: "1.24.0" + PESTER_VERSION: "5.7.1" + +jobs: + analyze: + name: PSScriptAnalyzer + runs-on: ubuntu-24.04 + timeout-minutes: 10 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Cache PowerShell modules (Linux) + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/.local/share/powershell/Modules + key: linux-psmodules-pssa-${{ env.PSSA_VERSION }} + restore-keys: | + linux-psmodules- + + - name: Install PSScriptAnalyzer + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $moduleName = 'PSScriptAnalyzer' + $requiredVersion = [Version]$env:PSSA_VERSION + + $installed = Get-Module -ListAvailable -Name $moduleName | Where-Object { $_.Version -eq $requiredVersion } + if (-not $installed) { + Install-Module -Name $moduleName -RequiredVersion $requiredVersion.ToString() -Scope CurrentUser -Force -AllowClobber + } + + - name: Run PSScriptAnalyzer (repo policy) + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $settingsPath = Join-Path $PWD 'PSScriptAnalyzerSettings.psd1' + if (-not (Test-Path $settingsPath)) { throw "Settings file not found at $settingsPath" } + + $targets = @( + 'modules' + 'scripts' + 'automation/runbooks' + ) | Where-Object { Test-Path $_ } + + if (-not $targets) { + Write-Host "No analyzer targets found; skipping." + exit 0 + } + + $results = foreach ($t in $targets) { + Invoke-ScriptAnalyzer -Path $t -Recurse -Settings $settingsPath + } + + if ($results) { + $results | Format-Table RuleName, Severity, ScriptName, Line, Message -AutoSize + throw "PSScriptAnalyzer found $($results.Count) issue(s)." + } + + test_ps51: + name: Pester (Windows PowerShell 5.1) + runs-on: windows-2022 + timeout-minutes: 20 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Cache PowerShell modules (Windows) + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~\Documents\PowerShell\Modules + key: windows-psmodules-pester-${{ env.PESTER_VERSION }} + restore-keys: | + windows-psmodules- + + - name: Install Pester + shell: powershell + run: | + $ErrorActionPreference = 'Stop' + $moduleName = 'Pester' + $requiredVersion = [Version]$env:PESTER_VERSION + + $installed = Get-Module -ListAvailable -Name $moduleName | Where-Object { $_.Version -eq $requiredVersion } + if (-not $installed) { + Install-Module -Name $moduleName -RequiredVersion $requiredVersion.ToString() -Scope CurrentUser -Force -AllowClobber + } + + - name: Run Pester + shell: powershell + run: | + $ErrorActionPreference = 'Stop' + + $resultsPath = Join-Path $env:GITHUB_WORKSPACE 'Output\TestResults' + New-Item -ItemType Directory -Path $resultsPath -Force | Out-Null + $outFile = Join-Path $resultsPath 'PesterResults_WindowsPowerShell-5.1.xml' + + Import-Module Pester -RequiredVersion $env:PESTER_VERSION -Force + + $paths = @() + + # Module tests: modules/**/Tests + $moduleTests = Get-ChildItem -Path 'modules' -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'Tests' } | + ForEach-Object { $_.FullName } + if ($moduleTests) { $paths += $moduleTests } + + # Script tests: scripts/**/Tests + $scriptTests = Get-ChildItem -Path 'scripts' -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'Tests' } | + ForEach-Object { $_.FullName } + if ($scriptTests) { $paths += $scriptTests } + + if (-not $paths) { + Write-Host 'No Pester tests found (modules/**/Tests or scripts/**/Tests). Skipping.' + exit 0 + } + + $config = [PesterConfiguration]::Default + $config.Run.Path = $paths + $config.Run.PassThru = $true + $config.Output.Verbosity = 'Detailed' + $config.TestResult.Enabled = $true + $config.TestResult.OutputFormat = 'JUnitXml' + $config.TestResult.OutputPath = $outFile + + $result = Invoke-Pester -Configuration $config + if ($result.FailedCount -gt 0) { throw "Pester failures: $($result.FailedCount)" } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: pester-results-windows-ps51 + path: Output/TestResults/*.xml + retention-days: 14 + if-no-files-found: warn + + test_pwsh7: + name: Pester (PowerShell 7) + runs-on: windows-2022 + timeout-minutes: 20 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Cache PowerShell modules (Windows) + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~\Documents\PowerShell\Modules + key: windows-psmodules-pester-${{ env.PESTER_VERSION }} + restore-keys: | + windows-psmodules- + + - name: Install Pester + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $moduleName = 'Pester' + $requiredVersion = [Version]$env:PESTER_VERSION + + $installed = Get-Module -ListAvailable -Name $moduleName | Where-Object { $_.Version -eq $requiredVersion } + if (-not $installed) { + Install-Module -Name $moduleName -RequiredVersion $requiredVersion.ToString() -Scope CurrentUser -Force -AllowClobber + } + + - name: Run Pester + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + + $resultsPath = Join-Path $env:GITHUB_WORKSPACE 'Output/TestResults' + New-Item -ItemType Directory -Path $resultsPath -Force | Out-Null + $outFile = Join-Path $resultsPath 'PesterResults_PowerShell-7.xml' + + Import-Module Pester -RequiredVersion $env:PESTER_VERSION -Force + + $paths = @() + + # Module tests: modules/**/Tests + $moduleTests = Get-ChildItem -Path 'modules' -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'Tests' } | + ForEach-Object { $_.FullName } + if ($moduleTests) { $paths += $moduleTests } + + # Script tests: scripts/**/Tests + $scriptTests = Get-ChildItem -Path 'scripts' -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq 'Tests' } | + ForEach-Object { $_.FullName } + if ($scriptTests) { $paths += $scriptTests } + + if (-not $paths) { + Write-Host 'No Pester tests found (modules/**/Tests or scripts/**/Tests). Skipping.' + exit 0 + } + + $config = [PesterConfiguration]::Default + $config.Run.Path = $paths + $config.Run.PassThru = $true + $config.Output.Verbosity = 'Detailed' + $config.TestResult.Enabled = $true + $config.TestResult.OutputFormat = 'JUnitXml' + $config.TestResult.OutputPath = $outFile + + $result = Invoke-Pester -Configuration $config + if ($result.FailedCount -gt 0) { throw "Pester failures: $($result.FailedCount)" } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: pester-results-windows-pwsh7 + path: Output/TestResults/*.xml + retention-days: 14 + if-no-files-found: warn diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 690337a..7cdcdfc 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit - name: 'Checkout Repository' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 'Dependency Review' - uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 188ca14..82b58d9 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -36,7 +36,7 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit @@ -76,6 +76,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: results.sarif