From 6888bdaeb44c34e911cf1ea9dbd992bc978705ea Mon Sep 17 00:00:00 2001 From: Nick Gallegos Date: Thu, 1 Oct 2026 16:05:33 -0600 Subject: [PATCH 1/5] Addd test for authorize-user --- .github/workflows/CI-workflows.yml | 12 +++------ .github/workflows/authorize-user.yml | 30 +++++++++++++++++++++-- .github/workflows/test-authorize-user.yml | 10 ++++++++ 3 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/test-authorize-user.yml diff --git a/.github/workflows/CI-workflows.yml b/.github/workflows/CI-workflows.yml index 1aaa02d..a2aaacd 100644 --- a/.github/workflows/CI-workflows.yml +++ b/.github/workflows/CI-workflows.yml @@ -7,19 +7,13 @@ on: paths: - '.github/workflows/**' jobs: - test-workflows: - runs-on: ubuntu-latest - defaults: - run: - shell: pwsh - steps: - - name: Placeholder - run: echo "Placeholder for testing workflows" + test-authorize-user: + uses: ./.github/workflows/test-authorize-user.yml validate-workflow-tests: # So we can add new tests without having to update branch protection rules name: Validate workflow test results - needs: [test-workflows] + needs: [test-authorize-user] if: always() runs-on: ubuntu-latest defaults: diff --git a/.github/workflows/authorize-user.yml b/.github/workflows/authorize-user.yml index 432d372..d4c648d 100644 --- a/.github/workflows/authorize-user.yml +++ b/.github/workflows/authorize-user.yml @@ -1,6 +1,21 @@ name: Check Authorized User on: workflow_call: + inputs: + actor: + required: false + type: string + default: '' + description: > + Test-only override for the actor to check. Only usable within the Particular/deployment-workflows + repository - set anywhere else, this fails the workflow. + authorized-users: + required: false + type: string + default: '' + description: > + Test-only override for the comma-separated authorized users list. Only usable within the + Particular/deployment-workflows repository - set anywhere else, this fails the workflow. jobs: check-authorized-user: runs-on: ubuntu-latest @@ -10,8 +25,19 @@ jobs: steps: - name: Verify authorized user run: | - $actor = "${{ github.actor }}" - $authorizedUsers = "${{ vars.AUTHORIZED_RELEASE_USERS }}" -split ',' | ForEach-Object { $_.Trim() } + $actorOverride = "${{ inputs.actor }}" + $authorizedUsersOverride = "${{ inputs.authorized-users }}" + $repository = "${{ github.repository }}" + + if (($actorOverride -or $authorizedUsersOverride) -and $repository -ne 'Particular/deployment-workflows') + { + throw "The 'actor' and 'authorized-users' inputs are test-only overrides and may only be used within the Particular/deployment-workflows repository. This run is in '$repository'." + } + + $actor = if ($actorOverride) { $actorOverride } else { "${{ github.actor }}" } + $authorizedUsersRaw = if ($authorizedUsersOverride) { $authorizedUsersOverride } else { "${{ vars.AUTHORIZED_RELEASE_USERS }}" } + $authorizedUsers = $authorizedUsersRaw -split ',' | ForEach-Object { $_.Trim() } + if ($authorizedUsers -notcontains $actor) { Write-Error "User '$actor' is not authorized to run this workflow." exit 1 diff --git a/.github/workflows/test-authorize-user.yml b/.github/workflows/test-authorize-user.yml new file mode 100644 index 0000000..be67746 --- /dev/null +++ b/.github/workflows/test-authorize-user.yml @@ -0,0 +1,10 @@ +name: Test - authorize-user +on: + workflow_call: +jobs: + test-authorized: + name: authorized actor + uses: ./.github/workflows/authorize-user.yml + with: + actor: test-authorized-user + authorized-users: test-authorized-user, another-user From 463603a7c5e80ab97cf555fb0330f19b978c7e92 Mon Sep 17 00:00:00 2001 From: Nick Gallegos Date: Thu, 1 Oct 2026 16:21:10 -0600 Subject: [PATCH 2/5] Add test for generate-release-notes --- .github/workflows/CI-workflows.yml | 5 ++++- .github/workflows/generate-release-notes.yml | 14 +++++++++++++- .github/workflows/test-generate-release-notes.yml | 10 ++++++++++ .release/.templates/default/issue-note.sbn | 6 ++++++ .release/GitReleaseManager.yaml | 10 ++++++++++ global.json | 9 +++++++++ 6 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/test-generate-release-notes.yml create mode 100644 .release/.templates/default/issue-note.sbn create mode 100644 .release/GitReleaseManager.yaml create mode 100644 global.json diff --git a/.github/workflows/CI-workflows.yml b/.github/workflows/CI-workflows.yml index a2aaacd..0a458ba 100644 --- a/.github/workflows/CI-workflows.yml +++ b/.github/workflows/CI-workflows.yml @@ -10,10 +10,13 @@ jobs: test-authorize-user: uses: ./.github/workflows/test-authorize-user.yml + test-generate-release-notes: + uses: ./.github/workflows/test-generate-release-notes.yml + validate-workflow-tests: # So we can add new tests without having to update branch protection rules name: Validate workflow test results - needs: [test-authorize-user] + needs: [test-authorize-user, test-generate-release-notes] if: always() runs-on: ubuntu-latest defaults: diff --git a/.github/workflows/generate-release-notes.yml b/.github/workflows/generate-release-notes.yml index e6f5e6f..ebbda6b 100644 --- a/.github/workflows/generate-release-notes.yml +++ b/.github/workflows/generate-release-notes.yml @@ -5,6 +5,10 @@ on: version: required: true type: string + dry-run: + required: false + type: boolean + default: false jobs: generate-release-notes: runs-on: ubuntu-latest @@ -29,6 +33,14 @@ jobs: - name: Update draft release notes from milestone run: | Write-Output "Updating draft release body for ${{ github.event.repository.name }} ${{ inputs.version }} from milestone '${{ inputs.version }}'" - dotnet-gitreleasemanager create --token $env:GITHUB_TOKEN -o "${{ github.repository_owner }}" -r "${{ github.event.repository.name }}" -m "${{ inputs.version }}" -n "${{ inputs.version }}" -d .release + + if ('${{ inputs.dry-run }}' -eq 'true') + { + Write-Output "[dry-run] Would run: dotnet-gitreleasemanager create -o ${{ github.repository_owner }} -r ${{ github.event.repository.name }} -m ${{ inputs.version }} -n ${{ inputs.version }} -d .release" + } + else + { + dotnet-gitreleasemanager create --token $env:GITHUB_TOKEN -o "${{ github.repository_owner }}" -r "${{ github.event.repository.name }}" -m "${{ inputs.version }}" -n "${{ inputs.version }}" -d .release + } env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-generate-release-notes.yml b/.github/workflows/test-generate-release-notes.yml new file mode 100644 index 0000000..2f17fa4 --- /dev/null +++ b/.github/workflows/test-generate-release-notes.yml @@ -0,0 +1,10 @@ +name: Test - generate-release-notes +on: + workflow_call: +jobs: + test-generate-release-notes: + name: valid version + uses: ./.github/workflows/generate-release-notes.yml + with: + version: 1.2.3 + dry-run: true diff --git a/.release/.templates/default/issue-note.sbn b/.release/.templates/default/issue-note.sbn new file mode 100644 index 0000000..cc3a32e --- /dev/null +++ b/.release/.templates/default/issue-note.sbn @@ -0,0 +1,6 @@ +{{ + if issue.is_pull_request +}}- [__#{{ issue.public_number }}__]({{ issue.html_url }}) {{ issue.title }} +{{ else +}}- [__#{{ issue.public_number }}__]({{ issue.html_url }}) {{ issue.title }} +{{ end -}} diff --git a/.release/GitReleaseManager.yaml b/.release/GitReleaseManager.yaml new file mode 100644 index 0000000..f6de466 --- /dev/null +++ b/.release/GitReleaseManager.yaml @@ -0,0 +1,10 @@ +templates-dir: .templates +create: + include-footer: true +issue-labels-include: + - "Bug" + - "Feature" + - "Improvement" + - "Dependency CVE" +issue-labels-exclude: + - "Refactoring" diff --git a/global.json b/global.json new file mode 100644 index 0000000..dee200c --- /dev/null +++ b/global.json @@ -0,0 +1,9 @@ +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestFeature" + }, + "msbuild-sdks": { + "Microsoft.Build.NoTargets": "3.7.56" + } +} From 7d0731713d2c238cc319a3daa17b2024b1076146 Mon Sep 17 00:00:00 2001 From: Nick Gallegos Date: Thu, 1 Oct 2026 16:26:47 -0600 Subject: [PATCH 3/5] Add test for publish-release-notes --- .github/workflows/CI-workflows.yml | 5 ++++- .github/workflows/publish-release-notes.yml | 14 +++++++++++++- .github/workflows/test-publish-release-notes.yml | 10 ++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/test-publish-release-notes.yml diff --git a/.github/workflows/CI-workflows.yml b/.github/workflows/CI-workflows.yml index 0a458ba..9a26010 100644 --- a/.github/workflows/CI-workflows.yml +++ b/.github/workflows/CI-workflows.yml @@ -13,10 +13,13 @@ jobs: test-generate-release-notes: uses: ./.github/workflows/test-generate-release-notes.yml + test-publish-release-notes: + uses: ./.github/workflows/test-publish-release-notes.yml + validate-workflow-tests: # So we can add new tests without having to update branch protection rules name: Validate workflow test results - needs: [test-authorize-user, test-generate-release-notes] + needs: [test-authorize-user, test-generate-release-notes, test-publish-release-notes] if: always() runs-on: ubuntu-latest defaults: diff --git a/.github/workflows/publish-release-notes.yml b/.github/workflows/publish-release-notes.yml index ef0c346..03e3752 100644 --- a/.github/workflows/publish-release-notes.yml +++ b/.github/workflows/publish-release-notes.yml @@ -5,6 +5,10 @@ on: version: required: true type: string + dry-run: + required: false + type: boolean + default: false jobs: publish-release-notes: runs-on: ubuntu-latest @@ -22,6 +26,14 @@ jobs: - name: Publish release run: | Write-Output "Publishing release ${{ inputs.version }} for ${{ github.event.repository.name }}" - dotnet-gitreleasemanager publish --token $env:GITHUB_TOKEN -o "${{ github.repository_owner }}" -r "${{ github.event.repository.name }}" -t "${{ inputs.version }}" + + if ('${{ inputs.dry-run }}' -eq 'true') + { + Write-Output "[dry-run] Would run: dotnet-gitreleasemanager publish -o ${{ github.repository_owner }} -r ${{ github.event.repository.name }} -t ${{ inputs.version }}" + } + else + { + dotnet-gitreleasemanager publish --token $env:GITHUB_TOKEN -o "${{ github.repository_owner }}" -r "${{ github.event.repository.name }}" -t "${{ inputs.version }}" + } env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-publish-release-notes.yml b/.github/workflows/test-publish-release-notes.yml new file mode 100644 index 0000000..9ac322b --- /dev/null +++ b/.github/workflows/test-publish-release-notes.yml @@ -0,0 +1,10 @@ +name: Test - publish-release-notes +on: + workflow_call: +jobs: + test-publish-release-notes: + name: valid version + uses: ./.github/workflows/publish-release-notes.yml + with: + version: 1.2.3 + dry-run: true From f15a2be64ff167dd2f544791a2856f197aa6bf66 Mon Sep 17 00:00:00 2001 From: Nick Gallegos Date: Thu, 1 Oct 2026 16:29:28 -0600 Subject: [PATCH 4/5] We don't need test workflows when they're just wrappers for the real one --- .github/workflows/CI-workflows.yml | 18 +++++++++++++++--- .github/workflows/test-authorize-user.yml | 10 ---------- .../workflows/test-generate-release-notes.yml | 10 ---------- .../workflows/test-publish-release-notes.yml | 10 ---------- 4 files changed, 15 insertions(+), 33 deletions(-) delete mode 100644 .github/workflows/test-authorize-user.yml delete mode 100644 .github/workflows/test-generate-release-notes.yml delete mode 100644 .github/workflows/test-publish-release-notes.yml diff --git a/.github/workflows/CI-workflows.yml b/.github/workflows/CI-workflows.yml index 9a26010..4f802c3 100644 --- a/.github/workflows/CI-workflows.yml +++ b/.github/workflows/CI-workflows.yml @@ -8,13 +8,25 @@ on: - '.github/workflows/**' jobs: test-authorize-user: - uses: ./.github/workflows/test-authorize-user.yml + name: authorized actor + uses: ./.github/workflows/authorize-user.yml + with: + actor: test-authorized-user + authorized-users: test-authorized-user, another-user test-generate-release-notes: - uses: ./.github/workflows/test-generate-release-notes.yml + name: valid version + uses: ./.github/workflows/generate-release-notes.yml + with: + version: 1.2.3 + dry-run: true test-publish-release-notes: - uses: ./.github/workflows/test-publish-release-notes.yml + name: valid version + uses: ./.github/workflows/publish-release-notes.yml + with: + version: 1.2.3 + dry-run: true validate-workflow-tests: # So we can add new tests without having to update branch protection rules diff --git a/.github/workflows/test-authorize-user.yml b/.github/workflows/test-authorize-user.yml deleted file mode 100644 index be67746..0000000 --- a/.github/workflows/test-authorize-user.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Test - authorize-user -on: - workflow_call: -jobs: - test-authorized: - name: authorized actor - uses: ./.github/workflows/authorize-user.yml - with: - actor: test-authorized-user - authorized-users: test-authorized-user, another-user diff --git a/.github/workflows/test-generate-release-notes.yml b/.github/workflows/test-generate-release-notes.yml deleted file mode 100644 index 2f17fa4..0000000 --- a/.github/workflows/test-generate-release-notes.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Test - generate-release-notes -on: - workflow_call: -jobs: - test-generate-release-notes: - name: valid version - uses: ./.github/workflows/generate-release-notes.yml - with: - version: 1.2.3 - dry-run: true diff --git a/.github/workflows/test-publish-release-notes.yml b/.github/workflows/test-publish-release-notes.yml deleted file mode 100644 index 9ac322b..0000000 --- a/.github/workflows/test-publish-release-notes.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Test - publish-release-notes -on: - workflow_call: -jobs: - test-publish-release-notes: - name: valid version - uses: ./.github/workflows/publish-release-notes.yml - with: - version: 1.2.3 - dry-run: true From ec7ad897eb301db1cdf4a06ff2f8c84c8b836eb2 Mon Sep 17 00:00:00 2001 From: Nick Gallegos Date: Thu, 1 Oct 2026 19:15:06 -0600 Subject: [PATCH 5/5] Add tests for powershell and aws s3 workflows --- .github/workflows/CI-workflows.yml | 19 +++- .../workflows/push-powershell-packages.yml | 30 ++++- .../workflows/push-release-info-to-aws-s3.yml | 104 ++++++++++-------- 3 files changed, 100 insertions(+), 53 deletions(-) diff --git a/.github/workflows/CI-workflows.yml b/.github/workflows/CI-workflows.yml index 4f802c3..49e2d9d 100644 --- a/.github/workflows/CI-workflows.yml +++ b/.github/workflows/CI-workflows.yml @@ -28,10 +28,27 @@ jobs: version: 1.2.3 dry-run: true + test-push-powershell-packages: + name: valid version + uses: ./.github/workflows/push-powershell-packages.yml + with: + version: 1.2.3 + package-pattern: '*.nupkg' + dry-run: true + + test-push-release-info-to-aws-s3: + name: valid version + uses: ./.github/workflows/push-release-info-to-aws-s3.yml + with: + version: 1.2.3 + product: TestProduct + bucket: test-bucket + dry-run: true + validate-workflow-tests: # So we can add new tests without having to update branch protection rules name: Validate workflow test results - needs: [test-authorize-user, test-generate-release-notes, test-publish-release-notes] + needs: [test-authorize-user, test-generate-release-notes, test-publish-release-notes, test-push-powershell-packages, test-push-release-info-to-aws-s3] if: always() runs-on: ubuntu-latest defaults: diff --git a/.github/workflows/push-powershell-packages.yml b/.github/workflows/push-powershell-packages.yml index 4d48ce8..a5c0690 100644 --- a/.github/workflows/push-powershell-packages.yml +++ b/.github/workflows/push-powershell-packages.yml @@ -8,6 +8,10 @@ on: package-pattern: required: true type: string + dry-run: + required: false + type: boolean + default: false jobs: push-powershell-packages: runs-on: ubuntu-latest @@ -17,7 +21,14 @@ jobs: steps: - name: Download assets from release run: | - gh release download "${{ inputs.version }}" --repo "${{ github.repository }}" --pattern "${{ inputs.package-pattern }}" --dir powershellModules + if ('${{ inputs.dry-run }}' -eq 'true') + { + Write-Output "[dry-run] Would download assets matching '${{ inputs.package-pattern }}' from release '${{ inputs.version }}'." + } + else + { + gh release download "${{ inputs.version }}" --repo "${{ github.repository }}" --pattern "${{ inputs.package-pattern }}" --dir powershellModules + } env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -29,10 +40,17 @@ jobs: { Register-PSResourceRepository -Name ${{ vars.GALLERY_NAME }} -Uri ${{ vars.GALLERY_SOURCE_LOCATION }} -Trusted } - $psModules = Get-ChildItem powershellModules -File - Write-Output "Pushing $($psModules.Count) PowerShell module package(s) to ${{ vars.GALLERY_NAME }}:" - $psModules | ForEach-Object { Write-Output " - $($_.Name)" } - foreach ($module in $psModules) + if ('${{ inputs.dry-run }}' -eq 'true') + { + Write-Output "[dry-run] Would push PowerShell module package(s) found under 'powershellModules/' to ${{ vars.GALLERY_NAME }}." + } + else { - Publish-PSResource -NupkgPath $module.FullName -Repository ${{ vars.GALLERY_NAME }} -ApiKey ${{ secrets.GALLERY_KEY }} + $psModules = Get-ChildItem powershellModules -File + Write-Output "Pushing $($psModules.Count) PowerShell module package(s) to ${{ vars.GALLERY_NAME }}:" + $psModules | ForEach-Object { Write-Output " - $($_.Name)" } + foreach ($module in $psModules) + { + Publish-PSResource -NupkgPath $module.FullName -Repository ${{ vars.GALLERY_NAME }} -ApiKey ${{ secrets.GALLERY_KEY }} + } } diff --git a/.github/workflows/push-release-info-to-aws-s3.yml b/.github/workflows/push-release-info-to-aws-s3.yml index f37b951..afa5c50 100644 --- a/.github/workflows/push-release-info-to-aws-s3.yml +++ b/.github/workflows/push-release-info-to-aws-s3.yml @@ -11,6 +11,10 @@ on: bucket: required: true type: string + dry-run: + required: false + type: boolean + default: false jobs: push-release-info-to-aws-s3: runs-on: ubuntu-latest @@ -19,6 +23,7 @@ jobs: shell: pwsh steps: - name: Setup AWS Credentials + if: ${{ !inputs.dry-run }} uses: aws-actions/configure-aws-credentials@v5.1.1 with: aws-access-key-id: ${{ secrets.AWS_ACCESSKEY }} @@ -26,59 +31,66 @@ jobs: aws-region: us-west-2 - name: Push release info to S3 run: | - $product = "${{ inputs.product }}" - - try { - $response = Invoke-WebRequest -Uri https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ inputs.version }} -UseBasicParsing - } - catch { - Write-Error "Failed to fetch release ${{ inputs.version }}: $($_.Exception.Message)" - exit 1 + if ('${{ inputs.dry-run }}' -eq 'true') + { + Write-Output "[dry-run] Would fetch release '${{ inputs.version }}', validate it, and upload its info to s3://${{ inputs.bucket }}/$('${{ inputs.product }}'.ToLower()).txt." } + else + { + $product = "${{ inputs.product }}" - # Filter and rework the JSON - $obj = ConvertFrom-Json $response.Content -ErrorAction Stop + try { + $response = Invoke-WebRequest -Uri https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ inputs.version }} -UseBasicParsing + } + catch { + Write-Error "Failed to fetch release ${{ inputs.version }}: $($_.Exception.Message)" + exit 1 + } - if ($obj.Draft -or $obj.Prerelease) { - Write-Error "Release $($obj.tag_name) is a draft or prerelease, aborting" - exit 1 - } + # Filter and rework the JSON + $obj = ConvertFrom-Json $response.Content -ErrorAction Stop - $release = $obj | Select-Object -Property @{Name="tag";Expression={$_."tag_name"}}, @{Name="release";Expression={$_."html_url"}}, @{Name="published";Expression={$_."published_at"}}, assets - if (-not ($release.tag -match '^\d+\.\d+\.\d+$')) { - Write-Error "Release $($release.tag) has an invalid tag format, aborting" - exit 1 - } - - $release.assets = @($release.assets | Where-Object name -Like "*.exe" | Select-Object -Property name, size, @{Name="download";Expression={$_."browser_download_url"}}) + if ($obj.Draft -or $obj.Prerelease) { + Write-Error "Release $($obj.tag_name) is a draft or prerelease, aborting" + exit 1 + } - if (-not $release.assets) { - Write-Error "Release $($release.tag) has no exe assets, aborting" - exit 1 - } + $release = $obj | Select-Object -Property @{Name="tag";Expression={$_."tag_name"}}, @{Name="release";Expression={$_."html_url"}}, @{Name="published";Expression={$_."published_at"}}, assets + if (-not ($release.tag -match '^\d+\.\d+\.\d+$')) { + Write-Error "Release $($release.tag) has an invalid tag format, aborting" + exit 1 + } - $newobj = @($release) - $json = ConvertTo-Json $newobj -Depth 100 -ErrorAction Stop + $release.assets = @($release.assets | Where-Object name -Like "*.exe" | Select-Object -Property name, size, @{Name="download";Expression={$_."browser_download_url"}}) - # Write JSON to S3 - $bucket = "${{ inputs.bucket }}" - $key = "{0}.txt" -f $product.ToLower() - $tempFile = New-TemporaryFile - Set-Content -Path $tempFile -Value $json -NoNewline + if (-not $release.assets) { + Write-Error "Release $($release.tag) has no exe assets, aborting" + exit 1 + } - aws s3 cp $tempFile "s3://$bucket/$key" --content-type application/json - if ($LASTEXITCODE -ne 0) { - Write-Error "Upload to S3 failed" - exit 1 - } + $newobj = @($release) + $json = ConvertTo-Json $newobj -Depth 100 -ErrorAction Stop - # Double check upload - $expectedSize = ([System.Text.Encoding]::UTF8.GetByteCount($json)) - $uploadedSize = aws s3api head-object --bucket $bucket --key $key --query ContentLength --output text - if ([int]$uploadedSize -ne $expectedSize) { - Write-Error "Content verification failed" - exit 1 - } - else { - Write-Output ("Updated file now be available at http://{0}/{1} (case sensitive URL)" -f $bucket, $key) + # Write JSON to S3 + $bucket = "${{ inputs.bucket }}" + $key = "{0}.txt" -f $product.ToLower() + $tempFile = New-TemporaryFile + Set-Content -Path $tempFile -Value $json -NoNewline + + aws s3 cp $tempFile "s3://$bucket/$key" --content-type application/json + if ($LASTEXITCODE -ne 0) { + Write-Error "Upload to S3 failed" + exit 1 + } + + # Double check upload + $expectedSize = ([System.Text.Encoding]::UTF8.GetByteCount($json)) + $uploadedSize = aws s3api head-object --bucket $bucket --key $key --query ContentLength --output text + if ([int]$uploadedSize -ne $expectedSize) { + Write-Error "Content verification failed" + exit 1 + } + else { + Write-Output ("Updated file now be available at http://{0}/{1} (case sensitive URL)" -f $bucket, $key) + } }