Repository: jorisroovers/gitlint (language: python)

Issue:
Refactor GHA pipelines for improved security
To consider as part of this:

- [ ] Ensure publishing secrets aren't available to CI jobs
- [x] Consider adding branch protection rules
- [ ] Add permissions: https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
- [ ] Reconsider how dev builds are triggered from CI, splitting the it out in a separate pipeline instead of calling the workflow directly from `ci.yml`
- Other?

Suggested by @webknjaz here:
https://github.com/jorisroovers/gitlint/pull/418/files#r1131056985

Adopt GHA environments
This will allow clean separation between pypi and test-pypi and reuse workflow code for publishing (instead of using if statements).

More context:
https://github.com/jorisroovers/gitlint/pull/418#discussion_r1132239712


Discussion / hints:
>     * [ ]  Consider adding branch protection rules

I was referring to the environment protections, not branch protection.

>     * [ ]  Reconsider how dev builds are triggered from CI, splitting the it out in a separate pipeline instead of calling the workflow directly from `ci.yml`

I don't see a problem with this for as long as the job has a separate environment set.
> [ ] Ensure publishing secrets aren't available to CI jobs

Hey @jorisroovers, I'd like to invite you to join the private beta of secretless publishing from GHA to PyPI. Please, fill out this form https://forms.gle/XUsRT8KTKy66TuUp7 to get in.
Note-to-self: This section in the OIDC docs has good suggestions on github action hardening:
https://github.com/pypi/warehouse/blob/ab05dd4c137eb57ff55794a659062f02b4c326bc/docs/user/trusted-publishers/security-model.md#considerations


Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2f4ac801..4f2bafa8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -134,6 +134,6 @@ jobs:
     uses: ./.github/workflows/publish-release.yml
     secrets: inherit # pass all secrets (required to access secrets in a called workflow)
     with:
-      pypi_target: "pypi.org"
+      environment: "production"
       repo_release_ref: "main"
       docker_image_tag: "latest_dev"
diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
index 4481e364..8910fe3b 100644
--- a/.github/workflows/publish-release.yml
+++ b/.github/workflows/publish-release.yml
@@ -1,15 +1,14 @@
 name: Publish Release
-run-name: "Publish Release (pypi_target=${{ inputs.pypi_target }}, repo_release_ref=${{ inputs.repo_release_ref }})"
+run-name: "Publish Release (environment=${{ inputs.environment }}, repo_release_ref=${{ inputs.repo_release_ref }})"
 
 on:
   # Trigger release workflow from other workflows (e.g. release dev build as part of CI)
   workflow_call:
     inputs:
-      pypi_target:
-        description: "PyPI repository to publish to"
-        required: true
+      environment:
+        description: "Target environment"
         type: string
-        default: "test.pypi.org"
+        required: true
       repo_release_ref:
         description: "Gitlint git reference to publish release for"
         type: string
@@ -23,14 +22,10 @@ on:
   # Manually trigger a release
   workflow_dispatch:
     inputs:
-      pypi_target:
-        description: "PyPI repository to publish to"
+      environment:
+        description: "Target environment"
+        type: environment
         required: true
-        type: choice
-        options:
-          - "pypi.org"
-          - "test.pypi.org"
-        default: "test.pypi.org"
       repo_release_ref:
         description: "Gitlint git reference to publish release for"
         type: string
@@ -49,6 +44,7 @@ jobs:
   publish:
     timeout-minutes: 15
     runs-on: "ubuntu-latest"
+    environment: ${{ inputs.environment }}
     permissions:
       # Required for trusted publishing to PyPI
       id-token: write
@@ -110,27 +106,16 @@ jobs:
         uses: pypa/gh-action-pypi-publish@release/v1
         with:
           packages-dir: gitlint-core/dist/
-        if: inputs.pypi_target == 'pypi.org'
+          repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment
 
       - name: Publish gitlint 🐍📦 to PyPI
-        uses: pypa/gh-action-pypi-publish@release/v1
-        if: inputs.pypi_target == 'pypi.org'
-
-      - name: Publish gitlint-core 🐍📦 to TestPyPI
-        uses: pypa/gh-action-pypi-publish@release/v1
-        with:
-          packages-dir: gitlint-core/dist/
-          repository-url: https://test.pypi.org/legacy/
-        if: inputs.pypi_target == 'test.pypi.org'
-
-      - name: Publish gitlint 🐍📦 to TestPyPI
         uses: pypa/gh-action-pypi-publish@release/v1
         with:
-          repository-url: https://test.pypi.org/legacy/
-        if: inputs.pypi_target == 'test.pypi.org'
+          repository-url: ${{ vars.PYPI_TARGET }} # PYPI_TARGET is defined in the GHA environment
 
   # Wait for gitlint package to be available in PyPI for installation
   wait-for-package:
+    environment: ${{ inputs.environment }}
     needs:
       - publish
     runs-on: "ubuntu-latest"
@@ -140,18 +125,10 @@ jobs:
         with:
           timeout_minutes: 1
           max_attempts: 10
+          # We need to add the --extra-index-url to the pip install command to deal with PYPI_TARGET=https://test.pypi.org/legacy,
+          # because gitlint's dependencies are not available on Test PyPI
           command: |
-            python -m pip install gitlint==${{ needs.publish.outputs.gitlint_version }}
-        if: inputs.pypi_target == 'pypi.org'
-
-      - name: Install gitlint (test.pypi.org)
-        uses: nick-fields/retry@v2.8.3
-        with:
-          timeout_minutes: 1
-          max_attempts: 10
-          command: |
-            pip install --no-cache-dir -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple gitlint==${{ needs.publish.outputs.gitlint_version }}
-        if: inputs.pypi_target == 'test.pypi.org'
+            python -m pip install --no-cache-dir -i ${{ vars.PYPI_TARGET }} --extra-index-url https://pypi.org/simple gitlint==${{ needs.publish.outputs.gitlint_version }}
 
       - name: gitlint --version
         run: |
@@ -171,7 +148,7 @@ jobs:
     uses: ./.github/workflows/test-release.yml
     with:
       gitlint_version: ${{ needs.publish.outputs.gitlint_version }}
-      pypi_source: ${{ inputs.pypi_target }}
+      environment: ${{ inputs.environment }}
       repo_test_ref: ${{ inputs.repo_release_ref }}
 
   publish-docker:


Test patch:
diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml
index 75e06f07..eb32806f 100644
--- a/.github/workflows/test-release.yml
+++ b/.github/workflows/test-release.yml
@@ -1,5 +1,5 @@
 name: Test Release
-run-name: "Test Release (${{ inputs.gitlint_version }}, pypi_source=${{ inputs.pypi_source }}, repo_test_ref=${{ inputs.repo_test_ref }})"
+run-name: "Test Release (${{ inputs.gitlint_version }}, environment=${{ inputs.environment }}, repo_test_ref=${{ inputs.repo_test_ref }})"
 on:
   workflow_call:
     inputs:
@@ -8,10 +8,10 @@ on:
         required: true
         default: "0.18.0"
         type: string
-      pypi_source:
-        description: "PyPI repository to use"
-        required: true
+      environment:
+        description: 'Gitlint package environment'
         type: string
+        required: true
       repo_test_ref:
         description: "Git reference to checkout for integration tests"
         default: "main"
@@ -22,14 +22,10 @@ on:
         description: "Gitlint version to test"
         required: true
         default: "0.18.0"
-      pypi_source:
-        description: "PyPI repository to use"
+      environment:
+        description: 'Gitlint package environment'
+        type: environment
         required: true
-        type: choice
-        options:
-          - "pypi.org"
-          - "test.pypi.org"
-        default: "pypi.org"
       repo_test_ref:
         description: "Git reference to checkout for integration tests"
         default: "main"
@@ -37,6 +33,7 @@ on:
 jobs:
   test-release:
     timeout-minutes: 10
+    environment: ${{ inputs.environment }}
     runs-on: "ubuntu-latest"
     strategy:
       matrix:
@@ -52,14 +49,10 @@ jobs:
         run: python -m pip install hatch==1.6.3
 
       - name: Install gitlint
+        # We need to add the --extra-index-url to the pip install command to deal with PYPI_TARGET=https://test.pypi.org/legacy,
+        # because gitlint's dependencies are not available on Test PyPI
         run: |
-          python -m pip install gitlint==${{ inputs.gitlint_version }}
-        if: inputs.pypi_source == 'pypi.org'
-
-      - name: Install gitlint (test.pypi.org)
-        run: |
-          pip install --no-cache-dir -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple gitlint==${{ inputs.gitlint_version }}
-        if: inputs.pypi_source == 'test.pypi.org'
+          python -m pip install --no-cache-dir -i ${{ vars.PYPI_TARGET }} --extra-index-url https://pypi.org/simple gitlint==${{ inputs.gitlint_version }}
 
       - name: gitlint --version
         run: |