Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 53 additions & 25 deletions .github/actions/push-nuget-packages/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ inputs:
required: false
default: ''
description: API key used to authenticate the push (required when publish-mode is api-key)
dry-run:
required: false
default: 'false'
description: If true, skips real release downloads, NuGet trusted-publishing login, the actual push to NuGet and logs what would happen instead.
runs:
using: composite
steps:
Expand Down Expand Up @@ -68,9 +72,17 @@ runs:
shell: pwsh
run: |
$version = "${{ inputs.version }}"
gh release download $version --repo "${{ github.repository }}" --pattern "*.nupkg" --dir nugets
if ($LASTEXITCODE -ne 0) {
throw "Failed to download release assets for tag '$version'"

if ('${{ inputs.dry-run }}' -eq 'true')
{
Write-Output "[dry-run] Would download assets from published release '$version'."
}
else
{
gh release download $version --repo "${{ github.repository }}" --pattern "*.nupkg" --dir nugets
if ($LASTEXITCODE -ne 0) {
throw "Failed to download release assets for tag '$version'"
}
}
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -79,15 +91,23 @@ runs:
shell: pwsh
run: |
$name = "${{ inputs.version }}"
$releases = gh api "repos/${{ github.repository }}/releases" | ConvertFrom-Json
$release = $releases | Where-Object { $_.name -eq $name } | Select-Object -First 1
if (-not $release) {
throw "Could not find a draft release named '$name'"

if ('${{ inputs.dry-run }}' -eq 'true')
{
Write-Output "[dry-run] Would look up and download assets from draft release named '$name'."
}
Write-Output "Found draft release id $($release.id) with tag '$($release.tag_name)' for '$name'"
gh release download $release.tag_name --repo "${{ github.repository }}" --pattern "*.nupkg" --dir nugets
if ($LASTEXITCODE -ne 0) {
throw "Failed to download release assets for tag '$($release.tag_name)'"
else
{
$releases = gh api "repos/${{ github.repository }}/releases" | ConvertFrom-Json
$release = $releases | Where-Object { $_.name -eq $name } | Select-Object -First 1
if (-not $release) {
throw "Could not find a draft release named '$name'"
}
Write-Output "Found draft release id $($release.id) with tag '$($release.tag_name)' for '$name'"
gh release download $release.tag_name --repo "${{ github.repository }}" --pattern "*.nupkg" --dir nugets
if ($LASTEXITCODE -ne 0) {
throw "Failed to download release assets for tag '$($release.tag_name)'"
}
}
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -99,7 +119,7 @@ runs:
$domain = ([Uri]"${{ inputs.feed-url }}").GetLeftPart([System.UriPartial]::Authority)
Write-Output "domain=$domain" >> $env:GITHUB_OUTPUT
- name: Get NuGet trusted publishing API key
if: inputs.publish-mode == 'trusted-publishing'
if: inputs.publish-mode == 'trusted-publishing' && inputs.dry-run != 'true'
uses: NuGet/login@v1
id: login
with:
Expand All @@ -109,20 +129,28 @@ runs:
- name: Push NuGet packages
shell: pwsh
run: |
$getChildItemParams = @{ Path = 'nugets/*'; Include = '*.nupkg' }
$excludePattern = "${{ inputs.exclude-package-pattern }}"
if ($excludePattern) { $getChildItemParams.Exclude = $excludePattern }
$packages = Get-ChildItem @getChildItemParams
$source = "${{ inputs.feed-url }}"
$apiKey = if ("${{ inputs.publish-mode }}" -eq 'trusted-publishing') { "${{ steps.login.outputs.NUGET_API_KEY }}" } else { "${{ inputs.api-key }}" }
Write-Output "Pushing $($packages.Count) NuGet package(s) to $source`:"
$packages | ForEach-Object { Write-Output " - $($_.Name)" }
$failed = @()
foreach ($package in $packages)

if ('${{ inputs.dry-run }}' -eq 'true')
{
dotnet nuget push $package.FullName --source $source --api-key $apiKey --skip-duplicate
if ($LASTEXITCODE -ne 0) { $failed += $package.Name }
Write-Output "[dry-run] Would push NuGet package(s) found under 'nugets/' to $source."
}
if ($failed.Count -gt 0) {
throw "Failed to push $($failed.Count) package(s): $($failed -join ', ')"
else
{
$getChildItemParams = @{ Path = 'nugets/*'; Include = '*.nupkg' }
$excludePattern = "${{ inputs.exclude-package-pattern }}"
if ($excludePattern) { $getChildItemParams.Exclude = $excludePattern }
$packages = Get-ChildItem @getChildItemParams
$apiKey = if ("${{ inputs.publish-mode }}" -eq 'trusted-publishing') { "${{ steps.login.outputs.NUGET_API_KEY }}" } else { "${{ inputs.api-key }}" }
Write-Output "Pushing $($packages.Count) NuGet package(s) to $source`:"
$packages | ForEach-Object { Write-Output " - $($_.Name)" }
$failed = @()
foreach ($package in $packages)
{
dotnet nuget push $package.FullName --source $source --api-key $apiKey --skip-duplicate
if ($LASTEXITCODE -ne 0) { $failed += $package.Name }
}
if ($failed.Count -gt 0) {
throw "Failed to push $($failed.Count) package(s): $($failed -join ', ')"
}
}
12 changes: 3 additions & 9 deletions .github/workflows/CI-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,13 @@ on:
paths:
- '.github/actions/**'
jobs:
test-actions:
runs-on: ubuntu-latest
defaults:
run:
shell: pwsh
steps:
- name: Placeholder
run: echo "Placeholder for testing actions"
test-push-nuget-packages:
uses: ./.github/workflows/test-push-nuget-packages.yml

validate-action-tests:
# So we can add new tests without having to update branch protection rules
name: Validate action test results
needs: [test-actions]
needs: [test-push-nuget-packages]
if: always()
runs-on: ubuntu-latest
defaults:
Expand Down
120 changes: 120 additions & 0 deletions .github/workflows/test-push-nuget-packages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Test - push-nuget-packages
on:
workflow_call:
jobs:
test-push-nuget-packages:
name: ${{ matrix.scenario }}
runs-on: ubuntu-latest
defaults:
run:
shell: pwsh
strategy:
fail-fast: false
matrix:
include:
- scenario: invalid publish-mode
publish-mode: not-a-mode
release-state: published
nuget-user: ''
api-key: ''
feed-url: https://nuget.example.org
expect-failure: true
- scenario: invalid release-state
publish-mode: api-key
release-state: not-a-state
nuget-user: ''
api-key: some-key
feed-url: https://nuget.example.org
expect-failure: true
- scenario: trusted-publishing missing nuget-user
publish-mode: trusted-publishing
release-state: published
nuget-user: ''
api-key: ''
feed-url: https://nuget.example.org
expect-failure: true
- scenario: trusted-publishing with conflicting api-key
publish-mode: trusted-publishing
release-state: published
nuget-user: someuser
api-key: some-key
feed-url: https://nuget.example.org
expect-failure: true
- scenario: api-key mode missing api-key
publish-mode: api-key
release-state: published
nuget-user: ''
api-key: ''
feed-url: https://nuget.example.org
expect-failure: true
- scenario: invalid feed-url
publish-mode: api-key
release-state: published
nuget-user: ''
api-key: some-key
feed-url: not-a-url
expect-failure: true
- scenario: valid (published, trusted-publishing)
publish-mode: trusted-publishing
release-state: published
nuget-user: test-user
api-key: ''
feed-url: https://nuget.example.org
expect-failure: false
- scenario: valid (published, api-key)
publish-mode: api-key
release-state: published
nuget-user: ''
api-key: test-key
feed-url: https://nuget.example.org
expect-failure: false
- scenario: valid (draft, trusted-publishing)
publish-mode: trusted-publishing
release-state: draft
nuget-user: test-user
api-key: ''
feed-url: https://nuget.example.org
expect-failure: false
- scenario: valid (draft, api-key)
publish-mode: api-key
release-state: draft
nuget-user: ''
api-key: test-key
feed-url: https://nuget.example.org
expect-failure: false
steps:
- name: Checkout
uses: actions/checkout@v7.0.1
- name: Push NuGet packages
id: push
continue-on-error: ${{ matrix.expect-failure }}
uses: ./.github/actions/push-nuget-packages
with:
version: 1.2.3
release-state: ${{ matrix.release-state }}
publish-mode: ${{ matrix.publish-mode }}
nuget-user: ${{ matrix.nuget-user }}
api-key: ${{ matrix.api-key }}
feed-url: ${{ matrix.feed-url }}
dry-run: true
- name: Verify
run: |
$expectFailure = '${{ matrix.expect-failure }}' -eq 'true'
$outcome = '${{ steps.push.outcome }}'

if ($expectFailure)
{
if ($outcome -ne 'failure')
{
throw "Expected push-nuget-packages to fail for scenario '${{ matrix.scenario }}', but outcome was '$outcome'."
}
Write-Output "OK: failed as expected ($outcome)"
}
else
{
if ($outcome -ne 'success')
{
throw "Expected push-nuget-packages to succeed for scenario '${{ matrix.scenario }}', but outcome was '$outcome'."
}
Write-Output "OK: succeeded as expected ($outcome)"
}
Loading