diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index f99757d635..36e7b725d5 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -25,13 +25,57 @@ on: - "test/testdata/**" jobs: + # New Job: Checks permissions for PRs and outputs the result. + check-permissions: + name: Check PR author permissions + # This job only runs for pull_request_target events. + if: github.event_name == 'pull_request_target' + runs-on: ubuntu-latest + outputs: + granted: ${{ steps.permission_check.outputs.result }} + steps: + - name: Check user permissions + id: permission_check + uses: actions/github-script@v7 + with: + result-encoding: string # Capture the script's return value as an output + script: | + const actor = context.payload.pull_request.user.login; + + // Allow dependabot and other bots unconditionally. + if (actor.endsWith('[bot]')) { + core.info(`User @${actor} is a bot, allowing.`); + return 'true'; + } + + try { + const response = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: actor, + }); + + const permission = response.data.permission; + if (permission === 'admin' || permission === 'write') { + core.info(`✅ User @${actor} has '${permission}' permission. Proceeding.`); + return 'true'; + } else { + core.warning(`User @${actor} has '${permission}' permission. 'write' or 'admin' is required. Skipping E2E tests.`); + return 'false'; + } + } catch (error) { + core.warning(`Could not verify permission for @${actor}. They might not be a collaborator. Error: ${error.message}`); + return 'false'; + } + + # Modified Job: Now depends on the check-permissions job. e2e-tests: - # Run on schedule, unconditional workflow_dispatch, - # or pull_request_target if the actor has write/admin permissions. + needs: [check-permissions] # It depends on the result of the check. + # The job runs on schedule/dispatch, or on PRs if the check-permissions job granted access. if: > github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || - (github.event_name == 'pull_request_target' && contains(fromJSON('["zakisk", "infernus01", "savitaashture", "chmouel", "vdemeester", "enarha", "aThorp96", "waveywaves", "mathur07", "dependabot[bot]"]'), github.event.pull_request.user.login)) + github.event_name == 'pull_request_target' concurrency: group: ${{ github.workflow }}-${{ matrix.provider }}-${{ github.event.pull_request.number || github.ref_name }} cancel-in-progress: true @@ -68,10 +112,74 @@ jobs: TEST_BITBUCKET_SERVER_USER: pipelines TEST_BITBUCKET_SERVER_E2E_REPOSITORY: PAC/pac-e2e-tests steps: + - name: Check user permissions (detailed) + id: check_perms + uses: actions/github-script@v7 + with: + script: | + const actor = context.actor; + const { owner, repo } = context.repo; + + try { + const response = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, + repo, + username: actor, + }); + + const userPermission = response.data.permission; + + if (userPermission === 'admin') { + core.info(`✅ Permission check successful. User @${actor} is an ADMIN.`); + } else if (userPermission === 'write') { + core.info(`✅ Permission check successful. User @${actor} has WRITE permission.`); + } else { + core.setFailed(`❌ Permission check failed. User @${actor} has '${userPermission}' permission, but 'write' or 'admin' is required to proceed.`); + } + + } catch (error) { + core.setFailed(`Could not verify permission for @${actor}. They might not be a collaborator. Error: ${error.message}`); + } + - uses: actions/checkout@v5 with: ref: ${{ inputs.target_ref || github.event.pull_request.head.sha || github.sha }} + # Step to check PR author's org membership and repo permissions. + # This step will fail the job if checks do not pass, skipping subsequent steps. + - name: Check user permissions on PRs + if: github.event_name == 'pull_request_target' + uses: actions/github-script@v7 + with: + script: | + const actor = context.payload.pull_request.user.login; + const org = context.repo.owner; + + // Allow dependabot and other bots unconditionally. + if (actor.endsWith('[bot]')) { + core.info(`User @${actor} is a bot, allowing.`); + return; + } + + try { + // Directly check the user's permission level on the repository. + // This covers both org members and external collaborators with sufficient access. + const response = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: org, + repo: context.repo.repo, + username: actor, + }); + + const permission = response.data.permission; + if (permission !== 'admin' && permission !== 'write') { + core.setFailed(`❌ User @${actor} has only '${permission}' repository permission. 'write' or 'admin' is required.`); + } else { + core.info(`✅ User @${actor} has '${permission}' repository permission. Proceeding.`); + } + } catch (error) { + core.setFailed(`Permission check failed for @${actor}. They are likely not a collaborator on the repository. Error: ${error.message}`); + } + - uses: actions/setup-go@v5 with: go-version-file: "go.mod" @@ -93,8 +201,8 @@ jobs: nohup gosmee client --saveDir /tmp/gosmee-replay ${{ secrets.PYSMEE_URL }} "http://${CONTROLLER_DOMAIN_URL}" & - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + uses: mxschmitt/action-tmate@v3 with: detached: true limit-access-to-actor: true @@ -121,11 +229,8 @@ jobs: run: | ./hack/gh-workflow-ci.sh create_second_github_app_controller_on_ghe - # Adjusted step-level conditions based on the new job-level logic - name: Run E2E Tests - # This step runs for schedule, PR target (if job started), or workflow_dispatch (if job started) - # Remove the old label check which is no longer relevant for triggering. - if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request_target' }} + # The job-level `if` condition already handles this, so the step can run unconditionally env: TEST_PROVIDER: ${{ matrix.provider }} TEST_BITBUCKET_CLOUD_TOKEN: ${{ secrets.BITBUCKET_CLOUD_TOKEN }} @@ -142,7 +247,6 @@ jobs: ./hack/gh-workflow-ci.sh run_e2e_tests - name: Run E2E Tests on nightly - # This step still runs specifically for schedule or workflow_dispatch if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} env: NIGHTLY_E2E_TEST: "true" @@ -188,3 +292,4 @@ jobs: notify_when: "failure" env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + diff --git a/pkg/pipelineascode/pipelineascode.go b/pkg/pipelineascode/pipelineascode.go index 451c0403f2..6a185d0653 100644 --- a/pkg/pipelineascode/pipelineascode.go +++ b/pkg/pipelineascode/pipelineascode.go @@ -150,6 +150,14 @@ func (p *PacRun) Run(ctx context.Context) error { return nil } +// startPR creates and initializes a Tekton PipelineRun for the given match. +// It may auto-create a basic auth Secret for git access (and set its ownerRef), +// adds Pipelines-as-Code labels/annotations, optionally marks the PipelineRun +// as pending when repository concurrency is enabled, creates the PipelineRun in +// the target namespace, reports an initial status with a console URL to the SCM +// provider, and patches state/logURL metadata used by the PAC watcher. On +// partial failures after creation, it returns the created PipelineRun along +// with the error so callers can decide how to proceed. func (p *PacRun) startPR(ctx context.Context, match matcher.Match) (*tektonv1.PipelineRun, error) { var gitAuthSecretName string