diff --git a/.github/workflows/integrations-check.yml b/.github/workflows/integrations-check.yml new file mode 100644 index 00000000..73ca73d0 --- /dev/null +++ b/.github/workflows/integrations-check.yml @@ -0,0 +1,103 @@ +name: 'Integration connectivity check' + +# Maintainer-only diagnostic: verify the 3rd-party integration secrets and their +# endpoints are usable, without building or running any project/PR code. +# +# workflow_dispatch can only be started by users with write access, from the +# default branch, so it is inherently maintainer-gated and never runs for fork +# PRs. It performs authenticated HTTP checks only; token values are never +# printed. This is a diagnostic and does not replace the fork-ci environment +# approval that gates the actual Sonar publish. +on: + workflow_dispatch: + inputs: + target: + description: 'Which integration(s) to check' + required: true + default: all + type: choice + options: + - all + - sonarcloud + - codecov + +permissions: + contents: read + +concurrency: + group: integrations-check-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + TARGET: ${{ github.event.inputs.target }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + steps: + - name: Check SonarCloud token and connectivity + if: env.TARGET == 'all' || env.TARGET == 'sonarcloud' + run: | + set -euo pipefail + if [ -z "${SONAR_TOKEN:-}" ]; then + echo "::error::SONAR_TOKEN secret is empty or not configured." + exit 1 + fi + # Validate the token via SonarCloud's authentication endpoint. The token + # is sent as HTTP basic-auth username; its value is never printed. + body="$RUNNER_TEMP/sonar-validate.json" + http_code="$(curl -sS -o "$body" -w '%{http_code}' \ + -u "$SONAR_TOKEN:" https://sonarcloud.io/api/authentication/validate || true)" + echo "sonarcloud.io/api/authentication/validate -> HTTP $http_code" + if [ "$http_code" != "200" ]; then + echo "::error::SonarCloud returned HTTP $http_code (token likely invalid/expired, or the API is unreachable)." + exit 1 + fi + if ! grep -q '"valid":true' "$body"; then + echo "::error::SonarCloud reports the token as NOT valid." + cat "$body" + exit 1 + fi + echo "OK: SONAR_TOKEN is valid and SonarCloud is reachable." + + - name: Check Codecov token and connectivity + if: env.TARGET == 'all' || env.TARGET == 'codecov' + run: | + set -euo pipefail + if [ -z "${CODECOV_TOKEN:-}" ]; then + echo "::error::CODECOV_TOKEN secret is empty or not configured." + exit 1 + fi + owner="${GITHUB_REPOSITORY%/*}" + repo="${GITHUB_REPOSITORY#*/}" + body="$RUNNER_TEMP/codecov.json" + # CODECOV_TOKEN is the repo upload token: exercise the first step of + # Codecov's upload handshake (create-commit) against the real HEAD + # commit. This is the same call the uploader makes, so a valid token + # accepts it; the token is sent only in the auth header (never printed). + url="https://api.codecov.io/upload/github/$owner::::$repo/commits" + http_code="$(curl -sS -o "$body" -w '%{http_code}' -X POST \ + -H "Authorization: token $CODECOV_TOKEN" \ + -H 'Content-Type: application/json' \ + -d "{\"commitid\":\"$GITHUB_SHA\"}" "$url" || true)" + echo "POST api.codecov.io/upload/github/.../commits -> HTTP $http_code" + case "$http_code" in + 200|201) + echo "OK: CODECOV_TOKEN authenticates against Codecov." ;; + 401|403) + echo "::error::Codecov rejected the token (HTTP $http_code) - it is invalid, expired, or lacks access to this repo." + exit 1 ;; + 404) + # The upload endpoint returns 404 "Repository not found" both for an + # invalid/unauthorized upload token and for a repo not activated on + # Codecov (it does not disclose which, by design). + echo "::error::Codecov returned 404 'Repository not found' - the upload token is invalid/unauthorized, or the repo is not activated on Codecov." + cat "$body" || true + exit 1 ;; + *) + echo "::error::Unexpected response from Codecov (HTTP $http_code) - likely a reachability/service issue." + cat "$body" || true + exit 1 ;; + esac