From 430e8a9382bd2969d5965bf8fbb35da217d828d2 Mon Sep 17 00:00:00 2001 From: Muhannad Al-Khatib Date: Tue, 10 Nov 2026 18:23:25 +0300 Subject: [PATCH] ci: publish through nuget.org trusted publishing The org's SWNUGETKEY API key has expired, so every push to nuget.org now fails with 403. Trusted publishing trades the job's OIDC token for a one-hour key, so nothing stored can expire. nuget.org requires the token's job_workflow_ref to be in this repository, which the shared reusable workflow's isn't, so its jobs (version, dependabot gate, build, pack and push, tag) are written out here with the same shared actions, and the push logs in with NuGet/login using the NUGET_USER variable. --- .github/workflows/nuget-publish.yml | 151 ++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 28 deletions(-) diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index d2fda49..4a62a1c 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -5,34 +5,129 @@ on: branches: [main] workflow_dispatch: -# No token scopes by default. The Publish job grants the ceiling the reusable -# workflow's jobs run under; each of its jobs narrows it further. -permissions: {} +# Published with nuget.org Trusted Publishing: GitHub's OIDC token is traded for an API key that +# lasts an hour, so there is no stored key to expire (the SWNUGETKEY secret had expired). nuget.org +# takes the workflow file from the token's job_workflow_ref and requires it to be in this +# repository, so the login can't run in simplify9/.github's reusable-nuget-publish.yml. Its jobs +# are therefore written out here, using the same shared actions. The nuget.org policy: owner +# simplify9, repository SW-CloudFiles, workflow nuget-publish.yml. NUGET_USER (a repository or +# organisation variable) is the nuget.org user who created the policy. + +permissions: + contents: read + +# Serialize runs per ref so concurrent pushes can't race the tag-based version. +concurrency: + group: nuget-publish-${{ github.ref }} + cancel-in-progress: false jobs: - Publish: + version: + name: Version + runs-on: ubuntu-24.04 + timeout-minutes: 10 + outputs: + git-tag: ${{ steps.semver.outputs.git-tag }} + is-release: ${{ steps.semver.outputs.is-release }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # every tag, to compute the next version + persist-credentials: false + - name: Determine semantic version + id: semver + uses: simplify9/.github/.github/actions/determine-semver@main + with: + major: '10' + minor: '0' + # Only the default branch gets a release version; others get a prerelease. + release-branch: ${{ github.event.repository.default_branch }} + current-ref: ${{ github.ref_name }} + build-id: ${{ github.run_number }} + + critical-vuln-gate: + name: Dependabot Check + needs: version + if: ${{ needs.version.outputs.is-release == 'true' }} + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + security-events: read + steps: + - name: Check for open critical Dependabot alerts + uses: simplify9/.github/.github/actions/check-critical-vulns@main + with: + dependabot-alerts-token: ${{ secrets.DEPENDABOT_ALERTS_TOKEN }} + repository: ${{ github.repository }} + + nuget: + name: NuGet + needs: [version, critical-vuln-gate] + # The gate is skipped on non-release runs, so allow 'skipped' explicitly. + if: >- + ${{ !cancelled() + && needs.version.result == 'success' + && (needs.critical-vuln-gate.result == 'success' + || needs.critical-vuln-gate.result == 'skipped') }} + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: read + id-token: write # the OIDC token NuGet/login trades for a short-lived API key + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Restore and build + uses: simplify9/.github/.github/actions/dotnet-build@main + with: + projects: '**/*.csproj' + configuration: Release + dotnet-version: '10.0.x' + run-tests: false + test-projects: '**/*UnitTests/*.csproj' + - name: NuGet login (OIDC to a one-hour API key) + id: login + uses: NuGet/login@v1 + with: + user: ${{ vars.NUGET_USER }} + - name: Pack and push + uses: simplify9/.github/.github/actions/dotnet-pack-push@main + with: + projects: >- + SW.CloudFiles.S3/SW.CloudFiles.S3.csproj + SW.CloudFiles.S3.Extensions/SW.CloudFiles.S3.Extensions.csproj + SW.CloudFiles.AS/SW.CloudFiles.AS.csproj + SW.CloudFiles.OC/SW.CloudFiles.OC.csproj + SW.CloudFiles.AS.Extensions/SW.CloudFiles.AS.Extensions.csproj + SW.CloudFiles.OC.Extensions/SW.CloudFiles.OC.Extensions.csproj + SW.CloudFiles.GC/SW.CloudFiles.GC.csproj + SW.CloudFiles.GC.Extensions/SW.CloudFiles.GC.Extensions.csproj + SW.CloudFiles.LocalTests/SW.CloudFiles.LocalTests.csproj + SW.CloudFiles.LocalTests.Extensions/SW.CloudFiles.LocalTests.Extensions.csproj + configuration: Release + version: ${{ needs.version.outputs.git-tag }} + api-key: ${{ steps.login.outputs.NUGET_API_KEY }} + nuget-source: 'https://api.nuget.org/v3/index.json' + + tag: + name: Tag + needs: [version, nuget] + # Only after a successful push, so a tag never names an unpublished version. + if: >- + ${{ !cancelled() + && needs.version.result == 'success' + && needs.nuget.result == 'success' }} + runs-on: ubuntu-24.04 + timeout-minutes: 10 permissions: - contents: write # tag job: create the git tag - packages: write # nuget job: only used when pushing to a GitHub Packages feed - security-events: read # critical-vuln gate job - uses: simplify9/.github/.github/workflows/reusable-nuget-publish.yml@main - with: - nuget-projects: >- - SW.CloudFiles.S3/SW.CloudFiles.S3.csproj - SW.CloudFiles.S3.Extensions/SW.CloudFiles.S3.Extensions.csproj - SW.CloudFiles.AS/SW.CloudFiles.AS.csproj - SW.CloudFiles.OC/SW.CloudFiles.OC.csproj - SW.CloudFiles.AS.Extensions/SW.CloudFiles.AS.Extensions.csproj - SW.CloudFiles.OC.Extensions/SW.CloudFiles.OC.Extensions.csproj - SW.CloudFiles.GC/SW.CloudFiles.GC.csproj - SW.CloudFiles.GC.Extensions/SW.CloudFiles.GC.Extensions.csproj - SW.CloudFiles.LocalTests/SW.CloudFiles.LocalTests.csproj - SW.CloudFiles.LocalTests.Extensions/SW.CloudFiles.LocalTests.Extensions.csproj - major-version: '10' - minor-version: '0' - dotnet-version: '10.0.x' - run-tests: false - test-projects: '**/*UnitTests/*.csproj' - secrets: - nuget-api-key: ${{ secrets.SWNUGETKEY }} - dependabot-alerts-token: ${{ secrets.DEPENDABOT_ALERTS_TOKEN }} + contents: write + steps: + - name: Tag the version + uses: simplify9/.github/.github/actions/tag-github-origin@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + tag: ${{ needs.version.outputs.git-tag }} + sha: ${{ github.sha }}