Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Live workflow. Legacy token-keeper snapshot: docs/ci/legacy/build-and-test.token-keeper.yml
name: Build and Test

on:
Expand All @@ -13,7 +14,7 @@ jobs:
build-and-test:
name: Build and Test
runs-on: ubuntu-latest
environment: chris-1xrn.wbx.ai
environment: webex-ci
env:
POETRY_VIRTUALENVS_CREATE: false
steps:
Expand All @@ -33,10 +34,31 @@ jobs:
poetry self add "poetry-dynamic-versioning[plugin]"
poetry install --no-root --only=dev

- name: Get Webex Token
- name: Get Webex access token
id: webex_token
env:
WEBEX_ACCESS_TOKEN_STATIC: ${{ secrets.WEBEX_ACCESS_TOKEN }}
WEBEX_CLIENT_ID: ${{ secrets.WEBEX_CLIENT_ID }}
WEBEX_CLIENT_SECRET: ${{ secrets.WEBEX_CLIENT_SECRET }}
WEBEX_REFRESH_TOKEN: ${{ secrets.WEBEX_REFRESH_TOKEN }}
run: |
WEBEX_ACCESS_TOKEN=$(curl -s ${{ secrets.WEBEX_TOKEN_KEEPER_URL }} | jq -r .access_token)
set -euo pipefail
if [ -n "${WEBEX_ACCESS_TOKEN_STATIC:-}" ]; then
echo "WEBEX_ACCESS_TOKEN=$WEBEX_ACCESS_TOKEN_STATIC" >> "$GITHUB_OUTPUT"
echo "::add-mask::$WEBEX_ACCESS_TOKEN_STATIC"
exit 0
fi
RESPONSE=$(curl -sS -X POST https://webexapis.com/v1/access_token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "client_id=${WEBEX_CLIENT_ID}" \
--data-urlencode "client_secret=${WEBEX_CLIENT_SECRET}" \
--data-urlencode "refresh_token=${WEBEX_REFRESH_TOKEN}")
WEBEX_ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)
if [ "$WEBEX_ACCESS_TOKEN" = "null" ] || [ -z "$WEBEX_ACCESS_TOKEN" ]; then
echo "::error::Token refresh failed (no access_token in response)."
exit 1
fi
echo "WEBEX_ACCESS_TOKEN=$WEBEX_ACCESS_TOKEN" >> "$GITHUB_OUTPUT"
echo "::add-mask::$WEBEX_ACCESS_TOKEN"

Expand Down
61 changes: 61 additions & 0 deletions docs/ci/MAINTAINERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# CI integration tests (Webex) — maintainer notes

This document describes how the GitHub Actions **Build and Test** workflow authenticates to Webex and which settings must be aligned so the suite can run against a shared org.

## Webex integration and org (stakeholder alignment)

Before running CI against a Webex org, maintainers should agree on:

- **Integration (Developer portal):** A single Webex integration used for **CI only** (bot or service user), with OAuth scopes sufficient for the API tests in `tests/api/`.
- **Control Hub org:** The org that hosts test users, licenses (e.g. **Advanced Messaging** where required by tests), and admin actions for people/rooms. Document who can grant licenses and manage the integration.
- **Rotation:** Multiple repository admins should have GitHub permission to manage **Environment secrets** for the `webex-ci` environment so updates are not blocked by a single person.

## GitHub Environment: `webex-ci`

Create a GitHub [Environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) named **`webex-ci`** (or rename the workflow to match if you choose a different name).

1. In the repo or organization: **Settings → Environments → New environment**.
2. Add **several** trusted maintainers as admins who can edit secrets and variables.
3. Configure **Environment secrets** and **Environment variables** as below (or move secrets from the old `chris-1xrn.wbx.ai` environment after migrating).

### Variables (GitHub Environment or repository)

| Variable | Purpose |
|----------|---------|
| `WEBEX_TEST_DOMAIN` | Email domain used for synthetic test addresses (see `tests/environment.py`). |
| `WEBEX_TEST_ID_START` | Starting index for generated test email addresses. |
| `WEBEX_TEST_FILE_URL` | URL of a downloadable file used by fixtures. |

### Secrets (GitHub Environment)

| Secret | Required | Purpose |
|--------|----------|---------|
| `WEBEX_GUEST_ISSUER_ID` | Yes | Guest issuer for tests that need it. |
| `WEBEX_GUEST_ISSUER_SECRET` | Yes | Guest issuer secret. |
| `WEBEX_CLIENT_ID` | Yes for OAuth path | Webex integration OAuth client ID. |
| `WEBEX_CLIENT_SECRET` | Yes for OAuth path | Webex integration OAuth client secret. |
| `WEBEX_REFRESH_TOKEN` | Yes for OAuth path | OAuth refresh token for the CI user/bot (obtained via authorization code flow). |
| `WEBEX_ACCESS_TOKEN` | Optional | **Alternative:** If set, the workflow uses this token directly and skips the OAuth refresh step (useful for short-term testing or when refresh setup is not ready). Prefer OAuth refresh for production CI. |

**Deprecated:** `WEBEX_TOKEN_KEEPER_URL` is no longer used by CI. The legacy keeper-based workflow is archived under [`docs/ci/legacy/`](legacy/README.md).

## Obtaining and rotating OAuth refresh tokens

1. In the [Webex Developer Portal](https://developer.webex.com/), open your CI integration and note **Client ID** and **Client Secret**.
2. Complete the OAuth **authorization code** flow for the CI identity to obtain an initial `access_token` and `refresh_token`.
3. Store `WEBEX_CLIENT_ID`, `WEBEX_CLIENT_SECRET`, and `WEBEX_REFRESH_TOKEN` in the `webex-ci` environment secrets.
4. The workflow exchanges the refresh token at **`POST https://webexapis.com/v1/access_token`** (`grant_type=refresh_token`). If the response includes a new `refresh_token`, update the secret in GitHub.

If refresh fails (invalid grant, revoked token), repeat the authorization code flow and update `WEBEX_REFRESH_TOKEN`.

## Fork and pull request limitations

- **Secrets are not available to workflows triggered from forks** by default. Contributors pushing from a fork will not run integration tests with real Webex credentials unless you use a different, carefully reviewed pattern (e.g. `pull_request_target` with strict guards — not recommended without security review).
- **Practical approach:** Run full integration tests on branches in the **main repository** (e.g. maintainer branches), or rely on scheduled runs / `push` to `main` after merge.

## Legacy reference

The previous token-keeper workflow (`WEBEX_TOKEN_KEEPER_URL`, environment `chris-1xrn.wbx.ai`) is preserved for reference only:

- [`docs/ci/legacy/build-and-test.token-keeper.yml`](legacy/build-and-test.token-keeper.yml)
- [`docs/ci/legacy/README.md`](legacy/README.md)
7 changes: 7 additions & 0 deletions docs/ci/legacy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Legacy CI workflow (reference only)

This directory preserves a snapshot of the **pre-migration** GitHub Actions workflow that obtained a Webex access token by calling an external HTTP endpoint (`WEBEX_TOKEN_KEEPER_URL`) and used the GitHub Environment `chris-1xrn.wbx.ai`.

- **Retired:** 2026-03-24 (archived for archaeology; not executed by GitHub Actions).
- **Not executed:** Files here are outside `.github/workflows/` on purpose so they do not register as workflows.
- **Replaced by:** [`.github/workflows/build-and-test.yml`](../../../.github/workflows/build-and-test.yml) using OAuth `refresh_token` grant (or optional static `WEBEX_ACCESS_TOKEN` secret) against the `webex-ci` GitHub Environment. See [`../MAINTAINERS.md`](../MAINTAINERS.md).
63 changes: 63 additions & 0 deletions docs/ci/legacy/build-and-test.token-keeper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Build and Test

on:
push:
branches:
- main
schedule:
- cron: "0 13 * * 1"
workflow_dispatch:
workflow_call:

jobs:
build-and-test:
name: Build and Test
runs-on: ubuntu-latest
environment: chris-1xrn.wbx.ai
env:
POETRY_VIRTUALENVS_CREATE: false
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.12"

- uses: Gr1N/setup-poetry@v9

- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true

- name: Install Dependencies
run: |
poetry self add "poetry-dynamic-versioning[plugin]"
poetry install --no-root --only=dev

- name: Get Webex Token
id: webex_token
run: |
WEBEX_ACCESS_TOKEN=$(curl -s ${{ secrets.WEBEX_TOKEN_KEEPER_URL }} | jq -r .access_token)
echo "WEBEX_ACCESS_TOKEN=$WEBEX_ACCESS_TOKEN" >> "$GITHUB_OUTPUT"
echo "::add-mask::$WEBEX_ACCESS_TOKEN"

- name: Build
run: poetry build

- name: Install
run: pip install dist/*.whl

- name: Test
run: pytest -s -m "not slow and not manual"
env:
WEBEX_ACCESS_TOKEN: ${{ steps.webex_token.outputs.WEBEX_ACCESS_TOKEN }}
WEBEX_TEST_DOMAIN: ${{ vars.WEBEX_TEST_DOMAIN }}
WEBEX_TEST_ID_START: ${{ vars.WEBEX_TEST_ID_START }}
WEBEX_TEST_FILE_URL: ${{ vars.WEBEX_TEST_FILE_URL }}
WEBEX_GUEST_ISSUER_ID: ${{ secrets.WEBEX_GUEST_ISSUER_ID }}
WEBEX_GUEST_ISSUER_SECRET: ${{ secrets.WEBEX_GUEST_ISSUER_SECRET }}

- name: Upload Distribution Files
uses: actions/upload-artifact@v4
with:
name: distribution-files
path: dist/