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 }}