diff --git a/.claude/skills/doc-tests/SKILL.md b/.claude/skills/doc-tests/SKILL.md index 5b25ed89b..9d99c3ef1 100644 --- a/.claude/skills/doc-tests/SKILL.md +++ b/.claude/skills/doc-tests/SKILL.md @@ -25,7 +25,7 @@ Use this skill when adding tests to documentation guides in the `agentgateway/we > **Critical**: Most Kubernetes topic pages (e.g. `content/docs/kubernetes/latest/resiliency/timeouts/request.md`) are thin wrappers that only contain `{{< reuse "agw-docs/pages/..." >}}`. **Always place doc-test blocks in the reuse file** (`assets/agw-docs/pages/...`), never in the content wrapper. This way both `latest` and `main` versions automatically inherit the tests — you only need to add them once. 3. **Extractor** resolves `{{< reuse "..." >}}` from `assets/`, so the script is built from the expanded content. Reference the **content file** in `test:` sources; the extractor will follow reuse. 4. **Block order**: Selected blocks are emitted in document order (by file and `start_line`). Hidden blocks (e.g. "start server in background") must appear *before* any visible block that depends on them (e.g. curl). The extractor sorts selected blocks by `(file_path, start_line)` so hidden blocks are not deferred to the end. -5. **Byte-identical blocks are silently dropped**: `build_script()` in `scripts/doc_test_extract.py` keeps a `seen` set of block contents and skips any block whose content (after stripping leading and trailing newlines) exactly matches an earlier selected block. Only the **first** copy reaches the generated script — there is no warning. See "Repeated commands across sections" under step 3 for what this breaks and how to avoid it. +5. **Byte-identical blocks are silently dropped**: `build_script()` in `docs-tests/scripts/doc_test_extract.py` keeps a `seen` set of block contents and skips any block whose content (after stripping leading and trailing newlines) exactly matches an earlier selected block. Only the **first** copy reaches the generated script — there is no warning. See "Repeated commands across sections" under step 3 for what this breaks and how to avoid it. --- @@ -335,8 +335,7 @@ Before generating, review any `yaml`/`yml` fenced blocks tagged with `paths=` to ### 9. Generate and verify -- From the repo root directory: `python3 scripts/doc_test_run.py --repo-root . --generate-only` -- From repo root: `python3 scripts/doc_test_run.py --generate-only` +- From the repo root directory: `python3 ../docs-tests/scripts/doc_test_run.py --repo-root . --generate-only` - Inspect `out/tests/generated/*.sh`: order of steps, no unresolved shortcodes, env vars and backgrounding correct. - Run a script manually, e.g. `bash out/tests/generated/.sh` (standalone tests do not use a kind cluster; use `--generate-only` and run the script in an env that has the binary/Docker/etc.). @@ -442,5 +441,5 @@ When in doubt, flag the failure to the user rather than silently adjusting the t ## Reference - Full framework: [scripts/TEST_FRAMEWORK.md](../../../scripts/TEST_FRAMEWORK.md) -- Extractor: `scripts/doc_test_extract.py` (block selection, reuse resolution, block order sort) -- Runner: `scripts/doc_test_run.py` (discovers `test:` pages, generates scripts, optional kind run) +- Extractor: `docs-tests/scripts/doc_test_extract.py` (block selection, reuse resolution, block order sort) +- Runner: `docs-tests/scripts/doc_test_run.py` (discovers `test:` pages, generates scripts, optional kind run) diff --git a/.cursor/skills/doc-tests/SKILL.md b/.cursor/skills/doc-tests/SKILL.md index 4d2573f0e..f6e678266 100644 --- a/.cursor/skills/doc-tests/SKILL.md +++ b/.cursor/skills/doc-tests/SKILL.md @@ -92,8 +92,8 @@ Write `file:` values **version-relative** so a page can be copied between `main` ### 8. Generate and verify -- From **website** directory: `python3 scripts/doc_test_run.py --repo-root . --generate-only` -- From repo root: `python3 website/scripts/doc_test_run.py --repo-root website --generate-only` +- From **website** directory: `python3 ../docs-tests/scripts/doc_test_run.py --repo-root . --generate-only` +- From repo root: `python3 docs-tests/scripts/doc_test_run.py --repo-root website --generate-only` - Inspect `out/tests/generated/*.sh`: order of steps, no unresolved shortcodes, env vars and backgrounding correct. - Run a script manually, e.g. `bash out/tests/generated/.sh` (standalone tests do not use a kind cluster; use `--generate-only` and run the script in an env that has the binary/Docker/etc.). @@ -125,5 +125,5 @@ Write `file:` values **version-relative** so a page can be copied between `main` ## Reference - Full framework: [website/scripts/TEST_FRAMEWORK.md](website/scripts/TEST_FRAMEWORK.md) -- Extractor: `website/scripts/doc_test_extract.py` (block selection, reuse resolution, block order sort) -- Runner: `website/scripts/doc_test_run.py` (discovers `test:` pages, generates scripts, optional kind run) +- Extractor: `docs-tests/scripts/doc_test_extract.py` (block selection, reuse resolution, block order sort) +- Runner: `docs-tests/scripts/doc_test_run.py` (discovers `test:` pages, generates scripts, optional kind run) diff --git a/.github/workflows/doc-tests.yaml b/.github/workflows/doc-tests.yaml index 94bbeb4dd..5b8f8aace 100644 --- a/.github/workflows/doc-tests.yaml +++ b/.github/workflows/doc-tests.yaml @@ -26,11 +26,21 @@ jobs: outputs: matrix: ${{ steps.list.outputs.matrix }} has_tests: ${{ steps.list.outputs.has_tests }} + matrix_credentialed: ${{ steps.list.outputs.matrix_credentialed }} + has_credentialed_tests: ${{ steps.list.outputs.has_credentialed_tests }} dev_version: ${{ steps.dev-version.outputs.dev_version }} steps: - name: Checkout website repo uses: actions/checkout@v6 + # Public repo, no token needed. The doc-test scripts live here now, not in this + # repo's own scripts/ dir -- see docs-tests/scripts/doc_test_run.py etc. below. + - name: Checkout docs-tests + uses: actions/checkout@v6 + with: + repository: solo-io/docs-tests + path: docs-tests + - name: Set up Python uses: actions/setup-python@v6 with: @@ -79,10 +89,15 @@ jobs: - name: List test cases id: list run: | + # 'functional' and 'live' run here and block the PR ('functional' = static + # validation against a real cluster, 'live' = unauthenticated check against a + # real external endpoint). 'schema' (config-vs-schema, no cluster) has its own + # job below. 'credentialed' is discovered separately and only run on the daily + # schedule, non-blocking. if [ -n "$CHANGED_FILES" ]; then - TESTS=$(python3 scripts/doc_test_run.py --repo-root . --list-tests --file $CHANGED_FILES) + TESTS=$(python3 docs-tests/scripts/doc_test_run.py --repo-root . --list-tests --types functional,live --file $CHANGED_FILES) else - TESTS=$(python3 scripts/doc_test_run.py --repo-root . --list-tests) + TESTS=$(python3 docs-tests/scripts/doc_test_run.py --repo-root . --list-tests --types functional,live) fi COUNT=$(echo "$TESTS" | jq 'length') if [ "$COUNT" -gt 0 ]; then @@ -92,7 +107,21 @@ jobs: fi MATRIX=$(echo "$TESTS" | jq -c '(length | [., 40] | min) as $shards | . as $tests | {include: [range($shards) | . as $i | ([$tests | to_entries[] | select(.key % $shards == $i) | .value] as $shard | {shard_index: $i, test_count: ($shard | length), tests: ($shard | tojson), shard_name: ($shard | [.[].test] | join(", ") | if length > 80 then .[:77] + "..." else . end)})]}') echo "matrix=${MATRIX}" >> $GITHUB_OUTPUT - echo "Discovered $COUNT test case(s)" + echo "Discovered $COUNT functional/live test case(s)" + + # 'credentialed' is not filtered by changed files -- it only ever runs on the + # schedule trigger (see run-test-credentialed's `if:`), never on a PR, so + # there's no "changed" set to narrow it against. + TESTS_CREDENTIALED=$(python3 docs-tests/scripts/doc_test_run.py --repo-root . --list-tests --types credentialed) + COUNT_CREDENTIALED=$(echo "$TESTS_CREDENTIALED" | jq 'length') + if [ "$COUNT_CREDENTIALED" -gt 0 ]; then + echo "has_credentialed_tests=true" >> $GITHUB_OUTPUT + else + echo "has_credentialed_tests=false" >> $GITHUB_OUTPUT + fi + MATRIX_CREDENTIALED=$(echo "$TESTS_CREDENTIALED" | jq -c '(length | [., 40] | min) as $shards | . as $tests | {include: [range($shards) | . as $i | ([$tests | to_entries[] | select(.key % $shards == $i) | .value] as $shard | {shard_index: $i, test_count: ($shard | length), tests: ($shard | tojson), shard_name: ($shard | [.[].test] | join(", ") | if length > 80 then .[:77] + "..." else . end)})]}') + echo "matrix_credentialed=${MATRIX_CREDENTIALED}" >> $GITHUB_OUTPUT + echo "Discovered $COUNT_CREDENTIALED credentialed test case(s)" env: CHANGED_FILES: ${{ steps.changed-files.outputs.files }} @@ -173,6 +202,54 @@ jobs: echo "dev_version=$RESOLVED" >> "$GITHUB_OUTPUT" echo "Resolved dev build version: $RESOLVED" + schema-check: + name: Schema validation (no cluster) + runs-on: ubuntu-latest + steps: + - name: Checkout website repo + uses: actions/checkout@v6 + + - name: Checkout docs-tests + uses: actions/checkout@v6 + with: + repository: solo-io/docs-tests + path: docs-tests + + - name: Checkout agentgateway (for CRD schemas) + uses: actions/checkout@v6 + with: + repository: agentgateway/agentgateway + path: agentgateway-product + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.x' + + - name: Install Python dependencies + run: pip install pyyaml jsonschema + + - name: Run schema checks + run: | + mkdir -p out/tests/generated + python3 docs-tests/scripts/doc_test_schema_check.py \ + --repo-root . \ + --crd-dir agentgateway-product/controller/install/helm/agentgateway-crds/templates \ + --docs-tests-root docs-tests \ + --report-file out/tests/generated/test-results.yaml \ + --verbose + + # Named test-result-schema so it's picked up by the same test-result-* pattern the + # report job already downloads and merges -- no changes needed there. Writes a + # test-results.yaml in the exact shape doc_test_run.py writes (see write_report()). + - name: Upload test results + if: always() + uses: actions/upload-artifact@v7 + with: + name: test-result-schema + path: out/tests/generated/test-results.yaml + retention-days: 1 + run-test: name: "${{ matrix.shard_name }}" needs: discover @@ -187,6 +264,12 @@ jobs: - name: Checkout website repo uses: actions/checkout@v6 + - name: Checkout docs-tests + uses: actions/checkout@v6 + with: + repository: solo-io/docs-tests + path: docs-tests + - name: Set up Python uses: actions/setup-python@v6 with: @@ -235,15 +318,16 @@ jobs: # Used by the install-agentgateway-binary snippet's `gh run download` of the # nightly release-binary-linux artifact from agentgateway/agentgateway. GH_TOKEN: ${{ github.token }} + DOCS_TESTS_ROOT: ${{ github.workspace }}/docs-tests run: | FAILED=0 TEST_INDEX=0 while IFS=$'\t' read -r file test; do - python3 scripts/doc_test_run.py --repo-root . --file "$file" --test "$test" \ + python3 docs-tests/scripts/doc_test_run.py --repo-root . --file "$file" --test "$test" \ --report-file "out/tests/generated/shard/${TEST_INDEX}/test-results.yaml" || FAILED=1 TEST_INDEX=$((TEST_INDEX + 1)) done < <(echo "$SHARD_TESTS" | jq -r '.[] | "\(.file)\t\(.test)"') - python3 scripts/merge_test_results.py out/tests/generated/shard/ out/tests/generated/test-results.yaml + python3 docs-tests/scripts/merge_test_results.py out/tests/generated/shard/ out/tests/generated/test-results.yaml exit $FAILED - name: Upload test results @@ -263,15 +347,119 @@ jobs: retention-days: 1 if-no-files-found: ignore + # 'credentialed' = checks against a real vendor (e.g. a real OpenAI key, a real Entra + # tenant). These never block a doc PR: they only run on the daily schedule, and + # continue-on-error means a credentialed failure never fails this workflow. Results + # are uploaded under a name that does NOT match report's `test-result-*` download + # pattern, so they never enter the blocking pass/fail gate. + # + # No credentialed scenario exists in this repo yet (matrix_credentialed is currently + # always empty -- every OSS traffic-management test that reaches 'live' or beyond + # needs a vendor this repo doesn't have; see the plan's Phase B/F notes on the Entra + # tenant gap), so this job is a structural no-op today: `has_credentialed_tests` stays + # 'false' and it skips. It starts doing real work the moment a page declares + # `type: credentialed` in its front matter. A dedicated summary/Slack step for this + # type is deliberately not built yet -- there's nothing to exercise it against; add + # one alongside the first real credentialed test. + run-test-credentialed: + name: "[credentialed, non-blocking] ${{ matrix.shard_name }}" + needs: discover + if: github.event_name == 'schedule' && needs.discover.outputs.has_credentialed_tests == 'true' + runs-on: ubuntu-latest + timeout-minutes: 45 + continue-on-error: true + + strategy: + matrix: ${{ fromJson(needs.discover.outputs.matrix_credentialed) }} + fail-fast: false + steps: + - name: Checkout website repo + uses: actions/checkout@v6 + + - name: Checkout docs-tests + uses: actions/checkout@v6 + with: + repository: solo-io/docs-tests + path: docs-tests + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.x' + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: 'stable' + cache: false + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 'lts/*' + + - name: Install Python dependencies + run: pip install pyyaml + + - name: Install cloud-provider-kind + run: go install sigs.k8s.io/cloud-provider-kind@latest + + - name: Install yamltest + run: npm install -g yamltest@latest + + - name: Run doc tests + env: + SHARD_TESTS: ${{ matrix.tests }} + DEBUG_MODE: true + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + PYTHONUNBUFFERED: '1' + GH_TOKEN: ${{ github.token }} + DOCS_TESTS_ROOT: ${{ github.workspace }}/docs-tests + run: | + FAILED=0 + TEST_INDEX=0 + while IFS=$'\t' read -r file test; do + python3 docs-tests/scripts/doc_test_run.py --repo-root . --file "$file" --test "$test" \ + --report-file "out/tests/generated/shard/${TEST_INDEX}/test-results.yaml" || FAILED=1 + TEST_INDEX=$((TEST_INDEX + 1)) + done < <(echo "$SHARD_TESTS" | jq -r '.[] | "\(.file)\t\(.test)"') + python3 docs-tests/scripts/merge_test_results.py out/tests/generated/shard/ out/tests/generated/test-results.yaml + exit $FAILED + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v7 + with: + name: credentialed-test-result-${{ strategy.job-index }} + path: out/tests/generated/test-results.yaml + retention-days: 1 + + - name: Upload test context (on failure) + if: failure() + uses: actions/upload-artifact@v7 + with: + name: credentialed-test-context-${{ strategy.job-index }} + path: out/tests/generated/context/ + retention-days: 1 + if-no-files-found: ignore + report: name: Aggregate results and report - needs: [discover, run-test] + needs: [discover, run-test, schema-check] if: always() && needs.discover.outputs.has_tests == 'true' runs-on: ubuntu-latest steps: - name: Checkout website repo uses: actions/checkout@v6 + # Public repo, no token needed. The doc-test scripts live here now, not in this + # repo's own scripts/ dir -- see docs-tests/scripts/merge_test_results.py etc. below. + - name: Checkout docs-tests + uses: actions/checkout@v6 + with: + repository: solo-io/docs-tests + path: docs-tests + - name: Set up Python uses: actions/setup-python@v6 with: @@ -287,7 +475,7 @@ jobs: path: collected-results/ - name: Merge test results - run: python3 scripts/merge_test_results.py collected-results/ out/tests/generated/test-results.yaml + run: python3 docs-tests/scripts/merge_test_results.py collected-results/ out/tests/generated/test-results.yaml - name: Generate job summary id: summary @@ -295,11 +483,11 @@ jobs: RESULTS_FILE=out/tests/generated/test-results.yaml # GitHub Step Summary (Markdown) - python3 scripts/report_summary.py "$RESULTS_FILE" >> "$GITHUB_STEP_SUMMARY" + python3 docs-tests/scripts/report_summary.py "$RESULTS_FILE" >> "$GITHUB_STEP_SUMMARY" # Slack Block Kit payload RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - SLACK_PAYLOAD=$(python3 scripts/report_summary.py --slack --run-url "$RUN_URL" "$RESULTS_FILE") + SLACK_PAYLOAD=$(python3 docs-tests/scripts/report_summary.py --slack --run-url "$RUN_URL" "$RESULTS_FILE") echo "slack_blocks<> "$GITHUB_OUTPUT" echo "$SLACK_PAYLOAD" | jq '.main' >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" @@ -320,7 +508,7 @@ jobs: - name: List untested docs if: always() run: | - python3 scripts/list_untested_docs.py \ + python3 docs-tests/scripts/list_untested_docs.py \ --docs-dir content/docs \ --output out/tests/generated/untested-docs.txt diff --git a/.github/workflows/playwright-screenshots-kube.yml b/.github/workflows/playwright-screenshots-kube.yml index 74d064756..573e557b7 100644 --- a/.github/workflows/playwright-screenshots-kube.yml +++ b/.github/workflows/playwright-screenshots-kube.yml @@ -26,9 +26,17 @@ jobs: - name: Checkout website repo uses: actions/checkout@v6 - # Cluster bring-up reuses the doc-tests machinery (scripts/doc_test_run.py): Python + - # pyyaml, Go for cloud-provider-kind, and yamltest. kind/kubectl/helm are preinstalled on - # the runner (same assumption as .github/workflows/doc-tests.yaml). + # Public repo, no token needed. Cluster bring-up reuses the doc-tests machinery + # (docs-tests/scripts/doc_test_run.py), which lives here now, not in this repo's own + # scripts/ dir. Python + pyyaml, Go for cloud-provider-kind, and yamltest. + # kind/kubectl/helm are preinstalled on the runner (same assumption as + # .github/workflows/doc-tests.yaml). + - name: Checkout docs-tests + uses: actions/checkout@v6 + with: + repository: solo-io/docs-tests + path: docs-tests + - name: Set up Python uses: actions/setup-python@v6 with: @@ -91,7 +99,7 @@ jobs: page="content/docs/kubernetes/$v/observability/ui.md" echo "::group::kube capture — $v" group_open=1 - python3 scripts/doc_test_run.py --repo-root . \ + python3 docs-tests/scripts/doc_test_run.py --repo-root . \ --file "$page" --test capture \ --keep-cluster --keep-cluster-file "out/tests/kept-$v.txt" diff --git a/Makefile b/Makefile index 1c8759ba7..f47808cb3 100644 --- a/Makefile +++ b/Makefile @@ -39,27 +39,32 @@ deps: # Doc tests run code blocks from markdown against a cluster. These targets support # generating scripts, running tests, fetching CI results, and injecting pass/fail # status into the markdown for the "Verified" badge. +# +# The scripts themselves live in the docs-tests repo, not here -- override +# DOCS_TESTS_DIR if you haven't cloned it as a sibling of this repo. #---------------------------------------------------------------------------------- +DOCS_TESTS_DIR ?= ../docs-tests + # Generate doc test scripts from markdown (no cluster needed) .PHONY: test-generate test-generate: deps - python3 scripts/doc_test_run.py --generate-only + python3 $(DOCS_TESTS_DIR)/scripts/doc_test_run.py --repo-root . --generate-only # Run all doc tests (requires kubeconfig / cluster access) .PHONY: test-run test-run: deps - python3 scripts/doc_test_run.py + python3 $(DOCS_TESTS_DIR)/scripts/doc_test_run.py --repo-root . # Download latest doc test results from GitHub Actions (main) .PHONY: test-artifacts-fetch test-artifacts-fetch: - bash scripts/doc_test_fetch_artifacts.sh + bash $(DOCS_TESTS_DIR)/scripts/doc_test_fetch_artifacts.sh # Write test pass/fail status into markdown front matter (for Verified badge) .PHONY: test-status test-status: deps - python3 scripts/doc_test_inject_status.py + python3 $(DOCS_TESTS_DIR)/scripts/doc_test_inject_status.py --repo-root . #---------------------------------------------------------------------------------- diff --git a/assets/agw-docs/pages/traffic-management/rewrite/host.md b/assets/agw-docs/pages/traffic-management/rewrite/host.md index 952b789cd..6d157025b 100644 --- a/assets/agw-docs/pages/traffic-management/rewrite/host.md +++ b/assets/agw-docs/pages/traffic-management/rewrite/host.md @@ -78,53 +78,6 @@ For more information, see the [{{< reuse "agw-docs/snippets/k8s-gateway-api-name } ``` -{{< doc-test paths="host-rewrite" >}} -YAMLTest -f - <<'EOF' -- name: wait for httpbin-rewrite HTTPRoute to be accepted - wait: - target: - kind: HTTPRoute - metadata: - namespace: httpbin - name: httpbin-rewrite - jsonPath: "$.status.parents[0].conditions[?(@.type=='Accepted')].status" - jsonPathExpectation: - comparator: equals - value: "True" - polling: - timeoutSeconds: 300 - intervalSeconds: 5 -EOF -{{< /doc-test >}} - -{{< doc-test paths="host-rewrite" >}} -for i in $(seq 1 60); do - curl -s --max-time 5 -o /dev/null "http://${INGRESS_GW_ADDRESS}:80/headers" -H "host: rewrite.example" && break - sleep 2 -done -{{< /doc-test >}} - -{{< doc-test paths="host-rewrite" >}} -YAMLTest -f - <<'EOF' -- name: host rewrite - rewrite.example rewrites host header to www.example.com - retries: 1 - http: - url: "http://${INGRESS_GW_ADDRESS}:80" - path: /headers - method: GET - headers: - host: "rewrite.example" - source: - type: local - expect: - statusCode: 200 - bodyJsonPath: - - path: "$.headers.Host[0]" - comparator: equals - value: "www.example.com" -EOF -{{< /doc-test >}} - ## External service host rewrites 1. Create an {{< reuse "/agw-docs/snippets/backend.md" >}} that represents your external service. The following example creates an {{< reuse "/agw-docs/snippets/backend.md" >}} for the `httpbin.org` domain. diff --git a/assets/agw-docs/pages/traffic-management/rewrite/path.md b/assets/agw-docs/pages/traffic-management/rewrite/path.md index 68a324901..560441dfc 100644 --- a/assets/agw-docs/pages/traffic-management/rewrite/path.md +++ b/assets/agw-docs/pages/traffic-management/rewrite/path.md @@ -104,53 +104,6 @@ Use the [HTTPPathModifier](https://gateway-api.sigs.k8s.io/reference/api-spec/ma ... ``` -{{< doc-test paths="path-rewrite-prefix" >}} -YAMLTest -f - <<'EOF' -- name: wait for httpbin-rewrite HTTPRoute to be accepted - wait: - target: - kind: HTTPRoute - metadata: - namespace: httpbin - name: httpbin-rewrite - jsonPath: "$.status.parents[0].conditions[?(@.type=='Accepted')].status" - jsonPathExpectation: - comparator: equals - value: "True" - polling: - timeoutSeconds: 300 - intervalSeconds: 5 -EOF -{{< /doc-test >}} - -{{< doc-test paths="path-rewrite-prefix" >}} -for i in $(seq 1 60); do - curl -s --max-time 5 -o /dev/null "http://${INGRESS_GW_ADDRESS}:80/headers" -H "host: rewrite.example" && break - sleep 2 -done -{{< /doc-test >}} - -{{< doc-test paths="path-rewrite-prefix" >}} -YAMLTest -f - <<'EOF' -- name: path rewrite prefix - /headers rewrites to /anything - retries: 1 - http: - url: "http://${INGRESS_GW_ADDRESS}:80" - path: /headers - method: GET - headers: - host: "rewrite.example" - source: - type: local - expect: - statusCode: 200 - bodyJsonPath: - - path: "$.url" - comparator: contains - value: "/anything" -EOF -{{< /doc-test >}} - ### External services 1. Create an {{< reuse "/agw-docs/snippets/backend.md" >}} that represents your external service. The following example creates an {{< reuse "/agw-docs/snippets/backend.md" >}} for the `httpbin.org` domain. @@ -353,53 +306,6 @@ Use the [HTTPPathModifier](https://gateway-api.sigs.k8s.io/reference/api-spec/ma ... ``` -{{< doc-test paths="path-rewrite-full" >}} -YAMLTest -f - <<'EOF' -- name: wait for httpbin-rewrite HTTPRoute to be accepted - wait: - target: - kind: HTTPRoute - metadata: - namespace: httpbin - name: httpbin-rewrite - jsonPath: "$.status.parents[0].conditions[?(@.type=='Accepted')].status" - jsonPathExpectation: - comparator: equals - value: "True" - polling: - timeoutSeconds: 300 - intervalSeconds: 5 -EOF -{{< /doc-test >}} - -{{< doc-test paths="path-rewrite-full" >}} -for i in $(seq 1 60); do - curl -s --max-time 5 -o /dev/null "http://${INGRESS_GW_ADDRESS}:80/headers" -H "host: rewrite.example" && break - sleep 2 -done -{{< /doc-test >}} - -{{< doc-test paths="path-rewrite-full" >}} -YAMLTest -f - <<'EOF' -- name: path rewrite full - /headers rewrites to /anything - retries: 1 - http: - url: "http://${INGRESS_GW_ADDRESS}:80" - path: /headers - method: GET - headers: - host: "rewrite.example" - source: - type: local - expect: - statusCode: 200 - bodyJsonPath: - - path: "$.url" - comparator: contains - value: "/anything" -EOF -{{< /doc-test >}} - ### External services 1. Create an {{< reuse "/agw-docs/snippets/backend.md" >}} that represents your external service. The following example creates an {{< reuse "/agw-docs/snippets/backend.md" >}} for the `httpbin.org` domain. diff --git a/assets/agw-docs/pages/traffic-management/transformations/encode.md b/assets/agw-docs/pages/traffic-management/transformations/encode.md index d3faa99c8..50d1b1769 100644 --- a/assets/agw-docs/pages/traffic-management/transformations/encode.md +++ b/assets/agw-docs/pages/traffic-management/transformations/encode.md @@ -32,26 +32,6 @@ In this example, you read a plain-text request header and add its base64-encoded EOF ``` - {{< doc-test paths="encode" >}} - YAMLTest -f - <<'EOF' - - name: verify x-user-id-encoded response header contains base64 value - http: - url: "http://${INGRESS_GW_ADDRESS}:80/response-headers" - method: GET - headers: - host: www.example.com - x-user-id: user123 - source: - type: local - expect: - statusCode: 200 - headers: - - name: x-user-id-encoded - comparator: equals - value: dXNlcjEyMw== - EOF - {{< /doc-test >}} - 2. Send a request to the httpbin app and include the `x-user-id` request header. Verify that you get back a 200 HTTP response code and that the `x-user-id-encoded` response header contains the base64-encoded value. {{< tabs >}} @@ -124,26 +104,6 @@ In this example, you take the encoded value from the encode example (`dXNlcjEyMw EOF ``` - {{< doc-test paths="decode" >}} - YAMLTest -f - <<'EOF' - - name: verify x-user-id-decoded response header contains plain-text value - http: - url: "http://${INGRESS_GW_ADDRESS}:80/response-headers" - method: GET - headers: - host: www.example.com - x-user-id-encoded: dXNlcjEyMw== - source: - type: local - expect: - statusCode: 200 - headers: - - name: x-user-id-decoded - comparator: equals - value: user123 - EOF - {{< /doc-test >}} - 2. Send a request to the httpbin app and include the base64-encoded value from the encode example in the `x-user-id-encoded` request header. Verify that you get back a 200 HTTP response code and that the `x-user-id-decoded` response header contains the original plain-text value. {{< tabs >}} diff --git a/assets/agw-docs/pages/traffic-management/transformations/rewrite.md b/assets/agw-docs/pages/traffic-management/transformations/rewrite.md index 298d9e47f..69fdcb6df 100644 --- a/assets/agw-docs/pages/traffic-management/transformations/rewrite.md +++ b/assets/agw-docs/pages/traffic-management/transformations/rewrite.md @@ -33,25 +33,6 @@ For example, a request to `/users/12345` is forwarded upstream as `/users/id`. EOF ``` - {{< doc-test paths="rewrite" >}} - YAMLTest -f - <<'EOF' - - name: verify numeric path segment is rewritten to /id - http: - url: "http://${INGRESS_GW_ADDRESS}:80/anything/users/12345" - method: GET - headers: - host: www.example.com - source: - type: local - expect: - statusCode: 200 - bodyJsonPath: - - path: "$.url" - comparator: contains - value: "/anything/users/id" - EOF - {{< /doc-test >}} - 2. Send a request to the httpbin app using a path with a numeric ID. Verify that you get back a 200 HTTP response code and that the `url` field in the response body shows the normalized path forwarded to the upstream. {{< tabs >}} diff --git a/assets/agw-docs/pages/traffic-management/transformations/status.md b/assets/agw-docs/pages/traffic-management/transformations/status.md index 6375a8a28..8d5dc77a5 100644 --- a/assets/agw-docs/pages/traffic-management/transformations/status.md +++ b/assets/agw-docs/pages/traffic-management/transformations/status.md @@ -30,31 +30,6 @@ In this example, the transformation applies after routing and targets a specific EOF ``` - {{< doc-test paths="change-response-status" >}} - YAMLTest -f - <<'EOF' - - name: verify response status is 401 when foo=bar query parameter is present - http: - url: "http://${INGRESS_GW_ADDRESS}:80/response-headers?foo=bar" - method: GET - headers: - host: www.example.com - source: - type: local - expect: - statusCode: 401 - - name: verify response status is 403 when foo=bar query parameter is absent - http: - url: "http://${INGRESS_GW_ADDRESS}:80/response-headers?foo=baz" - method: GET - headers: - host: www.example.com - source: - type: local - expect: - statusCode: 403 - EOF - {{< /doc-test >}} - 2. Send a request to the httpbin app and include the `foo=bar` query parameter. Verify that you get back a 401 HTTP response code. {{< tabs >}} diff --git a/content/docs/kubernetes/latest/agent/a2a.md b/content/docs/kubernetes/latest/agent/a2a.md index 3e28d53d7..9fa860174 100644 --- a/content/docs/kubernetes/latest/agent/a2a.md +++ b/content/docs/kubernetes/latest/agent/a2a.md @@ -4,10 +4,12 @@ weight: 40 description: Route to A2A servers and securely expose their skills through agentgateway. test: a2a: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/agent/a2a.md - path: a2a + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/agent/a2a.md + path: a2a --- {{< reuse "agw-docs/pages/agentgateway/agent/a2a.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/integrations/llm-clients/claude-code.md b/content/docs/kubernetes/latest/integrations/llm-clients/claude-code.md index 75741877d..27dce52d1 100644 --- a/content/docs/kubernetes/latest/integrations/llm-clients/claude-code.md +++ b/content/docs/kubernetes/latest/integrations/llm-clients/claude-code.md @@ -4,12 +4,14 @@ weight: 10 description: Configure Claude Code CLI to use agentgateway running in Kubernetes test: claude-code-k8s: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/integrations/llm-clients/claude-code.md - path: claude-code-k8s + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/integrations/llm-clients/claude-code.md + path: claude-code-k8s --- {{< reuse "agw-docs/pages/agentgateway/integrations/llm-clients-k8s/claude-code.md" >}} diff --git a/content/docs/kubernetes/latest/llm/content-routing.md b/content/docs/kubernetes/latest/llm/content-routing.md index b1af23e00..02caa637c 100644 --- a/content/docs/kubernetes/latest/llm/content-routing.md +++ b/content/docs/kubernetes/latest/llm/content-routing.md @@ -4,12 +4,14 @@ weight: 45 description: Route requests to different LLM backends based on request body content, such as the requested model name. test: content-routing-model: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/content-routing.md - path: content-routing + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/content-routing.md + path: content-routing --- {{< reuse "agw-docs/pages/agentgateway/llm/content-routing.md" >}} diff --git a/content/docs/kubernetes/latest/llm/load-balancing.md b/content/docs/kubernetes/latest/llm/load-balancing.md index 6fa2fa905..1540c7e6d 100644 --- a/content/docs/kubernetes/latest/llm/load-balancing.md +++ b/content/docs/kubernetes/latest/llm/load-balancing.md @@ -4,12 +4,14 @@ weight: 35 description: Distribute requests across multiple LLM providers automatically (Power of Two Choices, P2C). test: load-balancing-multi-provider: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/load-balancing.md - path: load-balancing + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/load-balancing.md + path: load-balancing --- {{< reuse "agw-docs/pages/agentgateway/llm/load-balancing.md" >}} diff --git a/content/docs/kubernetes/latest/llm/models/serve.md b/content/docs/kubernetes/latest/llm/models/serve.md index 24c33e2ec..52c95bab5 100644 --- a/content/docs/kubernetes/latest/llm/models/serve.md +++ b/content/docs/kubernetes/latest/llm/models/serve.md @@ -4,13 +4,15 @@ weight: 20 description: Expose an LLM model to clients with an AgentgatewayModel resource, including wildcard matching and provider credentials. test: serve-model: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/httpbun.md - path: setup-httpbun-llm - - path: serve-model + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/httpbun.md + path: setup-httpbun-llm + - path: serve-model --- Expose an LLM model to clients with an `{{< reuse "agw-docs/snippets/agentgatewaymodel.md" >}}` resource. diff --git a/content/docs/kubernetes/latest/llm/models/virtual.md b/content/docs/kubernetes/latest/llm/models/virtual.md index 277f84448..cf04e3f8e 100644 --- a/content/docs/kubernetes/latest/llm/models/virtual.md +++ b/content/docs/kubernetes/latest/llm/models/virtual.md @@ -4,15 +4,17 @@ weight: 30 description: Publish one client-facing model name and route requests across several models with weighted, failover, or conditional routing. test: virtual-models: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/httpbun.md - path: setup-httpbun-llm - - file: ${versionRoot}/llm/models/serve.md - path: serve-model - - path: virtual-models + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/httpbun.md + path: setup-httpbun-llm + - file: ${versionRoot}/llm/models/serve.md + path: serve-model + - path: virtual-models --- Publish one client-facing model name and route requests across several models. diff --git a/content/docs/kubernetes/latest/llm/providers/httpbun.md b/content/docs/kubernetes/latest/llm/providers/httpbun.md index 9b61950ef..d93daefa7 100644 --- a/content/docs/kubernetes/latest/llm/providers/httpbun.md +++ b/content/docs/kubernetes/latest/llm/providers/httpbun.md @@ -4,12 +4,14 @@ weight: 100 description: Set up httpbun as a mock OpenAI-compatible LLM backend for testing without API keys. test: setup-httpbun-llm: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/httpbun.md - path: setup-httpbun-llm + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/httpbun.md + path: setup-httpbun-llm --- {{< reuse "agw-docs/pages/agentgateway/llm/providers/httpbun.md" >}} diff --git a/content/docs/kubernetes/latest/llm/providers/ollama.md b/content/docs/kubernetes/latest/llm/providers/ollama.md index 9619bfb65..edd4a9e95 100644 --- a/content/docs/kubernetes/latest/llm/providers/ollama.md +++ b/content/docs/kubernetes/latest/llm/providers/ollama.md @@ -4,12 +4,14 @@ weight: 25 description: Configure agentgateway to route LLM traffic to Ollama for local model inference test: ollama-provider-setup: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/ollama.md - path: ollama-provider-setup + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/ollama.md + path: ollama-provider-setup --- > [!NOTE] diff --git a/content/docs/kubernetes/latest/llm/providers/openai.md b/content/docs/kubernetes/latest/llm/providers/openai.md index ebbf3cd7f..42851a56e 100644 --- a/content/docs/kubernetes/latest/llm/providers/openai.md +++ b/content/docs/kubernetes/latest/llm/providers/openai.md @@ -4,12 +4,14 @@ weight: 20 description: Configure OpenAI as an LLM provider for agentgateway. test: openai-setup: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup --- {{< reuse "agw-docs/pages/agentgateway/llm/providers/openai.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/llm/rate-limit.md b/content/docs/kubernetes/latest/llm/rate-limit.md index 226f03dc4..455c3b3bb 100644 --- a/content/docs/kubernetes/latest/llm/rate-limit.md +++ b/content/docs/kubernetes/latest/llm/rate-limit.md @@ -4,14 +4,16 @@ weight: 80 description: Control LLM costs with token-based rate limiting and request-based limits. test: llm-token-rate-limit: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/httpbun.md - path: setup-httpbun-llm - - file: ${versionRoot}/llm/rate-limit.md - path: llm-token-rate-limit + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/httpbun.md + path: setup-httpbun-llm + - file: ${versionRoot}/llm/rate-limit.md + path: llm-token-rate-limit --- {{< reuse "agw-docs/pages/agentgateway/llm/rate-limit.md" >}} diff --git a/content/docs/kubernetes/latest/llm/realtime.md b/content/docs/kubernetes/latest/llm/realtime.md index cc9d515d8..e6f87837c 100644 --- a/content/docs/kubernetes/latest/llm/realtime.md +++ b/content/docs/kubernetes/latest/llm/realtime.md @@ -4,14 +4,16 @@ weight: 47 description: Proxy OpenAI Realtime API WebSocket traffic and track token usage. test: realtime: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/llm/realtime.md - path: realtime + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/llm/realtime.md + path: realtime --- {{< reuse "agw-docs/pages/agentgateway/llm/realtime.md" >}} diff --git a/content/docs/kubernetes/latest/llm/transformations.md b/content/docs/kubernetes/latest/llm/transformations.md index bcc3d14a4..e8ec80154 100644 --- a/content/docs/kubernetes/latest/llm/transformations.md +++ b/content/docs/kubernetes/latest/llm/transformations.md @@ -4,23 +4,27 @@ weight: 70 description: Dynamically compute and set LLM request fields using CEL expressions. test: llm-transformations: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/llm/transformations.md - path: llm-transformations + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/llm/transformations.md + path: llm-transformations llm-model-headers: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/llm/transformations.md - path: llm-model-headers + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/llm/transformations.md + path: llm-model-headers --- {{< reuse "agw-docs/pages/agentgateway/llm/transformations.md" >}} diff --git a/content/docs/kubernetes/latest/mcp/auth/entra.md b/content/docs/kubernetes/latest/mcp/auth/entra.md index dcc7e6721..a707ce825 100644 --- a/content/docs/kubernetes/latest/mcp/auth/entra.md +++ b/content/docs/kubernetes/latest/mcp/auth/entra.md @@ -4,14 +4,16 @@ weight: 50 description: Configure Microsoft Entra ID (Azure AD) as an OAuth identity provider for MCP authentication with agentgateway. test: setup-entra: - - file: ${versionRoot}/install/helm.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/static-mcp.md - path: setup-mcp-server - - file: ${versionRoot}/mcp/auth/entra.md - path: setup-entra + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server + - file: ${versionRoot}/mcp/auth/entra.md + path: setup-entra --- {{< reuse "agw-docs/pages/agentgateway/mcp/mcp-auth-entra.md" >}} diff --git a/content/docs/kubernetes/latest/mcp/auth/setup.md b/content/docs/kubernetes/latest/mcp/auth/setup.md index 953d46ba0..f60daeae8 100644 --- a/content/docs/kubernetes/latest/mcp/auth/setup.md +++ b/content/docs/kubernetes/latest/mcp/auth/setup.md @@ -4,16 +4,18 @@ weight: 40 description: Secure MCP servers with OAuth 2.0 authentication using agentgateway and an identity provider like Keycloak. test: mcp-auth-setup: - - file: ${versionRoot}/install/helm.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/static-mcp.md - path: setup-mcp-server - - file: ${versionRoot}/mcp/auth/keycloak.md - path: setup-keycloak - - file: ${versionRoot}/mcp/auth/setup.md - path: mcp-auth-setup + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server + - file: ${versionRoot}/mcp/auth/keycloak.md + path: setup-keycloak + - file: ${versionRoot}/mcp/auth/setup.md + path: mcp-auth-setup --- {{< reuse "agw-docs/pages/agentgateway/mcp/mcp-auth-setup.md" >}} diff --git a/content/docs/kubernetes/latest/mcp/dynamic-mcp.md b/content/docs/kubernetes/latest/mcp/dynamic-mcp.md index 7b854148c..14c5ccb7f 100644 --- a/content/docs/kubernetes/latest/mcp/dynamic-mcp.md +++ b/content/docs/kubernetes/latest/mcp/dynamic-mcp.md @@ -4,12 +4,14 @@ weight: 20 description: Route traffic to MCP servers dynamically using label selectors so backends can be updated without changing the Backend resource. test: dynamic-mcp: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/dynamic-mcp.md - path: dynamic-mcp + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/dynamic-mcp.md + path: dynamic-mcp --- {{< reuse "agw-docs/pages/agentgateway/mcp/dynamic.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/mcp/guardrails/setup.md b/content/docs/kubernetes/latest/mcp/guardrails/setup.md index c4e53758e..758485971 100644 --- a/content/docs/kubernetes/latest/mcp/guardrails/setup.md +++ b/content/docs/kubernetes/latest/mcp/guardrails/setup.md @@ -4,12 +4,14 @@ weight: 20 description: Gate and mutate MCP method calls with an external ExtMCP policy server. test: mcp-guardrails: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/guardrails/setup.md - path: mcp-guardrails + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/guardrails/setup.md + path: mcp-guardrails --- Gate and mutate Model Context Protocol (MCP) method calls with an external policy server. For more information about how MCP guardrails work, see [About MCP guardrails]({{< link-hextra path="/mcp/guardrails/about" >}}). diff --git a/content/docs/kubernetes/latest/mcp/rate-limit.md b/content/docs/kubernetes/latest/mcp/rate-limit.md index 6ac3de687..9cc37d8b8 100644 --- a/content/docs/kubernetes/latest/mcp/rate-limit.md +++ b/content/docs/kubernetes/latest/mcp/rate-limit.md @@ -4,14 +4,16 @@ weight: 65 description: Control MCP tool call rates to prevent overload and ensure fair access to expensive tools. test: mcp-local-rate-limit: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/static-mcp.md - path: setup-mcp-server - - file: ${versionRoot}/mcp/rate-limit.md - path: mcp-local-rate-limit + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server + - file: ${versionRoot}/mcp/rate-limit.md + path: mcp-local-rate-limit --- {{< reuse "agw-docs/pages/agentgateway/mcp/rate-limit.md" >}} diff --git a/content/docs/kubernetes/latest/mcp/static-mcp.md b/content/docs/kubernetes/latest/mcp/static-mcp.md index d90d28b2b..e229bc2e7 100644 --- a/content/docs/kubernetes/latest/mcp/static-mcp.md +++ b/content/docs/kubernetes/latest/mcp/static-mcp.md @@ -4,12 +4,14 @@ weight: 10 description: Route traffic to an MCP server at a static address by configuring a fixed Backend resource. test: setup-mcp-server: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/static-mcp.md - path: setup-mcp-server + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server --- {{< reuse "agw-docs/pages/agentgateway/mcp/static.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/mcp/virtual.md b/content/docs/kubernetes/latest/mcp/virtual.md index 9d8b86b0c..90254dda9 100644 --- a/content/docs/kubernetes/latest/mcp/virtual.md +++ b/content/docs/kubernetes/latest/mcp/virtual.md @@ -4,12 +4,14 @@ weight: 30 description: Federate tools from multiple MCP servers on a single gateway endpoint using virtual MCP multiplexing. test: virtual-mcp: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/virtual.md - path: virtual-mcp + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/virtual.md + path: virtual-mcp --- {{< reuse "agw-docs/pages/agentgateway/mcp/multiplex.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/observability/tracing.md b/content/docs/kubernetes/latest/observability/tracing.md index b84e2ad70..3d377b05b 100644 --- a/content/docs/kubernetes/latest/observability/tracing.md +++ b/content/docs/kubernetes/latest/observability/tracing.md @@ -4,14 +4,16 @@ description: Integrate with OpenTelemetry to collect and analyze request traces. weight: 90 test: tracing: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/observability/tracing.md - path: tracing + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/observability/tracing.md + path: tracing --- Integrate your agentgateway proxy with an OpenTelemetry (OTel) collector and configure custom metadata for your traces with an {{< reuse "agw-docs/snippets/policy.md" >}}. diff --git a/content/docs/kubernetes/latest/quickstart/llm.md b/content/docs/kubernetes/latest/quickstart/llm.md index 845dcd7f4..22ac76f75 100644 --- a/content/docs/kubernetes/latest/quickstart/llm.md +++ b/content/docs/kubernetes/latest/quickstart/llm.md @@ -4,10 +4,12 @@ weight: 11 description: Route requests to OpenAI's chat completions API with agentgateway on Kubernetes. test: openai: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/quickstart/llm.md - path: openai-setup + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/quickstart/llm.md + path: openai-setup --- {{< reuse "agw-docs/pages/agentgateway/quickstart/llm.md" >}} diff --git a/content/docs/kubernetes/latest/quickstart/mcp.md b/content/docs/kubernetes/latest/quickstart/mcp.md index 56091c46c..231ba3b18 100644 --- a/content/docs/kubernetes/latest/quickstart/mcp.md +++ b/content/docs/kubernetes/latest/quickstart/mcp.md @@ -4,10 +4,12 @@ weight: 12 description: Connect to an MCP server and try tools with agentgateway on Kubernetes. test: mcp: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/quickstart/mcp.md - path: setup-mcp-server + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/quickstart/mcp.md + path: setup-mcp-server --- {{< reuse "agw-docs/pages/agentgateway/quickstart/mcp.md" >}} diff --git a/content/docs/kubernetes/latest/resiliency/backend-health.md b/content/docs/kubernetes/latest/resiliency/backend-health.md index cce65594b..0f5a21445 100644 --- a/content/docs/kubernetes/latest/resiliency/backend-health.md +++ b/content/docs/kubernetes/latest/resiliency/backend-health.md @@ -4,14 +4,16 @@ weight: 15 description: Automatically evict and restore unhealthy backend endpoints with passive health checking. test: backend-health: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/backend-health.md - path: backend-health + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/backend-health.md + path: backend-health --- {{< reuse "agw-docs/pages/resiliency/backend-health.md" >}} diff --git a/content/docs/kubernetes/latest/resiliency/connection.md b/content/docs/kubernetes/latest/resiliency/connection.md index ad31f82bf..abedf7e87 100644 --- a/content/docs/kubernetes/latest/resiliency/connection.md +++ b/content/docs/kubernetes/latest/resiliency/connection.md @@ -14,24 +14,28 @@ test: path: connection-general connection-http1: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/connection.md - path: connection-http1 + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/connection.md + path: connection-http1 connection-http2-flow: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/connection.md - path: connection-http2-flow + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/connection.md + path: connection-http2-flow --- {{< reuse "agw-docs/pages/resiliency/connection.md" >}} diff --git a/content/docs/kubernetes/latest/resiliency/fault-injection.md b/content/docs/kubernetes/latest/resiliency/fault-injection.md index 137864452..9ae501415 100644 --- a/content/docs/kubernetes/latest/resiliency/fault-injection.md +++ b/content/docs/kubernetes/latest/resiliency/fault-injection.md @@ -4,13 +4,15 @@ weight: 20 description: Inject artificial latency into requests to test how your clients and services handle slow responses. test: delay-in-trafficpolicy: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - path: delay-in-trafficpolicy + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - path: delay-in-trafficpolicy --- {{< reuse "agw-docs/pages/resiliency/fault-injection.md" >}} diff --git a/content/docs/kubernetes/latest/resiliency/keepalive.md b/content/docs/kubernetes/latest/resiliency/keepalive.md index 2a99f541d..a2d326d0a 100644 --- a/content/docs/kubernetes/latest/resiliency/keepalive.md +++ b/content/docs/kubernetes/latest/resiliency/keepalive.md @@ -4,24 +4,28 @@ weight: 10 description: Manage idle and stale connections with TCP and HTTP keepalive. test: tcp-keepalive: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/keepalive.md - path: tcp-keepalive + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/keepalive.md + path: tcp-keepalive http-keepalive: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/keepalive.md - path: http-keepalive + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/keepalive.md + path: http-keepalive --- {{< reuse "agw-docs/pages/resiliency/keepalive.md" >}} diff --git a/content/docs/kubernetes/latest/resiliency/retry/per-try-timeout.md b/content/docs/kubernetes/latest/resiliency/retry/per-try-timeout.md index 42858e582..e950fd1f8 100644 --- a/content/docs/kubernetes/latest/resiliency/retry/per-try-timeout.md +++ b/content/docs/kubernetes/latest/resiliency/retry/per-try-timeout.md @@ -13,23 +13,27 @@ test: - file: ${versionRoot}/resiliency/retry/per-try-timeout.md path: per-try-timeout-in-httproute per-try-timeout-in-agentgateway: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/retry/per-try-timeout.md - path: per-try-timeout-in-agentgateway + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/retry/per-try-timeout.md + path: per-try-timeout-in-agentgateway per-try-timeout-in-gatewaylistener: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/retry/per-try-timeout.md - path: per-try-timeout-in-gatewaylistener + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/retry/per-try-timeout.md + path: per-try-timeout-in-gatewaylistener --- {{< reuse "agw-docs/pages/resiliency/retry/per-try-timeout.md" >}} diff --git a/content/docs/kubernetes/latest/resiliency/retry/retry.md b/content/docs/kubernetes/latest/resiliency/retry/retry.md index 58b1633c9..270971c3e 100644 --- a/content/docs/kubernetes/latest/resiliency/retry/retry.md +++ b/content/docs/kubernetes/latest/resiliency/retry/retry.md @@ -13,23 +13,27 @@ test: - file: ${versionRoot}/resiliency/retry/retry.md path: retry-in-httproute retry-in-agentgateway: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/retry/retry.md - path: retry-in-agentgateway + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/retry/retry.md + path: retry-in-agentgateway retry-in-gatewaylistener: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/retry/retry.md - path: retry-in-gatewaylistener + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/retry/retry.md + path: retry-in-gatewaylistener --- {{< reuse "agw-docs/pages/resiliency/retry/retry.md" >}} diff --git a/content/docs/kubernetes/latest/resiliency/timeouts/idle.md b/content/docs/kubernetes/latest/resiliency/timeouts/idle.md index ae8ba329b..b76535ea8 100644 --- a/content/docs/kubernetes/latest/resiliency/timeouts/idle.md +++ b/content/docs/kubernetes/latest/resiliency/timeouts/idle.md @@ -4,14 +4,16 @@ weight: 20 description: Set idle timeouts to terminate inactive HTTP/1 connections. test: idle-timeout: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/timeouts/idle.md - path: idle-timeout + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/timeouts/idle.md + path: idle-timeout --- {{< reuse "agw-docs/pages/resiliency/timeouts/idle.md" >}} diff --git a/content/docs/kubernetes/latest/resiliency/timeouts/request.md b/content/docs/kubernetes/latest/resiliency/timeouts/request.md index aa931ea42..f228a073d 100644 --- a/content/docs/kubernetes/latest/resiliency/timeouts/request.md +++ b/content/docs/kubernetes/latest/resiliency/timeouts/request.md @@ -13,23 +13,27 @@ test: - file: ${versionRoot}/resiliency/timeouts/request.md path: timeout-in-httproute timeout-in-trafficpolicy: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/timeouts/request.md - path: timeout-in-trafficpolicy + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/timeouts/request.md + path: timeout-in-trafficpolicy timeout-in-gatewaylistener: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/timeouts/request.md - path: timeout-in-gatewaylistener + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/timeouts/request.md + path: timeout-in-gatewaylistener --- {{< reuse "agw-docs/pages/resiliency/timeouts/request.md" >}} diff --git a/content/docs/kubernetes/latest/security/access-logging.md b/content/docs/kubernetes/latest/security/access-logging.md index 0a197bf8d..6902ee759 100644 --- a/content/docs/kubernetes/latest/security/access-logging.md +++ b/content/docs/kubernetes/latest/security/access-logging.md @@ -4,14 +4,16 @@ weight: 10 description: Capture an access log for all the requests that enter the proxy. test: access-logging: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/access-logging.md - path: access-logging + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/access-logging.md + path: access-logging --- {{< reuse "agw-docs/pages/security/access-logging.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/security/authorization.md b/content/docs/kubernetes/latest/security/authorization.md index 086c36027..e198fa154 100644 --- a/content/docs/kubernetes/latest/security/authorization.md +++ b/content/docs/kubernetes/latest/security/authorization.md @@ -4,14 +4,16 @@ weight: 15 description: Control which requests are allowed to reach your backends using authorization policies with Allow, Require, and Deny actions. test: authorization: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/authorization.md - path: authorization + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/authorization.md + path: authorization --- {{< reuse "agw-docs/pages/security/authorization.md" >}} diff --git a/content/docs/kubernetes/latest/security/backend-authn-cross-app-access.md b/content/docs/kubernetes/latest/security/backend-authn-cross-app-access.md index a3a00b67b..0bc87b2e1 100644 --- a/content/docs/kubernetes/latest/security/backend-authn-cross-app-access.md +++ b/content/docs/kubernetes/latest/security/backend-authn-cross-app-access.md @@ -4,14 +4,16 @@ weight: 20 description: Call a downstream API as the authenticated end user with the OAuth Identity Assertion Authorization Grant. test: cross-app-access: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/backend-authn-cross-app-access.md - path: cross-app-access + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/backend-authn-cross-app-access.md + path: cross-app-access --- {{< reuse "agw-docs/pages/security/backend-authn-cross-app-access.md" >}} diff --git a/content/docs/kubernetes/latest/security/cors.md b/content/docs/kubernetes/latest/security/cors.md index d2430a80e..21c778831 100644 --- a/content/docs/kubernetes/latest/security/cors.md +++ b/content/docs/kubernetes/latest/security/cors.md @@ -14,14 +14,16 @@ test: path: cors-in-httproute cors-in-agentgatewaypolicy: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/cors.md - path: cors-in-agentgatewaypolicy + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/cors.md + path: cors-in-agentgatewaypolicy --- {{< reuse "agw-docs/pages/security/cors.md" >}} diff --git a/content/docs/kubernetes/latest/security/csrf.md b/content/docs/kubernetes/latest/security/csrf.md index 7c9b92a72..704cabfb2 100644 --- a/content/docs/kubernetes/latest/security/csrf.md +++ b/content/docs/kubernetes/latest/security/csrf.md @@ -4,14 +4,16 @@ weight: 10 description: Protect your applications from Cross-Site Request Forgery (CSRF) attacks. test: csrf: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/csrf.md - path: csrf + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/csrf.md + path: csrf --- {{< reuse "agw-docs/pages/security/csrf.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/security/jwt/setup.md b/content/docs/kubernetes/latest/security/jwt/setup.md index d66a53d06..e1bdffbce 100644 --- a/content/docs/kubernetes/latest/security/jwt/setup.md +++ b/content/docs/kubernetes/latest/security/jwt/setup.md @@ -4,16 +4,18 @@ description: Set up JWT authentication with an identity provider like Keycloak. weight: 10 test: jwt-claims: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/jwt/setup.md - path: setup-keycloak - - file: ${versionRoot}/security/jwt/setup.md - path: jwt-claims + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/jwt/setup.md + path: setup-keycloak + - file: ${versionRoot}/security/jwt/setup.md + path: jwt-claims --- {{< reuse "agw-docs/pages/security/jwt-setup.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/security/rate-limit-global.md b/content/docs/kubernetes/latest/security/rate-limit-global.md index 1af634236..e10f2af33 100644 --- a/content/docs/kubernetes/latest/security/rate-limit-global.md +++ b/content/docs/kubernetes/latest/security/rate-limit-global.md @@ -4,14 +4,16 @@ weight: 45 description: Apply distributed rate limits across multiple agentgateway replicas using an external rate limit service. test: global-rate-limit-by-ip: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/rate-limit-global.md - path: global-rate-limit-by-ip + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/rate-limit-global.md + path: global-rate-limit-by-ip --- {{< reuse "agw-docs/pages/security/rate-limit-global.md" >}} diff --git a/content/docs/kubernetes/latest/security/rate-limit-http.md b/content/docs/kubernetes/latest/security/rate-limit-http.md index e915c0db8..90aff3d96 100644 --- a/content/docs/kubernetes/latest/security/rate-limit-http.md +++ b/content/docs/kubernetes/latest/security/rate-limit-http.md @@ -4,14 +4,16 @@ weight: 40 description: Apply local and global rate limits to HTTP traffic to protect your backend services from overload. test: local-rate-limit: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/rate-limit-http.md - path: local-rate-limit + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/rate-limit-http.md + path: local-rate-limit --- {{< reuse "agw-docs/pages/security/rate-limit-http.md" >}} diff --git a/content/docs/kubernetes/latest/setup/customize/customize.md b/content/docs/kubernetes/latest/setup/customize/customize.md index 13a104290..2d4b7785f 100644 --- a/content/docs/kubernetes/latest/setup/customize/customize.md +++ b/content/docs/kubernetes/latest/setup/customize/customize.md @@ -4,12 +4,14 @@ weight: 20 description: Customize the agentgateway proxy for different deployment scenarios and requirements. test: customize: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/setup/customize/customize.md - path: customize + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/setup/customize/customize.md + path: customize --- {{< reuse "agw-docs/pages/setup/customize.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/setup/listeners/tls-settings.md b/content/docs/kubernetes/latest/setup/listeners/tls-settings.md index 0ce68dfe0..b0095b3c9 100644 --- a/content/docs/kubernetes/latest/setup/listeners/tls-settings.md +++ b/content/docs/kubernetes/latest/setup/listeners/tls-settings.md @@ -4,12 +4,14 @@ description: Configure advanced TLS settings such as cipher suites and protocol weight: 20 test: tls-settings: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/setup/listeners/tls-settings.md - path: tls-settings + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/setup/listeners/tls-settings.md + path: tls-settings --- {{< reuse "agw-docs/pages/setup/listeners/tls-settings.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/traffic-management/buffering.md b/content/docs/kubernetes/latest/traffic-management/buffering.md index 469604ccd..daa42e851 100644 --- a/content/docs/kubernetes/latest/traffic-management/buffering.md +++ b/content/docs/kubernetes/latest/traffic-management/buffering.md @@ -4,12 +4,14 @@ weight: 10 description: Buffer requests and responses for inspection or replay. test: buffering: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/traffic-management/buffering.md - path: buffering + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/traffic-management/buffering.md + path: buffering --- {{< reuse "agw-docs/pages/traffic-management/buffering.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/dfp.md b/content/docs/kubernetes/latest/traffic-management/dfp.md index 9b4378d1a..8673e5ece 100644 --- a/content/docs/kubernetes/latest/traffic-management/dfp.md +++ b/content/docs/kubernetes/latest/traffic-management/dfp.md @@ -4,14 +4,16 @@ weight: 10 description: Route traffic dynamically to upstream servers based on request characteristics. test: dfp: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/dfp.md - path: dfp + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/dfp.md + path: dfp --- {{< reuse "agw-docs/pages/traffic-management/dfp.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/latest/traffic-management/direct-response.md b/content/docs/kubernetes/latest/traffic-management/direct-response.md index f9e608057..f99571557 100644 --- a/content/docs/kubernetes/latest/traffic-management/direct-response.md +++ b/content/docs/kubernetes/latest/traffic-management/direct-response.md @@ -4,14 +4,16 @@ weight: 10 description: Return responses directly without forwarding to upstream services. test: direct-response: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/direct-response.md - path: direct-response + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/direct-response.md + path: direct-response --- {{< reuse "agw-docs/pages/traffic-management/direct-response.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/extproc.md b/content/docs/kubernetes/latest/traffic-management/extproc.md index 1312dd29f..23f3f5f22 100644 --- a/content/docs/kubernetes/latest/traffic-management/extproc.md +++ b/content/docs/kubernetes/latest/traffic-management/extproc.md @@ -4,14 +4,16 @@ weight: 10 description: Modify requests and responses with an external gRPC processing server. test: extproc: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/extproc.md - path: extproc + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/extproc.md + path: extproc --- Modify aspects of an HTTP request or response with an external processing server. diff --git a/content/docs/kubernetes/latest/traffic-management/header-control/early-request-header-modifier.md b/content/docs/kubernetes/latest/traffic-management/header-control/early-request-header-modifier.md index 86d02c79b..9e0ffd5ed 100644 --- a/content/docs/kubernetes/latest/traffic-management/header-control/early-request-header-modifier.md +++ b/content/docs/kubernetes/latest/traffic-management/header-control/early-request-header-modifier.md @@ -4,14 +4,16 @@ weight: 30 description: Modify request headers in the early phase of request processing. test: remove-reserved-header: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/header-control/early-request-header-modifier.md - path: remove-reserved-header + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/header-control/early-request-header-modifier.md + path: remove-reserved-header --- {{< reuse "agw-docs/pages/traffic-management/header-control/early-request-header-modifier.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/rewrite/host.md b/content/docs/kubernetes/latest/traffic-management/rewrite/host.md index 010b23735..b37a17632 100644 --- a/content/docs/kubernetes/latest/traffic-management/rewrite/host.md +++ b/content/docs/kubernetes/latest/traffic-management/rewrite/host.md @@ -4,14 +4,20 @@ weight: 461 description: Replace the host header value before forwarding a request to a backend service. test: host-rewrite: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/rewrite/host.md - path: host-rewrite + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/rewrite/host.md + path: host-rewrite + assert: + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-wait.sh + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-warmup.sh + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-assert.sh --- {{< reuse "agw-docs/pages/traffic-management/rewrite/host.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/rewrite/path.md b/content/docs/kubernetes/latest/traffic-management/rewrite/path.md index 019c849be..60edf35eb 100644 --- a/content/docs/kubernetes/latest/traffic-management/rewrite/path.md +++ b/content/docs/kubernetes/latest/traffic-management/rewrite/path.md @@ -4,23 +4,35 @@ weight: 462 description: Rewrite path prefixes in requests. test: path-rewrite-prefix: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/rewrite/path.md - path: path-rewrite-prefix + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/rewrite/path.md + path: path-rewrite-prefix + assert: + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-prefix-wait.sh + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-prefix-warmup.sh + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-prefix-assert.sh path-rewrite-full: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/rewrite/path.md - path: path-rewrite-full + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/rewrite/path.md + path: path-rewrite-full + assert: + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-full-wait.sh + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-full-warmup.sh + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-full-assert.sh --- {{< reuse "agw-docs/pages/traffic-management/rewrite/path.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/route-delegation/inheritance/trafficpolicies.md b/content/docs/kubernetes/latest/traffic-management/route-delegation/inheritance/trafficpolicies.md index 8d0781eab..95902d5b8 100644 --- a/content/docs/kubernetes/latest/traffic-management/route-delegation/inheritance/trafficpolicies.md +++ b/content/docs/kubernetes/latest/traffic-management/route-delegation/inheritance/trafficpolicies.md @@ -4,14 +4,16 @@ weight: 20 description: Learn how policies in `AgentgatewayPolicy` resources are inherited and overridden along the route delegation chain. test: trafficpolicies: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/traffic-management/route-delegation/inheritance/trafficpolicies.md - path: route-delegation-prereq - - file: ${versionRoot}/traffic-management/route-delegation/inheritance/trafficpolicies.md - path: trafficpolicies + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/traffic-management/route-delegation/inheritance/trafficpolicies.md + path: route-delegation-prereq + - file: ${versionRoot}/traffic-management/route-delegation/inheritance/trafficpolicies.md + path: trafficpolicies --- {{< reuse "agw-docs/pages/traffic-management/route-delegation/inheritance/trafficpolicies.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/traffic-split.md b/content/docs/kubernetes/latest/traffic-management/traffic-split.md index 14c9f7c52..04bb64801 100644 --- a/content/docs/kubernetes/latest/traffic-management/traffic-split.md +++ b/content/docs/kubernetes/latest/traffic-management/traffic-split.md @@ -4,14 +4,16 @@ weight: 60 description: Set up A/B testing, traffic splitting, and canary deployments using weighted routing. test: traffic-split-llm-models: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/traffic-management/traffic-split.md - path: traffic-split-llm + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/traffic-management/traffic-split.md + path: traffic-split-llm --- {{< reuse "agw-docs/pages/traffic-management/traffic-split.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/access-logs.md b/content/docs/kubernetes/latest/traffic-management/transformations/access-logs.md index 355c4202c..af1536b45 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/access-logs.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/access-logs.md @@ -4,23 +4,27 @@ weight: 120 description: Log CEL context variables to access logs to inspect and debug transformation expressions at runtime. test: access-logs: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/access-logs.md - path: access-logs + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/access-logs.md + path: access-logs access-logs-filter: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/access-logs.md - path: access-logs-filter + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/access-logs.md + path: access-logs-filter --- {{< reuse "agw-docs/pages/traffic-management/transformations/access-logs.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/encode.md b/content/docs/kubernetes/latest/traffic-management/transformations/encode.md index a60518f3c..e224b9dc3 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/encode.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/encode.md @@ -4,22 +4,40 @@ weight: 20 description: Automatically encode and decode base64 values in request headers. test: encode: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/encode.md - path: encode + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/encode.md + path: encode + assert: + - products/agentgateway/main/traffic-management/transformations/encode.sh + encode-schema: + type: schema + steps: + - file: ${versionRoot}/traffic-management/transformations/encode.md + path: encode decode: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/encode.md - path: decode + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/encode.md + path: decode + assert: + - products/agentgateway/main/traffic-management/transformations/decode.sh + decode-schema: + type: schema + steps: + - file: ${versionRoot}/traffic-management/transformations/encode.md + path: decode --- {{< reuse "agw-docs/pages/traffic-management/transformations/encode.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/filter-request-body.md b/content/docs/kubernetes/latest/traffic-management/transformations/filter-request-body.md index ec5ac6866..33cc24476 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/filter-request-body.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/filter-request-body.md @@ -4,14 +4,16 @@ weight: 60 description: Use filterKeys() and merge() CEL functions to strip unwanted fields from a JSON request body and inject defaults before forwarding to the upstream. test: filter-request-body: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/filter-request-body.md - path: filter-request-body + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/filter-request-body.md + path: filter-request-body --- {{< reuse "agw-docs/pages/traffic-management/transformations/filter-request-body.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/forward.md b/content/docs/kubernetes/latest/traffic-management/transformations/forward.md index a7dd77c7d..7976f0b83 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/forward.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/forward.md @@ -4,14 +4,16 @@ weight: 40 description: Use CEL expressions to construct a full request URL from context variables and forward it upstream as a request header. test: forward: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/forward.md - path: forward + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/forward.md + path: forward --- {{< reuse "agw-docs/pages/traffic-management/transformations/forward.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/inject-response-body.md b/content/docs/kubernetes/latest/traffic-management/transformations/inject-response-body.md index f5d8002e9..eef0a7beb 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/inject-response-body.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/inject-response-body.md @@ -4,23 +4,27 @@ weight: 55 description: Learn how to return a customized response body and how to replace specific values in the body. test: inject-header-into-body: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/inject-response-body.md - path: inject-header-into-body + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/inject-response-body.md + path: inject-header-into-body inject-body-field-into-body: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/inject-response-body.md - path: inject-body-field-into-body + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/inject-response-body.md + path: inject-body-field-into-body --- {{< reuse "agw-docs/pages/traffic-management/transformations/inject-response-body.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/inject-response-headers.md b/content/docs/kubernetes/latest/traffic-management/transformations/inject-response-headers.md index a69d3f775..7cf0702ef 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/inject-response-headers.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/inject-response-headers.md @@ -4,14 +4,16 @@ weight: 5 description: Extract values from a request header and inject it as a header to your response. test: inject-response-headers: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/inject-response-headers.md - path: inject-response-headers + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/inject-response-headers.md + path: inject-response-headers --- {{< reuse "agw-docs/pages/traffic-management/transformations/inject-response-headers.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/llm-model-headers.md b/content/docs/kubernetes/latest/traffic-management/transformations/llm-model-headers.md index c6eb9f8cb..60f5a25eb 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/llm-model-headers.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/llm-model-headers.md @@ -4,24 +4,28 @@ weight: 47 description: Detect model fallback by injecting the requested and actual LLM model names as response headers using llm.requestModel and llm.responseModel CEL variables. test: llm-transformations: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/traffic-management/transformations/llm-model-headers.md - path: llm-transformations + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/traffic-management/transformations/llm-model-headers.md + path: llm-transformations llm-model-headers: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/traffic-management/transformations/llm-model-headers.md - path: llm-model-headers + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/traffic-management/transformations/llm-model-headers.md + path: llm-model-headers --- {{< reuse "agw-docs/pages/agentgateway/llm/transformations.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/path-method.md b/content/docs/kubernetes/latest/traffic-management/transformations/path-method.md index ef2b7afd6..f8f29f74d 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/path-method.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/path-method.md @@ -4,14 +4,16 @@ weight: 45 description: Use pseudo headers to conditionally rewrite the request path and HTTP method based on a request header value. test: path-method: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/path-method.md - path: path-method + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/path-method.md + path: path-method --- {{< reuse "agw-docs/pages/traffic-management/transformations/path-method.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/query.md b/content/docs/kubernetes/latest/traffic-management/transformations/query.md index 29e47d76a..504a8a908 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/query.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/query.md @@ -4,14 +4,16 @@ weight: 46 description: Read a query parameter from the request URI and inject it as a request header using a CEL conditional expression. test: query: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/query.md - path: query + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/query.md + path: query --- {{< reuse "agw-docs/pages/traffic-management/transformations/query.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/remove-header.md b/content/docs/kubernetes/latest/traffic-management/transformations/remove-header.md index cbfabc497..27bfe902e 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/remove-header.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/remove-header.md @@ -4,14 +4,16 @@ weight: 50 description: Remove sensitive or internal headers from requests before they reach the upstream. test: remove-header: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/remove-header.md - path: remove-header + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/remove-header.md + path: remove-header --- {{< reuse "agw-docs/pages/traffic-management/transformations/remove-header.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/rewrite.md b/content/docs/kubernetes/latest/traffic-management/transformations/rewrite.md index 5832f15ca..04cf8f768 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/rewrite.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/rewrite.md @@ -4,14 +4,23 @@ weight: 30 description: Use CEL functions to rewrite request paths in a header. test: rewrite: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/rewrite.md - path: rewrite + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/rewrite.md + path: rewrite + assert: + - products/agentgateway/main/traffic-management/transformations/rewrite.sh + rewrite-schema: + type: schema + steps: + - file: ${versionRoot}/traffic-management/transformations/rewrite.md + path: rewrite --- {{< reuse "agw-docs/pages/traffic-management/transformations/rewrite.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/status.md b/content/docs/kubernetes/latest/traffic-management/transformations/status.md index 6f47d560e..e743dba75 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/status.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/status.md @@ -4,14 +4,23 @@ weight: 60 description: Update the response status based on the headers in a response. test: change-response-status: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/status.md - path: change-response-status + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/status.md + path: change-response-status + assert: + - products/agentgateway/main/traffic-management/transformations/status.sh + change-response-status-schema: + type: schema + steps: + - file: ${versionRoot}/traffic-management/transformations/status.md + path: change-response-status --- {{< reuse "agw-docs/pages/traffic-management/transformations/status.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/tracing.md b/content/docs/kubernetes/latest/traffic-management/transformations/tracing.md index 044e1405d..5e4c62afe 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/tracing.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/tracing.md @@ -4,14 +4,16 @@ weight: 10 description: Use uuid() and random() CEL functions to inject a unique request ID and a random sampling value into request headers. test: tracing: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/tracing.md - path: tracing + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/tracing.md + path: tracing --- {{< reuse "agw-docs/pages/traffic-management/transformations/tracing.md" >}} diff --git a/content/docs/kubernetes/latest/traffic-management/transformations/validate.md b/content/docs/kubernetes/latest/traffic-management/transformations/validate.md index 313066d79..48c11ea6b 100644 --- a/content/docs/kubernetes/latest/traffic-management/transformations/validate.md +++ b/content/docs/kubernetes/latest/traffic-management/transformations/validate.md @@ -4,23 +4,27 @@ weight: 70 description: Use default() and fail() CEL functions to enforce required fields and apply default values on a JSON request body. test: validate-defaults: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/validate.md - path: validate-defaults + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/validate.md + path: validate-defaults validate-skip: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/validate.md - path: validate-skip + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/validate.md + path: validate-skip --- {{< reuse "agw-docs/pages/traffic-management/transformations/validate.md" >}} diff --git a/content/docs/kubernetes/main/agent/a2a.md b/content/docs/kubernetes/main/agent/a2a.md index 3e28d53d7..9fa860174 100644 --- a/content/docs/kubernetes/main/agent/a2a.md +++ b/content/docs/kubernetes/main/agent/a2a.md @@ -4,10 +4,12 @@ weight: 40 description: Route to A2A servers and securely expose their skills through agentgateway. test: a2a: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/agent/a2a.md - path: a2a + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/agent/a2a.md + path: a2a --- {{< reuse "agw-docs/pages/agentgateway/agent/a2a.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/integrations/llm-clients/claude-code.md b/content/docs/kubernetes/main/integrations/llm-clients/claude-code.md index 75741877d..27dce52d1 100644 --- a/content/docs/kubernetes/main/integrations/llm-clients/claude-code.md +++ b/content/docs/kubernetes/main/integrations/llm-clients/claude-code.md @@ -4,12 +4,14 @@ weight: 10 description: Configure Claude Code CLI to use agentgateway running in Kubernetes test: claude-code-k8s: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/integrations/llm-clients/claude-code.md - path: claude-code-k8s + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/integrations/llm-clients/claude-code.md + path: claude-code-k8s --- {{< reuse "agw-docs/pages/agentgateway/integrations/llm-clients-k8s/claude-code.md" >}} diff --git a/content/docs/kubernetes/main/llm/content-routing.md b/content/docs/kubernetes/main/llm/content-routing.md index b1af23e00..02caa637c 100644 --- a/content/docs/kubernetes/main/llm/content-routing.md +++ b/content/docs/kubernetes/main/llm/content-routing.md @@ -4,12 +4,14 @@ weight: 45 description: Route requests to different LLM backends based on request body content, such as the requested model name. test: content-routing-model: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/content-routing.md - path: content-routing + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/content-routing.md + path: content-routing --- {{< reuse "agw-docs/pages/agentgateway/llm/content-routing.md" >}} diff --git a/content/docs/kubernetes/main/llm/load-balancing.md b/content/docs/kubernetes/main/llm/load-balancing.md index 6fa2fa905..1540c7e6d 100644 --- a/content/docs/kubernetes/main/llm/load-balancing.md +++ b/content/docs/kubernetes/main/llm/load-balancing.md @@ -4,12 +4,14 @@ weight: 35 description: Distribute requests across multiple LLM providers automatically (Power of Two Choices, P2C). test: load-balancing-multi-provider: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/load-balancing.md - path: load-balancing + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/load-balancing.md + path: load-balancing --- {{< reuse "agw-docs/pages/agentgateway/llm/load-balancing.md" >}} diff --git a/content/docs/kubernetes/main/llm/models/serve.md b/content/docs/kubernetes/main/llm/models/serve.md index 68fa0ca24..f5cda183c 100644 --- a/content/docs/kubernetes/main/llm/models/serve.md +++ b/content/docs/kubernetes/main/llm/models/serve.md @@ -4,13 +4,15 @@ weight: 20 description: Expose an LLM model to clients with an AgentgatewayModel resource, including wildcard matching and provider credentials. test: serve-model: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/httpbun.md - path: setup-httpbun-llm - - path: serve-model + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/httpbun.md + path: setup-httpbun-llm + - path: serve-model --- Expose an LLM model to clients with an `{{< reuse "agw-docs/snippets/agentgatewaymodel.md" >}}` resource. diff --git a/content/docs/kubernetes/main/llm/models/virtual.md b/content/docs/kubernetes/main/llm/models/virtual.md index 277f84448..cf04e3f8e 100644 --- a/content/docs/kubernetes/main/llm/models/virtual.md +++ b/content/docs/kubernetes/main/llm/models/virtual.md @@ -4,15 +4,17 @@ weight: 30 description: Publish one client-facing model name and route requests across several models with weighted, failover, or conditional routing. test: virtual-models: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/httpbun.md - path: setup-httpbun-llm - - file: ${versionRoot}/llm/models/serve.md - path: serve-model - - path: virtual-models + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/httpbun.md + path: setup-httpbun-llm + - file: ${versionRoot}/llm/models/serve.md + path: serve-model + - path: virtual-models --- Publish one client-facing model name and route requests across several models. diff --git a/content/docs/kubernetes/main/llm/providers/httpbun.md b/content/docs/kubernetes/main/llm/providers/httpbun.md index 9b61950ef..d93daefa7 100644 --- a/content/docs/kubernetes/main/llm/providers/httpbun.md +++ b/content/docs/kubernetes/main/llm/providers/httpbun.md @@ -4,12 +4,14 @@ weight: 100 description: Set up httpbun as a mock OpenAI-compatible LLM backend for testing without API keys. test: setup-httpbun-llm: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/httpbun.md - path: setup-httpbun-llm + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/httpbun.md + path: setup-httpbun-llm --- {{< reuse "agw-docs/pages/agentgateway/llm/providers/httpbun.md" >}} diff --git a/content/docs/kubernetes/main/llm/providers/ollama.md b/content/docs/kubernetes/main/llm/providers/ollama.md index 9619bfb65..edd4a9e95 100644 --- a/content/docs/kubernetes/main/llm/providers/ollama.md +++ b/content/docs/kubernetes/main/llm/providers/ollama.md @@ -4,12 +4,14 @@ weight: 25 description: Configure agentgateway to route LLM traffic to Ollama for local model inference test: ollama-provider-setup: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/ollama.md - path: ollama-provider-setup + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/ollama.md + path: ollama-provider-setup --- > [!NOTE] diff --git a/content/docs/kubernetes/main/llm/providers/openai.md b/content/docs/kubernetes/main/llm/providers/openai.md index ebbf3cd7f..42851a56e 100644 --- a/content/docs/kubernetes/main/llm/providers/openai.md +++ b/content/docs/kubernetes/main/llm/providers/openai.md @@ -4,12 +4,14 @@ weight: 20 description: Configure OpenAI as an LLM provider for agentgateway. test: openai-setup: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup --- {{< reuse "agw-docs/pages/agentgateway/llm/providers/openai.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/llm/rate-limit.md b/content/docs/kubernetes/main/llm/rate-limit.md index 226f03dc4..455c3b3bb 100644 --- a/content/docs/kubernetes/main/llm/rate-limit.md +++ b/content/docs/kubernetes/main/llm/rate-limit.md @@ -4,14 +4,16 @@ weight: 80 description: Control LLM costs with token-based rate limiting and request-based limits. test: llm-token-rate-limit: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/httpbun.md - path: setup-httpbun-llm - - file: ${versionRoot}/llm/rate-limit.md - path: llm-token-rate-limit + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/httpbun.md + path: setup-httpbun-llm + - file: ${versionRoot}/llm/rate-limit.md + path: llm-token-rate-limit --- {{< reuse "agw-docs/pages/agentgateway/llm/rate-limit.md" >}} diff --git a/content/docs/kubernetes/main/llm/realtime.md b/content/docs/kubernetes/main/llm/realtime.md index cc9d515d8..e6f87837c 100644 --- a/content/docs/kubernetes/main/llm/realtime.md +++ b/content/docs/kubernetes/main/llm/realtime.md @@ -4,14 +4,16 @@ weight: 47 description: Proxy OpenAI Realtime API WebSocket traffic and track token usage. test: realtime: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/llm/realtime.md - path: realtime + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/llm/realtime.md + path: realtime --- {{< reuse "agw-docs/pages/agentgateway/llm/realtime.md" >}} diff --git a/content/docs/kubernetes/main/llm/transformations.md b/content/docs/kubernetes/main/llm/transformations.md index bcc3d14a4..e8ec80154 100644 --- a/content/docs/kubernetes/main/llm/transformations.md +++ b/content/docs/kubernetes/main/llm/transformations.md @@ -4,23 +4,27 @@ weight: 70 description: Dynamically compute and set LLM request fields using CEL expressions. test: llm-transformations: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/llm/transformations.md - path: llm-transformations + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/llm/transformations.md + path: llm-transformations llm-model-headers: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/llm/transformations.md - path: llm-model-headers + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/llm/transformations.md + path: llm-model-headers --- {{< reuse "agw-docs/pages/agentgateway/llm/transformations.md" >}} diff --git a/content/docs/kubernetes/main/mcp/auth/entra.md b/content/docs/kubernetes/main/mcp/auth/entra.md index dcc7e6721..a707ce825 100644 --- a/content/docs/kubernetes/main/mcp/auth/entra.md +++ b/content/docs/kubernetes/main/mcp/auth/entra.md @@ -4,14 +4,16 @@ weight: 50 description: Configure Microsoft Entra ID (Azure AD) as an OAuth identity provider for MCP authentication with agentgateway. test: setup-entra: - - file: ${versionRoot}/install/helm.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/static-mcp.md - path: setup-mcp-server - - file: ${versionRoot}/mcp/auth/entra.md - path: setup-entra + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server + - file: ${versionRoot}/mcp/auth/entra.md + path: setup-entra --- {{< reuse "agw-docs/pages/agentgateway/mcp/mcp-auth-entra.md" >}} diff --git a/content/docs/kubernetes/main/mcp/auth/setup.md b/content/docs/kubernetes/main/mcp/auth/setup.md index 953d46ba0..f60daeae8 100644 --- a/content/docs/kubernetes/main/mcp/auth/setup.md +++ b/content/docs/kubernetes/main/mcp/auth/setup.md @@ -4,16 +4,18 @@ weight: 40 description: Secure MCP servers with OAuth 2.0 authentication using agentgateway and an identity provider like Keycloak. test: mcp-auth-setup: - - file: ${versionRoot}/install/helm.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/static-mcp.md - path: setup-mcp-server - - file: ${versionRoot}/mcp/auth/keycloak.md - path: setup-keycloak - - file: ${versionRoot}/mcp/auth/setup.md - path: mcp-auth-setup + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server + - file: ${versionRoot}/mcp/auth/keycloak.md + path: setup-keycloak + - file: ${versionRoot}/mcp/auth/setup.md + path: mcp-auth-setup --- {{< reuse "agw-docs/pages/agentgateway/mcp/mcp-auth-setup.md" >}} diff --git a/content/docs/kubernetes/main/mcp/dynamic-mcp.md b/content/docs/kubernetes/main/mcp/dynamic-mcp.md index 7b854148c..14c5ccb7f 100644 --- a/content/docs/kubernetes/main/mcp/dynamic-mcp.md +++ b/content/docs/kubernetes/main/mcp/dynamic-mcp.md @@ -4,12 +4,14 @@ weight: 20 description: Route traffic to MCP servers dynamically using label selectors so backends can be updated without changing the Backend resource. test: dynamic-mcp: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/dynamic-mcp.md - path: dynamic-mcp + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/dynamic-mcp.md + path: dynamic-mcp --- {{< reuse "agw-docs/pages/agentgateway/mcp/dynamic.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/mcp/guardrails/setup.md b/content/docs/kubernetes/main/mcp/guardrails/setup.md index c4e53758e..758485971 100644 --- a/content/docs/kubernetes/main/mcp/guardrails/setup.md +++ b/content/docs/kubernetes/main/mcp/guardrails/setup.md @@ -4,12 +4,14 @@ weight: 20 description: Gate and mutate MCP method calls with an external ExtMCP policy server. test: mcp-guardrails: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/guardrails/setup.md - path: mcp-guardrails + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/guardrails/setup.md + path: mcp-guardrails --- Gate and mutate Model Context Protocol (MCP) method calls with an external policy server. For more information about how MCP guardrails work, see [About MCP guardrails]({{< link-hextra path="/mcp/guardrails/about" >}}). diff --git a/content/docs/kubernetes/main/mcp/rate-limit.md b/content/docs/kubernetes/main/mcp/rate-limit.md index 6ac3de687..9cc37d8b8 100644 --- a/content/docs/kubernetes/main/mcp/rate-limit.md +++ b/content/docs/kubernetes/main/mcp/rate-limit.md @@ -4,14 +4,16 @@ weight: 65 description: Control MCP tool call rates to prevent overload and ensure fair access to expensive tools. test: mcp-local-rate-limit: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/static-mcp.md - path: setup-mcp-server - - file: ${versionRoot}/mcp/rate-limit.md - path: mcp-local-rate-limit + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server + - file: ${versionRoot}/mcp/rate-limit.md + path: mcp-local-rate-limit --- {{< reuse "agw-docs/pages/agentgateway/mcp/rate-limit.md" >}} diff --git a/content/docs/kubernetes/main/mcp/static-mcp.md b/content/docs/kubernetes/main/mcp/static-mcp.md index d90d28b2b..e229bc2e7 100644 --- a/content/docs/kubernetes/main/mcp/static-mcp.md +++ b/content/docs/kubernetes/main/mcp/static-mcp.md @@ -4,12 +4,14 @@ weight: 10 description: Route traffic to an MCP server at a static address by configuring a fixed Backend resource. test: setup-mcp-server: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/static-mcp.md - path: setup-mcp-server + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server --- {{< reuse "agw-docs/pages/agentgateway/mcp/static.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/mcp/virtual.md b/content/docs/kubernetes/main/mcp/virtual.md index 9d8b86b0c..90254dda9 100644 --- a/content/docs/kubernetes/main/mcp/virtual.md +++ b/content/docs/kubernetes/main/mcp/virtual.md @@ -4,12 +4,14 @@ weight: 30 description: Federate tools from multiple MCP servers on a single gateway endpoint using virtual MCP multiplexing. test: virtual-mcp: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/mcp/virtual.md - path: virtual-mcp + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/virtual.md + path: virtual-mcp --- {{< reuse "agw-docs/pages/agentgateway/mcp/multiplex.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/observability/tracing.md b/content/docs/kubernetes/main/observability/tracing.md index b84e2ad70..3d377b05b 100644 --- a/content/docs/kubernetes/main/observability/tracing.md +++ b/content/docs/kubernetes/main/observability/tracing.md @@ -4,14 +4,16 @@ description: Integrate with OpenTelemetry to collect and analyze request traces. weight: 90 test: tracing: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/observability/tracing.md - path: tracing + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/observability/tracing.md + path: tracing --- Integrate your agentgateway proxy with an OpenTelemetry (OTel) collector and configure custom metadata for your traces with an {{< reuse "agw-docs/snippets/policy.md" >}}. diff --git a/content/docs/kubernetes/main/quickstart/llm.md b/content/docs/kubernetes/main/quickstart/llm.md index 845dcd7f4..22ac76f75 100644 --- a/content/docs/kubernetes/main/quickstart/llm.md +++ b/content/docs/kubernetes/main/quickstart/llm.md @@ -4,10 +4,12 @@ weight: 11 description: Route requests to OpenAI's chat completions API with agentgateway on Kubernetes. test: openai: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/quickstart/llm.md - path: openai-setup + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/quickstart/llm.md + path: openai-setup --- {{< reuse "agw-docs/pages/agentgateway/quickstart/llm.md" >}} diff --git a/content/docs/kubernetes/main/quickstart/mcp.md b/content/docs/kubernetes/main/quickstart/mcp.md index 56091c46c..231ba3b18 100644 --- a/content/docs/kubernetes/main/quickstart/mcp.md +++ b/content/docs/kubernetes/main/quickstart/mcp.md @@ -4,10 +4,12 @@ weight: 12 description: Connect to an MCP server and try tools with agentgateway on Kubernetes. test: mcp: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/quickstart/mcp.md - path: setup-mcp-server + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/quickstart/mcp.md + path: setup-mcp-server --- {{< reuse "agw-docs/pages/agentgateway/quickstart/mcp.md" >}} diff --git a/content/docs/kubernetes/main/resiliency/backend-health.md b/content/docs/kubernetes/main/resiliency/backend-health.md index cce65594b..0f5a21445 100644 --- a/content/docs/kubernetes/main/resiliency/backend-health.md +++ b/content/docs/kubernetes/main/resiliency/backend-health.md @@ -4,14 +4,16 @@ weight: 15 description: Automatically evict and restore unhealthy backend endpoints with passive health checking. test: backend-health: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/backend-health.md - path: backend-health + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/backend-health.md + path: backend-health --- {{< reuse "agw-docs/pages/resiliency/backend-health.md" >}} diff --git a/content/docs/kubernetes/main/resiliency/connection.md b/content/docs/kubernetes/main/resiliency/connection.md index ad31f82bf..abedf7e87 100644 --- a/content/docs/kubernetes/main/resiliency/connection.md +++ b/content/docs/kubernetes/main/resiliency/connection.md @@ -14,24 +14,28 @@ test: path: connection-general connection-http1: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/connection.md - path: connection-http1 + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/connection.md + path: connection-http1 connection-http2-flow: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/connection.md - path: connection-http2-flow + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/connection.md + path: connection-http2-flow --- {{< reuse "agw-docs/pages/resiliency/connection.md" >}} diff --git a/content/docs/kubernetes/main/resiliency/fault-injection.md b/content/docs/kubernetes/main/resiliency/fault-injection.md index 137864452..9ae501415 100644 --- a/content/docs/kubernetes/main/resiliency/fault-injection.md +++ b/content/docs/kubernetes/main/resiliency/fault-injection.md @@ -4,13 +4,15 @@ weight: 20 description: Inject artificial latency into requests to test how your clients and services handle slow responses. test: delay-in-trafficpolicy: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - path: delay-in-trafficpolicy + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - path: delay-in-trafficpolicy --- {{< reuse "agw-docs/pages/resiliency/fault-injection.md" >}} diff --git a/content/docs/kubernetes/main/resiliency/keepalive.md b/content/docs/kubernetes/main/resiliency/keepalive.md index 2a99f541d..a2d326d0a 100644 --- a/content/docs/kubernetes/main/resiliency/keepalive.md +++ b/content/docs/kubernetes/main/resiliency/keepalive.md @@ -4,24 +4,28 @@ weight: 10 description: Manage idle and stale connections with TCP and HTTP keepalive. test: tcp-keepalive: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/keepalive.md - path: tcp-keepalive + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/keepalive.md + path: tcp-keepalive http-keepalive: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/keepalive.md - path: http-keepalive + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/keepalive.md + path: http-keepalive --- {{< reuse "agw-docs/pages/resiliency/keepalive.md" >}} diff --git a/content/docs/kubernetes/main/resiliency/retry/per-try-timeout.md b/content/docs/kubernetes/main/resiliency/retry/per-try-timeout.md index 42858e582..e950fd1f8 100644 --- a/content/docs/kubernetes/main/resiliency/retry/per-try-timeout.md +++ b/content/docs/kubernetes/main/resiliency/retry/per-try-timeout.md @@ -13,23 +13,27 @@ test: - file: ${versionRoot}/resiliency/retry/per-try-timeout.md path: per-try-timeout-in-httproute per-try-timeout-in-agentgateway: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/retry/per-try-timeout.md - path: per-try-timeout-in-agentgateway + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/retry/per-try-timeout.md + path: per-try-timeout-in-agentgateway per-try-timeout-in-gatewaylistener: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/retry/per-try-timeout.md - path: per-try-timeout-in-gatewaylistener + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/retry/per-try-timeout.md + path: per-try-timeout-in-gatewaylistener --- {{< reuse "agw-docs/pages/resiliency/retry/per-try-timeout.md" >}} diff --git a/content/docs/kubernetes/main/resiliency/retry/retry.md b/content/docs/kubernetes/main/resiliency/retry/retry.md index 58b1633c9..270971c3e 100644 --- a/content/docs/kubernetes/main/resiliency/retry/retry.md +++ b/content/docs/kubernetes/main/resiliency/retry/retry.md @@ -13,23 +13,27 @@ test: - file: ${versionRoot}/resiliency/retry/retry.md path: retry-in-httproute retry-in-agentgateway: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/retry/retry.md - path: retry-in-agentgateway + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/retry/retry.md + path: retry-in-agentgateway retry-in-gatewaylistener: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/retry/retry.md - path: retry-in-gatewaylistener + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/retry/retry.md + path: retry-in-gatewaylistener --- {{< reuse "agw-docs/pages/resiliency/retry/retry.md" >}} diff --git a/content/docs/kubernetes/main/resiliency/timeouts/idle.md b/content/docs/kubernetes/main/resiliency/timeouts/idle.md index ae8ba329b..b76535ea8 100644 --- a/content/docs/kubernetes/main/resiliency/timeouts/idle.md +++ b/content/docs/kubernetes/main/resiliency/timeouts/idle.md @@ -4,14 +4,16 @@ weight: 20 description: Set idle timeouts to terminate inactive HTTP/1 connections. test: idle-timeout: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/timeouts/idle.md - path: idle-timeout + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/timeouts/idle.md + path: idle-timeout --- {{< reuse "agw-docs/pages/resiliency/timeouts/idle.md" >}} diff --git a/content/docs/kubernetes/main/resiliency/timeouts/request.md b/content/docs/kubernetes/main/resiliency/timeouts/request.md index aa931ea42..f228a073d 100644 --- a/content/docs/kubernetes/main/resiliency/timeouts/request.md +++ b/content/docs/kubernetes/main/resiliency/timeouts/request.md @@ -13,23 +13,27 @@ test: - file: ${versionRoot}/resiliency/timeouts/request.md path: timeout-in-httproute timeout-in-trafficpolicy: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/timeouts/request.md - path: timeout-in-trafficpolicy + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/timeouts/request.md + path: timeout-in-trafficpolicy timeout-in-gatewaylistener: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/resiliency/timeouts/request.md - path: timeout-in-gatewaylistener + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/resiliency/timeouts/request.md + path: timeout-in-gatewaylistener --- {{< reuse "agw-docs/pages/resiliency/timeouts/request.md" >}} diff --git a/content/docs/kubernetes/main/security/access-logging.md b/content/docs/kubernetes/main/security/access-logging.md index 0a197bf8d..6902ee759 100644 --- a/content/docs/kubernetes/main/security/access-logging.md +++ b/content/docs/kubernetes/main/security/access-logging.md @@ -4,14 +4,16 @@ weight: 10 description: Capture an access log for all the requests that enter the proxy. test: access-logging: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/access-logging.md - path: access-logging + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/access-logging.md + path: access-logging --- {{< reuse "agw-docs/pages/security/access-logging.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/security/authorization.md b/content/docs/kubernetes/main/security/authorization.md index 086c36027..e198fa154 100644 --- a/content/docs/kubernetes/main/security/authorization.md +++ b/content/docs/kubernetes/main/security/authorization.md @@ -4,14 +4,16 @@ weight: 15 description: Control which requests are allowed to reach your backends using authorization policies with Allow, Require, and Deny actions. test: authorization: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/authorization.md - path: authorization + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/authorization.md + path: authorization --- {{< reuse "agw-docs/pages/security/authorization.md" >}} diff --git a/content/docs/kubernetes/main/security/backend-authn-cross-app-access.md b/content/docs/kubernetes/main/security/backend-authn-cross-app-access.md index a3a00b67b..0bc87b2e1 100644 --- a/content/docs/kubernetes/main/security/backend-authn-cross-app-access.md +++ b/content/docs/kubernetes/main/security/backend-authn-cross-app-access.md @@ -4,14 +4,16 @@ weight: 20 description: Call a downstream API as the authenticated end user with the OAuth Identity Assertion Authorization Grant. test: cross-app-access: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/backend-authn-cross-app-access.md - path: cross-app-access + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/backend-authn-cross-app-access.md + path: cross-app-access --- {{< reuse "agw-docs/pages/security/backend-authn-cross-app-access.md" >}} diff --git a/content/docs/kubernetes/main/security/backend-authn-jwt-sign.md b/content/docs/kubernetes/main/security/backend-authn-jwt-sign.md index aa96342c9..dd20eb619 100644 --- a/content/docs/kubernetes/main/security/backend-authn-jwt-sign.md +++ b/content/docs/kubernetes/main/security/backend-authn-jwt-sign.md @@ -4,14 +4,16 @@ weight: 30 description: Sign a short-lived JWT with your own private key on every request to a backend. test: jwt-sign: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/backend-authn-jwt-sign.md - path: jwt-sign + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/backend-authn-jwt-sign.md + path: jwt-sign --- {{< reuse "agw-docs/pages/security/backend-authn-jwt-sign.md" >}} diff --git a/content/docs/kubernetes/main/security/backendtls.md b/content/docs/kubernetes/main/security/backendtls.md index 806ef3252..0b071f2ab 100644 --- a/content/docs/kubernetes/main/security/backendtls.md +++ b/content/docs/kubernetes/main/security/backendtls.md @@ -4,11 +4,13 @@ weight: 10 description: Originate one-way TLS connections from the Gateway to backend services. test: backendtls-secret-ca: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - path: backendtls-secret-ca + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - path: backendtls-secret-ca --- {{< reuse "agw-docs/pages/security/backendtls.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/security/cors.md b/content/docs/kubernetes/main/security/cors.md index d2430a80e..21c778831 100644 --- a/content/docs/kubernetes/main/security/cors.md +++ b/content/docs/kubernetes/main/security/cors.md @@ -14,14 +14,16 @@ test: path: cors-in-httproute cors-in-agentgatewaypolicy: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/cors.md - path: cors-in-agentgatewaypolicy + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/cors.md + path: cors-in-agentgatewaypolicy --- {{< reuse "agw-docs/pages/security/cors.md" >}} diff --git a/content/docs/kubernetes/main/security/csrf.md b/content/docs/kubernetes/main/security/csrf.md index 7c9b92a72..704cabfb2 100644 --- a/content/docs/kubernetes/main/security/csrf.md +++ b/content/docs/kubernetes/main/security/csrf.md @@ -4,14 +4,16 @@ weight: 10 description: Protect your applications from Cross-Site Request Forgery (CSRF) attacks. test: csrf: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/csrf.md - path: csrf + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/csrf.md + path: csrf --- {{< reuse "agw-docs/pages/security/csrf.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/security/jwt/setup.md b/content/docs/kubernetes/main/security/jwt/setup.md index d66a53d06..e1bdffbce 100644 --- a/content/docs/kubernetes/main/security/jwt/setup.md +++ b/content/docs/kubernetes/main/security/jwt/setup.md @@ -4,16 +4,18 @@ description: Set up JWT authentication with an identity provider like Keycloak. weight: 10 test: jwt-claims: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/jwt/setup.md - path: setup-keycloak - - file: ${versionRoot}/security/jwt/setup.md - path: jwt-claims + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/jwt/setup.md + path: setup-keycloak + - file: ${versionRoot}/security/jwt/setup.md + path: jwt-claims --- {{< reuse "agw-docs/pages/security/jwt-setup.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/security/rate-limit-global.md b/content/docs/kubernetes/main/security/rate-limit-global.md index 1af634236..e10f2af33 100644 --- a/content/docs/kubernetes/main/security/rate-limit-global.md +++ b/content/docs/kubernetes/main/security/rate-limit-global.md @@ -4,14 +4,16 @@ weight: 45 description: Apply distributed rate limits across multiple agentgateway replicas using an external rate limit service. test: global-rate-limit-by-ip: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/rate-limit-global.md - path: global-rate-limit-by-ip + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/rate-limit-global.md + path: global-rate-limit-by-ip --- {{< reuse "agw-docs/pages/security/rate-limit-global.md" >}} diff --git a/content/docs/kubernetes/main/security/rate-limit-http.md b/content/docs/kubernetes/main/security/rate-limit-http.md index e915c0db8..90aff3d96 100644 --- a/content/docs/kubernetes/main/security/rate-limit-http.md +++ b/content/docs/kubernetes/main/security/rate-limit-http.md @@ -4,14 +4,16 @@ weight: 40 description: Apply local and global rate limits to HTTP traffic to protect your backend services from overload. test: local-rate-limit: - - file: ${versionRoot}/install/helm.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/security/rate-limit-http.md - path: local-rate-limit + type: [schema, functional] + steps: + - file: ${versionRoot}/install/helm.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/security/rate-limit-http.md + path: local-rate-limit --- {{< reuse "agw-docs/pages/security/rate-limit-http.md" >}} diff --git a/content/docs/kubernetes/main/setup/customize/customize.md b/content/docs/kubernetes/main/setup/customize/customize.md index 13a104290..2d4b7785f 100644 --- a/content/docs/kubernetes/main/setup/customize/customize.md +++ b/content/docs/kubernetes/main/setup/customize/customize.md @@ -4,12 +4,14 @@ weight: 20 description: Customize the agentgateway proxy for different deployment scenarios and requirements. test: customize: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/setup/customize/customize.md - path: customize + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/setup/customize/customize.md + path: customize --- {{< reuse "agw-docs/pages/setup/customize.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/setup/listeners/tls-settings.md b/content/docs/kubernetes/main/setup/listeners/tls-settings.md index 0ce68dfe0..b0095b3c9 100644 --- a/content/docs/kubernetes/main/setup/listeners/tls-settings.md +++ b/content/docs/kubernetes/main/setup/listeners/tls-settings.md @@ -4,12 +4,14 @@ description: Configure advanced TLS settings such as cipher suites and protocol weight: 20 test: tls-settings: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/setup/listeners/tls-settings.md - path: tls-settings + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/setup/listeners/tls-settings.md + path: tls-settings --- {{< reuse "agw-docs/pages/setup/listeners/tls-settings.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/traffic-management/buffering.md b/content/docs/kubernetes/main/traffic-management/buffering.md index 469604ccd..daa42e851 100644 --- a/content/docs/kubernetes/main/traffic-management/buffering.md +++ b/content/docs/kubernetes/main/traffic-management/buffering.md @@ -4,12 +4,14 @@ weight: 10 description: Buffer requests and responses for inspection or replay. test: buffering: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/traffic-management/buffering.md - path: buffering + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/traffic-management/buffering.md + path: buffering --- {{< reuse "agw-docs/pages/traffic-management/buffering.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/dfp.md b/content/docs/kubernetes/main/traffic-management/dfp.md index 9b4378d1a..8673e5ece 100644 --- a/content/docs/kubernetes/main/traffic-management/dfp.md +++ b/content/docs/kubernetes/main/traffic-management/dfp.md @@ -4,14 +4,16 @@ weight: 10 description: Route traffic dynamically to upstream servers based on request characteristics. test: dfp: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/dfp.md - path: dfp + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/dfp.md + path: dfp --- {{< reuse "agw-docs/pages/traffic-management/dfp.md" >}} \ No newline at end of file diff --git a/content/docs/kubernetes/main/traffic-management/direct-response.md b/content/docs/kubernetes/main/traffic-management/direct-response.md index f9e608057..f99571557 100644 --- a/content/docs/kubernetes/main/traffic-management/direct-response.md +++ b/content/docs/kubernetes/main/traffic-management/direct-response.md @@ -4,14 +4,16 @@ weight: 10 description: Return responses directly without forwarding to upstream services. test: direct-response: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/direct-response.md - path: direct-response + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/direct-response.md + path: direct-response --- {{< reuse "agw-docs/pages/traffic-management/direct-response.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/extproc.md b/content/docs/kubernetes/main/traffic-management/extproc.md index 1312dd29f..23f3f5f22 100644 --- a/content/docs/kubernetes/main/traffic-management/extproc.md +++ b/content/docs/kubernetes/main/traffic-management/extproc.md @@ -4,14 +4,16 @@ weight: 10 description: Modify requests and responses with an external gRPC processing server. test: extproc: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/extproc.md - path: extproc + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/extproc.md + path: extproc --- Modify aspects of an HTTP request or response with an external processing server. diff --git a/content/docs/kubernetes/main/traffic-management/header-control/early-request-header-modifier.md b/content/docs/kubernetes/main/traffic-management/header-control/early-request-header-modifier.md index 86d02c79b..9e0ffd5ed 100644 --- a/content/docs/kubernetes/main/traffic-management/header-control/early-request-header-modifier.md +++ b/content/docs/kubernetes/main/traffic-management/header-control/early-request-header-modifier.md @@ -4,14 +4,16 @@ weight: 30 description: Modify request headers in the early phase of request processing. test: remove-reserved-header: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/header-control/early-request-header-modifier.md - path: remove-reserved-header + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/header-control/early-request-header-modifier.md + path: remove-reserved-header --- {{< reuse "agw-docs/pages/traffic-management/header-control/early-request-header-modifier.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/rewrite/host.md b/content/docs/kubernetes/main/traffic-management/rewrite/host.md index 010b23735..b37a17632 100644 --- a/content/docs/kubernetes/main/traffic-management/rewrite/host.md +++ b/content/docs/kubernetes/main/traffic-management/rewrite/host.md @@ -4,14 +4,20 @@ weight: 461 description: Replace the host header value before forwarding a request to a backend service. test: host-rewrite: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/rewrite/host.md - path: host-rewrite + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/rewrite/host.md + path: host-rewrite + assert: + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-wait.sh + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-warmup.sh + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-assert.sh --- {{< reuse "agw-docs/pages/traffic-management/rewrite/host.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/rewrite/path.md b/content/docs/kubernetes/main/traffic-management/rewrite/path.md index 019c849be..60edf35eb 100644 --- a/content/docs/kubernetes/main/traffic-management/rewrite/path.md +++ b/content/docs/kubernetes/main/traffic-management/rewrite/path.md @@ -4,23 +4,35 @@ weight: 462 description: Rewrite path prefixes in requests. test: path-rewrite-prefix: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/rewrite/path.md - path: path-rewrite-prefix + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/rewrite/path.md + path: path-rewrite-prefix + assert: + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-prefix-wait.sh + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-prefix-warmup.sh + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-prefix-assert.sh path-rewrite-full: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/rewrite/path.md - path: path-rewrite-full + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/rewrite/path.md + path: path-rewrite-full + assert: + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-full-wait.sh + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-full-warmup.sh + - products/agentgateway/main/traffic-management/rewrite/path-rewrite-full-assert.sh --- {{< reuse "agw-docs/pages/traffic-management/rewrite/path.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/route-delegation/inheritance/trafficpolicies.md b/content/docs/kubernetes/main/traffic-management/route-delegation/inheritance/trafficpolicies.md index 8d0781eab..95902d5b8 100644 --- a/content/docs/kubernetes/main/traffic-management/route-delegation/inheritance/trafficpolicies.md +++ b/content/docs/kubernetes/main/traffic-management/route-delegation/inheritance/trafficpolicies.md @@ -4,14 +4,16 @@ weight: 20 description: Learn how policies in `AgentgatewayPolicy` resources are inherited and overridden along the route delegation chain. test: trafficpolicies: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/traffic-management/route-delegation/inheritance/trafficpolicies.md - path: route-delegation-prereq - - file: ${versionRoot}/traffic-management/route-delegation/inheritance/trafficpolicies.md - path: trafficpolicies + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/traffic-management/route-delegation/inheritance/trafficpolicies.md + path: route-delegation-prereq + - file: ${versionRoot}/traffic-management/route-delegation/inheritance/trafficpolicies.md + path: trafficpolicies --- {{< reuse "agw-docs/pages/traffic-management/route-delegation/inheritance/trafficpolicies.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/traffic-split.md b/content/docs/kubernetes/main/traffic-management/traffic-split.md index 14c9f7c52..04bb64801 100644 --- a/content/docs/kubernetes/main/traffic-management/traffic-split.md +++ b/content/docs/kubernetes/main/traffic-management/traffic-split.md @@ -4,14 +4,16 @@ weight: 60 description: Set up A/B testing, traffic splitting, and canary deployments using weighted routing. test: traffic-split-llm-models: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/traffic-management/traffic-split.md - path: traffic-split-llm + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/traffic-management/traffic-split.md + path: traffic-split-llm --- {{< reuse "agw-docs/pages/traffic-management/traffic-split.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/access-logs.md b/content/docs/kubernetes/main/traffic-management/transformations/access-logs.md index 355c4202c..af1536b45 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/access-logs.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/access-logs.md @@ -4,23 +4,27 @@ weight: 120 description: Log CEL context variables to access logs to inspect and debug transformation expressions at runtime. test: access-logs: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/access-logs.md - path: access-logs + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/access-logs.md + path: access-logs access-logs-filter: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/access-logs.md - path: access-logs-filter + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/access-logs.md + path: access-logs-filter --- {{< reuse "agw-docs/pages/traffic-management/transformations/access-logs.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/encode.md b/content/docs/kubernetes/main/traffic-management/transformations/encode.md index a60518f3c..e224b9dc3 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/encode.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/encode.md @@ -4,22 +4,40 @@ weight: 20 description: Automatically encode and decode base64 values in request headers. test: encode: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/encode.md - path: encode + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/encode.md + path: encode + assert: + - products/agentgateway/main/traffic-management/transformations/encode.sh + encode-schema: + type: schema + steps: + - file: ${versionRoot}/traffic-management/transformations/encode.md + path: encode decode: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/encode.md - path: decode + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/encode.md + path: decode + assert: + - products/agentgateway/main/traffic-management/transformations/decode.sh + decode-schema: + type: schema + steps: + - file: ${versionRoot}/traffic-management/transformations/encode.md + path: decode --- {{< reuse "agw-docs/pages/traffic-management/transformations/encode.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/filter-request-body.md b/content/docs/kubernetes/main/traffic-management/transformations/filter-request-body.md index ec5ac6866..33cc24476 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/filter-request-body.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/filter-request-body.md @@ -4,14 +4,16 @@ weight: 60 description: Use filterKeys() and merge() CEL functions to strip unwanted fields from a JSON request body and inject defaults before forwarding to the upstream. test: filter-request-body: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/filter-request-body.md - path: filter-request-body + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/filter-request-body.md + path: filter-request-body --- {{< reuse "agw-docs/pages/traffic-management/transformations/filter-request-body.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/forward.md b/content/docs/kubernetes/main/traffic-management/transformations/forward.md index a7dd77c7d..7976f0b83 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/forward.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/forward.md @@ -4,14 +4,16 @@ weight: 40 description: Use CEL expressions to construct a full request URL from context variables and forward it upstream as a request header. test: forward: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/forward.md - path: forward + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/forward.md + path: forward --- {{< reuse "agw-docs/pages/traffic-management/transformations/forward.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/inject-response-body.md b/content/docs/kubernetes/main/traffic-management/transformations/inject-response-body.md index f5d8002e9..eef0a7beb 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/inject-response-body.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/inject-response-body.md @@ -4,23 +4,27 @@ weight: 55 description: Learn how to return a customized response body and how to replace specific values in the body. test: inject-header-into-body: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/inject-response-body.md - path: inject-header-into-body + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/inject-response-body.md + path: inject-header-into-body inject-body-field-into-body: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/inject-response-body.md - path: inject-body-field-into-body + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/inject-response-body.md + path: inject-body-field-into-body --- {{< reuse "agw-docs/pages/traffic-management/transformations/inject-response-body.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/inject-response-headers.md b/content/docs/kubernetes/main/traffic-management/transformations/inject-response-headers.md index a69d3f775..7cf0702ef 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/inject-response-headers.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/inject-response-headers.md @@ -4,14 +4,16 @@ weight: 5 description: Extract values from a request header and inject it as a header to your response. test: inject-response-headers: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/inject-response-headers.md - path: inject-response-headers + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/inject-response-headers.md + path: inject-response-headers --- {{< reuse "agw-docs/pages/traffic-management/transformations/inject-response-headers.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/llm-model-headers.md b/content/docs/kubernetes/main/traffic-management/transformations/llm-model-headers.md index c6eb9f8cb..60f5a25eb 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/llm-model-headers.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/llm-model-headers.md @@ -4,24 +4,28 @@ weight: 47 description: Detect model fallback by injecting the requested and actual LLM model names as response headers using llm.requestModel and llm.responseModel CEL variables. test: llm-transformations: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/traffic-management/transformations/llm-model-headers.md - path: llm-transformations + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/traffic-management/transformations/llm-model-headers.md + path: llm-transformations llm-model-headers: - - file: ${versionRoot}/quickstart/install.md - path: standard - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/llm/providers/openai.md - path: openai-setup - - file: ${versionRoot}/traffic-management/transformations/llm-model-headers.md - path: llm-model-headers + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: standard + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/llm/providers/openai.md + path: openai-setup + - file: ${versionRoot}/traffic-management/transformations/llm-model-headers.md + path: llm-model-headers --- {{< reuse "agw-docs/pages/agentgateway/llm/transformations.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/path-method.md b/content/docs/kubernetes/main/traffic-management/transformations/path-method.md index ef2b7afd6..f8f29f74d 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/path-method.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/path-method.md @@ -4,14 +4,16 @@ weight: 45 description: Use pseudo headers to conditionally rewrite the request path and HTTP method based on a request header value. test: path-method: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/path-method.md - path: path-method + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/path-method.md + path: path-method --- {{< reuse "agw-docs/pages/traffic-management/transformations/path-method.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/query.md b/content/docs/kubernetes/main/traffic-management/transformations/query.md index 29e47d76a..504a8a908 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/query.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/query.md @@ -4,14 +4,16 @@ weight: 46 description: Read a query parameter from the request URI and inject it as a request header using a CEL conditional expression. test: query: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/query.md - path: query + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/query.md + path: query --- {{< reuse "agw-docs/pages/traffic-management/transformations/query.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/remove-header.md b/content/docs/kubernetes/main/traffic-management/transformations/remove-header.md index cbfabc497..27bfe902e 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/remove-header.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/remove-header.md @@ -4,14 +4,16 @@ weight: 50 description: Remove sensitive or internal headers from requests before they reach the upstream. test: remove-header: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/remove-header.md - path: remove-header + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/remove-header.md + path: remove-header --- {{< reuse "agw-docs/pages/traffic-management/transformations/remove-header.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/rewrite.md b/content/docs/kubernetes/main/traffic-management/transformations/rewrite.md index 5832f15ca..04cf8f768 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/rewrite.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/rewrite.md @@ -4,14 +4,23 @@ weight: 30 description: Use CEL functions to rewrite request paths in a header. test: rewrite: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/rewrite.md - path: rewrite + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/rewrite.md + path: rewrite + assert: + - products/agentgateway/main/traffic-management/transformations/rewrite.sh + rewrite-schema: + type: schema + steps: + - file: ${versionRoot}/traffic-management/transformations/rewrite.md + path: rewrite --- {{< reuse "agw-docs/pages/traffic-management/transformations/rewrite.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/status.md b/content/docs/kubernetes/main/traffic-management/transformations/status.md index 6f47d560e..e743dba75 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/status.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/status.md @@ -4,14 +4,23 @@ weight: 60 description: Update the response status based on the headers in a response. test: change-response-status: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/status.md - path: change-response-status + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/status.md + path: change-response-status + assert: + - products/agentgateway/main/traffic-management/transformations/status.sh + change-response-status-schema: + type: schema + steps: + - file: ${versionRoot}/traffic-management/transformations/status.md + path: change-response-status --- {{< reuse "agw-docs/pages/traffic-management/transformations/status.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/tracing.md b/content/docs/kubernetes/main/traffic-management/transformations/tracing.md index 044e1405d..5e4c62afe 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/tracing.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/tracing.md @@ -4,14 +4,16 @@ weight: 10 description: Use uuid() and random() CEL functions to inject a unique request ID and a random sampling value into request headers. test: tracing: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/tracing.md - path: tracing + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/tracing.md + path: tracing --- {{< reuse "agw-docs/pages/traffic-management/transformations/tracing.md" >}} diff --git a/content/docs/kubernetes/main/traffic-management/transformations/validate.md b/content/docs/kubernetes/main/traffic-management/transformations/validate.md index 313066d79..48c11ea6b 100644 --- a/content/docs/kubernetes/main/traffic-management/transformations/validate.md +++ b/content/docs/kubernetes/main/traffic-management/transformations/validate.md @@ -4,23 +4,27 @@ weight: 70 description: Use default() and fail() CEL functions to enforce required fields and apply default values on a JSON request body. test: validate-defaults: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/validate.md - path: validate-defaults + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/validate.md + path: validate-defaults validate-skip: - - file: ${versionRoot}/quickstart/install.md - path: experimental - - file: ${versionRoot}/setup/gateway.md - path: all - - file: ${versionRoot}/install/sample-app.md - path: install-httpbin - - file: ${versionRoot}/traffic-management/transformations/validate.md - path: validate-skip + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/install/sample-app.md + path: install-httpbin + - file: ${versionRoot}/traffic-management/transformations/validate.md + path: validate-skip --- {{< reuse "agw-docs/pages/traffic-management/transformations/validate.md" >}} diff --git a/go.mod b/go.mod index 2619e12c6..bd9ab595b 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,4 @@ module github.com/agentgateway/website go 1.21 -require ( - github.com/solo-io/docs-theme-extras v0.1.25 // indirect -) +require github.com/solo-io/docs-theme-extras v0.2.0-beta.13 // indirect diff --git a/go.sum b/go.sum index ed1bbca56..36952f436 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/solo-io/docs-theme-extras v0.1.25 h1:9SvHWXlrBxgk3/0NnV+JvkqQ7/rww4daIzdCYtSubVg= github.com/solo-io/docs-theme-extras v0.1.25/go.mod h1:jjjYu/QoD+vMu30zgcpfEuTEGuJOJWs5qai/K18kltg= +github.com/solo-io/docs-theme-extras v0.2.0-beta.13 h1:dHS5XZKRQEKobUv2UFxSDUQSXHvwclyjATxI3XkXyjI= +github.com/solo-io/docs-theme-extras v0.2.0-beta.13/go.mod h1:jjjYu/QoD+vMu30zgcpfEuTEGuJOJWs5qai/K18kltg= diff --git a/scripts/TEST_FRAMEWORK.md b/scripts/TEST_FRAMEWORK.md index 17e66b085..44ccbb9bb 100644 --- a/scripts/TEST_FRAMEWORK.md +++ b/scripts/TEST_FRAMEWORK.md @@ -65,6 +65,60 @@ The `paths=` attribute works identically to fenced blocks. --- +## External test content in docs-tests + +The hidden `{{< doc-test >}}` shortcode above accepts an optional `file="..."` +attribute. When present, the extractor reads the block's content from that path +resolved against a `docs-tests` checkout, instead of an inline body between the +shortcode tags: + +```md +{{< doc-test paths="rewrite" file="products/agentgateway/main/traffic-management/transformations/rewrite.sh" >}}{{< /doc-test >}} +``` + +- `doc_test_extract.py` and `doc_test_run.py` both accept `--docs-tests-root `, + or the `DOCS_TESTS_ROOT` environment variable. If neither is set, it defaults to a + sibling `docs-tests` directory next to this repo's own root. +- A missing or misspelled `file=` path fails immediately with a `FileNotFoundError` + naming the source doc, line number, and the resolved path — it never silently + drops the block. + +A step in front matter's `test:` metadata can name the same kind of external content +directly instead, via an `assert:` list, leaving nothing but the real content and the +`paths="X"` tag in the page body — no shortcode line at all: + +```yaml +test: + host-rewrite: + type: functional + steps: + - file: ${versionRoot}/traffic-management/rewrite/host.md + path: host-rewrite + assert: + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-wait.sh + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-warmup.sh + - products/agentgateway/main/traffic-management/rewrite/host-rewrite-assert.sh +``` + +Each entry is a `docs-tests`-relative path, run in list order, anchored to the first +block in that file sharing the step's `paths=` selector — the same position a reader +would expect from where an inline shortcode would otherwise sit. Anchoring to the +*first* matching block (not appending at the end of the file) matters because a page +often reuses the same `paths=` value later for something unrelated, like a `Cleanup` +section's `kubectl delete`; the assertion needs to run before that, not after it. Only +scenarios that actually execute something need `assert:` — `type: schema` never runs +anything, so it has no equivalent. + +Both mechanisms are supported and can coexist across different pages. `assert:` is the +newer, cleaner form for any new page; the inline `file="..."` shortcode still works for +pages that haven't been converted. + +This is unrelated to the `` HTML comment some of +the extractor's code still supports — that mechanism runs an external `bun test ` +as a separate subprocess and is not used for this external-content use case. + +--- + ## Front matter test metadata On the page being tested, add a `test:` key to the YAML front matter. Each child key is a named test scenario. Each entry in the list is a `file`+`path` pair — a source file and the path selector to pull from it. @@ -118,6 +172,74 @@ The literal equivalent (hardcoding `main`) is still accepted, but then copying t Multiple scenarios on the same page each get their own kind cluster and generated script. +### Typing scenarios + +A scenario can declare a `type:` alongside its `file`/`path` entries, renamed `steps:` under it: + +```yaml +test: + rewrite: + type: functional + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - path: rewrite +``` + +A bare list (no `type:`/`steps:` wrapper) still works and is treated as `functional` — this is every test written before this typing existed, and `functional` (real cluster, real assertions, no vendor dependency) is what all of them already do. You only need the `type:`/`steps:` form to declare something other than `functional`. + +| Type | What it checks | Needs | Blocks the PR? | +|---|---|---|---| +| `schema` | The doc's own example CR validates against the real CRD's OpenAPI schema (renamed/removed/mistyped fields, wrong types) | Nothing — no cluster, no execution. See `doc_test_schema_check.py`. | Yes, in its own job (`schema-check`) | +| `functional` | Real behavior in a real cluster (apply config, assert on a real response) | A `kind` cluster, no vendor credentials | Yes | +| `live` | Same as functional, but a real external endpoint is reachable and returns the documented unauthenticated response (e.g. a JWKS discovery endpoint rejecting an unauthenticated request) | A real public endpoint, no credentials | Yes — runs in the same job as `functional` | +| `credentialed` | Full behavior against a real vendor with real credentials (e.g. a real OpenAI key) | Named secrets, provisioned out of band | No — runs on its own daily schedule via `--types credentialed`, `continue-on-error: true`, never on a PR | + +`schema` has no `steps:` prerequisite chain to trace — since nothing executes, it only needs the one step that shows the CR itself (the declaring page, or wherever the example lives): + +```yaml +test: + rewrite-schema: + type: schema + steps: + - path: rewrite # file: omitted -> the declaring page +``` + +`doc_test_run.py --list-tests` and every generated report include each case's `type`. Filter what runs with `--types schema,functional,live` (comma-separated); omit it to run everything, which is what a plain local run still does. + +**A known gap, not a limitation of the typing mechanism itself:** some enterprise-only pages (Entra token exchange, for one) can't reach `live` today because there's no shared dev tenant to test against — registering a real Entra app/tenant is manual, human, one-time setup with no vendor-provided sandbox. That test stays tagged at whatever type it can actually reach, with the gap noted in its front matter or the tracking issue, rather than silently passing at a narrower type than the page's own content would suggest. + +#### Declaring more than one type on the same scenario + +`type:` also accepts a list, so one `steps:` chain gets validated more than one way without +copy-pasting the whole scenario into a second one just to change its type: + +```yaml +test: + rewrite: + type: [schema, functional] + steps: + - file: ${versionRoot}/quickstart/install.md + path: experimental + - path: rewrite +``` + +This produces two independent test cases — `rewrite::schema` and `rewrite::functional` — +sharing the same `steps:`, each flowing into the pipeline exactly as if you'd hand-written +two separate scenarios (`rewrite-schema` and `rewrite`). `--test rewrite` selects both; +`--test rewrite::schema` selects only the schema one. A single-type scenario is unaffected +— its name, generated filenames, and report key stay exactly as before; the `name::type` +suffix only appears once a scenario declares more than one type. + +This is the recommended way to add schema coverage to an existing `functional` scenario, +rather than duplicating it into a same-named `-schema` sibling — **but only when the +scenario's final step is a recognized custom-resource kind with a local CRD schema** +(currently `AgentgatewayPolicy`/`AgentgatewayBackend` — see `doc_test_schema_check.py`). +A scenario whose example is a plain Gateway API resource (`HTTPRoute`, `Gateway`) has +nothing to validate against and would only ever pass vacuously, so `schema` isn't added +automatically to every scenario — it has to be requested per scenario, once its CR kind +is confirmed to have a schema to check. + --- ## Tracing prerequisites @@ -298,10 +420,16 @@ EOF ## Running the tests +The scripts themselves live in the `docs-tests` repo, not here (see [External test +content in docs-tests](#external-test-content-in-docs-tests)) — the commands below +assume it's cloned as a sibling directory (`../docs-tests`), same as the +`DOCS_TESTS_ROOT` default. `make test-generate` / `make test-run` wrap these same +commands and respect a `DOCS_TESTS_DIR` override if you've cloned it elsewhere. + ### Generate scripts only (no cluster) ```sh -python3 scripts/doc_test_run.py --generate-only +python3 ../docs-tests/scripts/doc_test_run.py --repo-root . --generate-only ``` Scripts are written to `out/tests/generated/`. @@ -311,7 +439,7 @@ Scripts are written to `out/tests/generated/`. Requires `kind` and `cloud-provider-kind` in PATH. ```sh -python3 scripts/doc_test_run.py +python3 ../docs-tests/scripts/doc_test_run.py --repo-root . ``` Each test scenario: @@ -335,19 +463,19 @@ Point directly to a file and (optionally) a named scenario. This generates the s ```sh # Run one specific scenario -python3 scripts/doc_test_run.py \ +python3 ../docs-tests/scripts/doc_test_run.py --repo-root . \ --file content/docs/kubernetes/main/security/cors.md \ --test cors-in-httproute # Run all scenarios defined in a single file -python3 scripts/doc_test_run.py \ +python3 ../docs-tests/scripts/doc_test_run.py --repo-root . \ --file content/docs/kubernetes/main/security/cors.md ``` To only generate the script without running (useful for inspection): ```sh -python3 scripts/doc_test_run.py \ +python3 ../docs-tests/scripts/doc_test_run.py --repo-root . \ --file content/docs/kubernetes/main/security/cors.md \ --test cors-in-httproute \ --generate-only @@ -366,6 +494,8 @@ bash ../../generated/.sh | `--test` | — | Name of a specific test scenario within `--file` | | `--docs-glob` | `content/docs/**/*.md` | Glob to discover pages with `test:` metadata (ignored when `--file` is set) | | `--product` | `kubernetes` | Context product used for `conditional-text` resolution | +| `--types` | all types | Comma-separated test types to run, e.g. `schema,functional,live`. See [Typing scenarios](#typing-scenarios) | +| `--docs-tests-root` | sibling `docs-tests` dir | Checkout root for `{{< doc-test file="..." >}}` external content (or `DOCS_TESTS_ROOT` env var). See [External test content in docs-tests](#external-test-content-in-docs-tests) | | `--generated-dir` | `out/tests/generated` | Output directory for scripts and manifests | | `--generate-only` | false | Skip cluster creation and execution | | `--verbose` | true | Stream all command output | @@ -384,6 +514,8 @@ The `version` context (used to resolve `{{< version include-if="..." >}}` blocks - **Indentation is stripped** from fenced block content so heredocs work correctly in bash. - **Duplicate blocks** (same content) are emitted only once. - Blocks without a `paths=` attribute are skipped. +- **`{{< doc-test file="..." >}}`** — content is read from the `docs-tests` checkout instead of the shortcode body (see [External test content in docs-tests](#external-test-content-in-docs-tests)). +- **A step's `assert:` list** — synthesizes the same kind of hidden block directly from front matter, anchored to the first block in that file sharing the step's `paths=` selector (see above). --- @@ -472,7 +604,7 @@ make test-artifacts-fetch If you run into issues with installing yamltest, include the `--force` flag. -On macOS, you might need to run either the `python3 scripts/doc_test_run.py` command with `sudo`, or run `sudo cloud-provider-kind --gateway-channel=disabled` in a separate tab before running the tests. In macOS, the cloud-provider-kind tool to get a LoadBalancer IP requires elevated permissions. +On macOS, you might need to run either the `doc_test_run.py` command with `sudo`, or run `sudo cloud-provider-kind --gateway-channel=disabled` in a separate tab before running the tests. In macOS, the cloud-provider-kind tool to get a LoadBalancer IP requires elevated permissions. ### Common issues diff --git a/scripts/doc_test_extract.py b/scripts/doc_test_extract.py deleted file mode 100644 index 4474a3ddd..000000000 --- a/scripts/doc_test_extract.py +++ /dev/null @@ -1,668 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import json -import re -import textwrap -from dataclasses import dataclass, field -from pathlib import Path -from typing import Dict, List, Optional, Set, Tuple - -try: - import yaml as _yaml # type: ignore[import-not-found] -except ModuleNotFoundError: - _yaml = None - - -SHELL_LANGS = {"", "sh", "bash", "shell", "zsh", "yaml", "yml"} - - -@dataclass -class CodeBlock: - file_path: Path - start_line: int - language: str - paths: List[str] - content: str - hidden: bool = False - - -@dataclass -class TestInclude: - file_path: Path - start_line: int - paths: List[str] - test_file: Path - - -@dataclass -class FileResult: - file_path: Path - expanded_text: str - code_blocks: List[CodeBlock] = field(default_factory=list) - test_includes: List[TestInclude] = field(default_factory=list) - links: List[str] = field(default_factory=list) - - -def _load_link_version_map(repo_root: Path) -> Dict[str, str]: - """Build a {linkVersion: version} mapping from hugo.yaml's params.sections. - - Hugo's version shortcode resolves URL tokens like "latest" or "main" to - their canonical version strings (e.g. "2.2.x", "1.0.x") via this mapping. - The Python extractor must perform the same lookup so that include-if - comparisons work correctly. - - Returns an empty dict if hugo.yaml is missing or cannot be parsed. - """ - if _yaml is None: - return {} - hugo_yaml = repo_root / "hugo.yaml" - if not hugo_yaml.exists(): - hugo_yaml = repo_root / "config.yaml" - if not hugo_yaml.exists(): - return {} - try: - data = _yaml.safe_load(hugo_yaml.read_text(encoding="utf-8")) or {} - except Exception: - return {} - mapping: Dict[str, str] = {} - sections = data.get("params", {}).get("sections", {}) - for section_data in sections.values(): - for entry in section_data.get("versions", []): - link_ver = entry.get("linkVersion") - ver = entry.get("version") - if link_ver and ver: - mapping[link_ver] = ver - return mapping - - -class Extractor: - def __init__(self, repo_root: Path, definition: dict): - self.repo_root = repo_root - self.definition = definition - options = definition.get("options", {}) - self.follow_reuse = bool(options.get("follow_reuse", True)) - self.follow_include = bool(options.get("follow_include", True)) - self.follow_internal_links = bool(options.get("follow_internal_links", True)) - self.max_depth = int(options.get("max_depth", 8)) - self.skip_tabs_without_paths = bool(options.get("skip_tabs_without_paths", True)) - self.version = definition.get("context", {}).get("version") - self.product = definition.get("context", {}).get("product") - - # Map linkVersion tokens (e.g. "latest", "main") to canonical version - # strings (e.g. "2.2.x", "1.0.x") as defined in hugo.yaml. This - # mirrors what Hugo's version.html shortcode does at build time so that - # include-if comparisons resolve correctly. - self._link_version_map = _load_link_version_map(repo_root) - # Resolve the context version token to its canonical version string once. - self._resolved_version = self._link_version_map.get(self.version, self.version) if self.version else self.version - - self.sources = definition.get("sources", []) - self.main_file = self._resolve_workspace_path(definition["main_file"]) - - self.path_selectors_by_file: Dict[Path, Set[str]] = {} - for source in self.sources: - source_file = self._resolve_workspace_path(source["file"]) - selectors = set(source.get("paths", [])) - if source_file in self.path_selectors_by_file: - self.path_selectors_by_file[source_file].update(selectors) - else: - self.path_selectors_by_file[source_file] = selectors - - self.file_cache: Dict[Path, FileResult] = {} - self.visited: Set[Path] = set() - self.recursion_edges: List[Tuple[str, str, str]] = [] - - # Memoization caches for link resolution. _route_for_file and - # _find_by_route_like_target are pure with respect to the (static) - # all_markdown_files set, but get called once per link per visited - # file. Without caching, walk() is ~O(links x files^2) of filesystem - # stat calls, which is pathologically slow for pages that link into - # densely cross-linked hub pages. - self._route_cache: Dict[Path, Optional[str]] = {} - self._target_cache: Dict[str, Optional[Path]] = {} - - self.all_markdown_files = [ - p - for p in self.repo_root.rglob("*.md") - if "/public/" not in p.as_posix() and "/resources/" not in p.as_posix() - ] - - def _resolve_workspace_path(self, path_value: str) -> Path: - path = Path(path_value) - if path.is_absolute(): - return path.resolve() - return (self.repo_root / path).resolve() - - def _read_file(self, path: Path) -> str: - return path.read_text(encoding="utf-8") - - def _parse_shortcode_params(self, params: str) -> Dict[str, str]: - return {m.group(1): m.group(2) for m in re.finditer(r'([\w-]+)="([^"]*)"', params)} - - def _evaluate_version_block(self, params: str) -> bool: - if not self.version: - return True - kv = self._parse_shortcode_params(params) - include_if = [x.strip() for x in kv.get("include-if", "").split(",") if x.strip()] - exclude_if = [x.strip() for x in kv.get("exclude-if", "").split(",") if x.strip()] - # Identifiers that represent the current version. We accept both the - # canonical version string (e.g. "2.2.x") AND the stable linkVersion - # token (e.g. "main"/"latest"). This lets include-if/exclude-if target a - # directory by its release-stable name ("main") instead of the version - # number, which rotates every release. Matching the canonical string - # still works regardless of whether context.version was supplied as a - # linkVersion token or directly as a version string. Mirrors the - # linkVersion matching in the Hugo version.html shortcode. - identifiers = {self.version, self._resolved_version} - for link_ver, ver in self._link_version_map.items(): - if ver == self._resolved_version: - identifiers.add(link_ver) - identifiers.discard(None) - if include_if and not identifiers.intersection(include_if): - return False - if exclude_if and identifiers.intersection(exclude_if): - return False - return True - - def _strip_version_blocks(self, text: str) -> str: - pattern = re.compile(r"\{\{[<%]\s*version\s*([^%>]*)[ \t]*[>%]\}\}(.*?)\{\{[<%]\s*/version\s*[>%]\}\}", re.DOTALL) - - def repl(match: re.Match) -> str: - params = match.group(1) or "" - body = match.group(2) or "" - return body if self._evaluate_version_block(params) else "" - - return pattern.sub(repl, text) - - def _resolve_reuse(self, source_file: Path, asset_path: str, depth: int) -> str: - if depth > self.max_depth: - return "" - trimmed = asset_path.lstrip("/") - candidate = (self.repo_root / "assets" / trimmed).resolve() - if not candidate.exists(): - return "" - self.recursion_edges.append((source_file.as_posix(), candidate.as_posix(), "reuse")) - return self._expand_text(candidate, self._read_file(candidate), depth + 1).rstrip("\n") - - def _resolve_include(self, source_file: Path, include_path: str, depth: int) -> str: - if depth > self.max_depth: - return "" - rel = include_path.strip().strip("\"") - rel = rel.strip("/") - candidates = [] - include_candidate = self.repo_root / "content" / rel - if include_candidate.suffix == ".md": - candidates.append(include_candidate) - else: - candidates.extend([include_candidate.with_suffix(".md"), include_candidate / "_index.md"]) - - for candidate in candidates: - if candidate.exists(): - resolved = candidate.resolve() - self.recursion_edges.append((source_file.as_posix(), resolved.as_posix(), "include")) - return self._expand_text(resolved, self._read_file(resolved), depth + 1) - return "" - - def _expand_text(self, source_file: Path, text: str, depth: int = 0) -> str: - if depth > self.max_depth: - return text - - text = self._strip_version_blocks(text) - - def replace_reuse(match: re.Match) -> str: - shortcode = match.group(1) - asset_path = match.group(2) - if shortcode not in {"reuse", "reuse-append"} or not self.follow_reuse: - return match.group(0) - return self._resolve_reuse(source_file, asset_path, depth) - - text = re.sub(r"\{\{[<%]\s*(reuse|reuse-append)\s+\"([^\"]+)\"\s*[>%]\}\}", replace_reuse, text) - - def replace_include(match: re.Match) -> str: - include_path = match.group(1) - if not self.follow_include: - return match.group(0) - return self._resolve_include(source_file, include_path, depth) - - text = re.sub(r"\{\{[%<]\s*include\s+\"([^\"]+)\"\s*[%>]\}\}", replace_include, text) - - def replace_link_hextra(match: re.Match) -> str: - return match.group(1) - - text = re.sub(r"\{\{<\s*link-hextra\s+path=\"([^\"]+)\"\s*>\}\}", replace_link_hextra, text) - - text = self._resolve_conditional_text_blocks(text) - - return text - - def _resolve_conditional_text_blocks(self, text: str) -> str: - pattern = re.compile( - r"\{\{[<%]\s*conditional-text\s*([^%>]*)[ \t]*[>%]\}\}(.*?)\{\{[<%]\s*/conditional-text\s*[>%]\}\}", - re.DOTALL, - ) - - def repl(match: re.Match) -> str: - params = self._parse_shortcode_params(match.group(1) or "") - body = match.group(2) or "" - include_if = [x.strip() for x in params.get("include-if", "").split(",") if x.strip()] - exclude_if = [x.strip() for x in params.get("exclude-if", "").split(",") if x.strip()] - - if include_if: - if not self.product: - return "" - if self.product not in include_if: - return "" - - if exclude_if and self.product and self.product in exclude_if: - return "" - - return body - - return pattern.sub(repl, text) - - def _extract_links(self, text: str) -> List[str]: - found = [] - for match in re.finditer(r"\[[^\]]+\]\(([^)]+)\)", text): - target = match.group(1).strip() - if not target: - continue - if target.startswith(("http://", "https://", "mailto:", "#")): - continue - target = target.split("#", 1)[0] - if target: - found.append(target) - return found - - def _find_by_route_like_target(self, target: str) -> Optional[Path]: - if target in self._target_cache: - return self._target_cache[target] - result = self._find_by_route_like_target_uncached(target) - self._target_cache[target] = result - return result - - def _find_by_route_like_target_uncached(self, target: str) -> Optional[Path]: - stripped = target.strip().strip("/") - if not stripped: - return None - - direct_candidates = [ - self.repo_root / "content" / stripped, - self.repo_root / "assets" / stripped, - self.repo_root / "content" / f"{stripped}.md", - self.repo_root / "assets" / f"{stripped}.md", - self.repo_root / "content" / stripped / "_index.md", - self.repo_root / "assets" / stripped / "_index.md", - self.repo_root / "content" / "docs" / stripped / "_index.md", - self.repo_root / "content" / "docs" / f"{stripped}.md", - self.repo_root / "assets" / "agw-docs" / "pages" / f"{stripped}.md", - self.repo_root / "assets" / "agw-docs" / "pages" / stripped / "_index.md", - ] - for candidate in direct_candidates: - if candidate.is_file(): - return candidate.resolve() - - last_segment = stripped.split("/")[-1] - route_suffix = f"/{stripped}/" - - best: Optional[Path] = None - best_score: Optional[int] = None - for file_path in self.all_markdown_files: - route_guess = self._route_for_file(file_path) - if route_guess and route_guess.endswith(route_suffix): - score = len(file_path.as_posix()) - elif file_path.stem == last_segment or (file_path.name == "_index.md" and file_path.parent.name == last_segment): - score = len(file_path.as_posix()) + 1000 - else: - continue - - if best is None or score < best_score: - best = file_path.resolve() - best_score = score - - return best - - def _route_for_file(self, file_path: Path) -> Optional[str]: - if file_path in self._route_cache: - return self._route_cache[file_path] - route = self._route_for_file_uncached(file_path) - self._route_cache[file_path] = route - return route - - def _route_for_file_uncached(self, file_path: Path) -> Optional[str]: - p = file_path.resolve() - if (self.repo_root / "content") in p.parents: - rel = p.relative_to(self.repo_root / "content").as_posix() - if rel.endswith("/_index.md"): - rel = rel[: -len("/_index.md")] - elif rel.endswith(".md"): - rel = rel[: -len(".md")] - return "/" + rel.strip("/") + "/" - if (self.repo_root / "assets" / "agw-docs" / "pages") in p.parents: - rel = p.relative_to(self.repo_root / "assets" / "agw-docs" / "pages").as_posix() - if rel.endswith("/_index.md"): - rel = rel[: -len("/_index.md")] - elif rel.endswith(".md"): - rel = rel[: -len(".md")] - return "/" + rel.strip("/") + "/" - return None - - def _extract_code_blocks(self, source_file: Path, text: str) -> List[CodeBlock]: - blocks: List[CodeBlock] = [] - lines = text.splitlines() - i = 0 - while i < len(lines): - line = lines[i] - open_match = re.match(r"^\s*(`{3,})(.*)$", line) - if not open_match: - i += 1 - continue - - fence = open_match.group(1) - info = (open_match.group(2) or "").strip() - start_line = i + 1 - lang = "" - if info: - lang = re.split(r"[,{\s]", info, maxsplit=1)[0].strip().lower() - - j = i + 1 - content_lines = [] - close_pattern = re.compile(rf"^\s*`{{{len(fence)},}}\s*$") - while j < len(lines) and not close_pattern.match(lines[j]): - content_lines.append(lines[j]) - j += 1 - - attrs = info - path_match = re.search(r'paths\s*=\s*"([^"]+)"', attrs) - paths = [] - if path_match: - paths = [x.strip() for x in path_match.group(1).split(",") if x.strip()] - - content = "\n".join(content_lines) - content = textwrap.dedent(content).rstrip() + "\n" - - blocks.append( - CodeBlock( - file_path=source_file, - start_line=start_line, - language=lang, - paths=paths, - content=content, - ) - ) - - i = j + 1 if j < len(lines) else j - return blocks - - def _extract_hidden_shell_blocks(self, source_file: Path, text: str) -> List[CodeBlock]: - blocks: List[CodeBlock] = [] - # Format: {{< doc-test paths="..." >}}\nBODY\n{{< /doc-test >}} - # An empty shortcode template (layouts/shortcodes/doc-test.html) causes - # Hugo to emit nothing for these blocks, keeping tests out of HTML output. - pattern = re.compile(r"\{\{<\s*doc-test\b([^>]*?)>\}\}(.*?)\{\{<\s*/doc-test\s*>\}\}", re.DOTALL) - - for match in pattern.finditer(text): - attrs = match.group(1) or "" - body = match.group(2) or "" - params = self._parse_shortcode_params(attrs) - paths = [x.strip() for x in params.get("paths", "").split(",") if x.strip()] - start_line = text[: match.start()].count("\n") + 1 - - content = textwrap.dedent(body).strip("\n") - if not content: - continue - - blocks.append( - CodeBlock( - file_path=source_file, - start_line=start_line, - language="sh", - paths=paths, - content=content + "\n", - hidden=True, - ) - ) - - return blocks - - def _extract_test_includes(self, source_file: Path, text: str) -> List[TestInclude]: - includes: List[TestInclude] = [] - pattern = re.compile(r"") - - for match in pattern.finditer(text): - attrs = match.group(1) or "" - params = self._parse_shortcode_params(attrs) - test_file_value = params.get("file") - if not test_file_value: - continue - - include_path = Path(test_file_value) - test_file = include_path if include_path.is_absolute() else (source_file.parent / include_path) - test_file = test_file.resolve() - - paths = [x.strip() for x in params.get("paths", "").split(",") if x.strip()] - start_line = text[: match.start()].count("\n") + 1 - - includes.append( - TestInclude( - file_path=source_file, - start_line=start_line, - paths=paths, - test_file=test_file, - ) - ) - - return includes - - def process_file(self, file_path: Path, depth: int = 0) -> FileResult: - file_path = file_path.resolve() - if file_path in self.file_cache: - return self.file_cache[file_path] - - raw = self._read_file(file_path) - expanded = self._expand_text(file_path, raw, depth) - code_blocks = self._extract_code_blocks(file_path, expanded) - code_blocks.extend(self._extract_hidden_shell_blocks(file_path, expanded)) - test_includes = self._extract_test_includes(file_path, expanded) - links = self._extract_links(expanded) - - result = FileResult( - file_path=file_path, - expanded_text=expanded, - code_blocks=code_blocks, - test_includes=test_includes, - links=links, - ) - self.file_cache[file_path] = result - return result - - def walk(self) -> None: - queue: List[Tuple[Path, int]] = [(self.main_file, 0)] - for source_file in self.path_selectors_by_file: - queue.append((source_file, 0)) - - while queue: - file_path, depth = queue.pop(0) - file_path = file_path.resolve() - if file_path in self.visited: - continue - if depth > self.max_depth: - continue - self.visited.add(file_path) - - result = self.process_file(file_path, depth) - if not self.follow_internal_links: - continue - - for link in result.links: - linked = self._find_by_route_like_target(link) - if linked and linked.is_file() and linked not in self.visited: - self.recursion_edges.append((file_path.as_posix(), linked.as_posix(), "link")) - queue.append((linked, depth + 1)) - - def select_blocks(self) -> List[CodeBlock]: - selected: List[CodeBlock] = [] - # Preserve source order (helm -> gateway -> sample-app -> feature) so that - # e.g. the Gateway is created before the HTTPRoute that references it. - source_order = {p.resolve(): i for i, p in enumerate(self.path_selectors_by_file.keys())} - for source_file, selectors in self.path_selectors_by_file.items(): - result = self.file_cache.get(source_file.resolve()) - if not result: - continue - for block in result.code_blocks: - if self.skip_tabs_without_paths and not block.paths: - continue - if block.language not in SHELL_LANGS: - continue - if selectors and ("all" in selectors or "all" in block.paths or set(block.paths).intersection(selectors)): - selected.append(block) - # Emit blocks in source order (prereqs first), then by line within each file, - # so hidden blocks (e.g. start server in background) appear before dependent blocks. - def sort_key(b: CodeBlock) -> Tuple[int, int]: - idx = source_order.get(b.file_path.resolve(), 999) - return (idx, b.start_line) - - selected.sort(key=sort_key) - #selected.sort(key=lambda b: (b.file_path, b.start_line)) - return selected - - def select_test_includes(self) -> List[TestInclude]: - selected: List[TestInclude] = [] - for source_file, selectors in self.path_selectors_by_file.items(): - result = self.file_cache.get(source_file.resolve()) - if not result: - continue - for test_include in result.test_includes: - if self.skip_tabs_without_paths and not test_include.paths: - continue - if selectors and ("all" in selectors or "all" in test_include.paths or set(test_include.paths).intersection(selectors)): - selected.append(test_include) - return selected - - def build_script(self, blocks: List[CodeBlock], test_includes: List[TestInclude]) -> str: - lines = ["#!/usr/bin/env bash", "set -euo pipefail", ""] - seen = set() - for block in blocks: - content = block.content.strip("\n") - if not content: - continue - if content in seen: - continue - seen.add(content) - rel = block.file_path.relative_to(self.repo_root).as_posix() - if block.hidden: - lines.append(f"# Hidden source: {rel}:{block.start_line} paths={','.join(block.paths)}") - else: - lines.append(f"# Source: {rel}:{block.start_line} paths={','.join(block.paths)}") - lines.append(content) - lines.append("") - - seen_tests = set() - for test_include in test_includes: - rel_source = test_include.file_path.relative_to(self.repo_root).as_posix() - rel_test = test_include.test_file.relative_to(self.repo_root).as_posix() - if rel_test in seen_tests: - continue - seen_tests.add(rel_test) - if not test_include.test_file.exists(): - raise FileNotFoundError( - f"doc-test-include file not found: {rel_test} (from {rel_source}:{test_include.start_line})" - ) - lines.append( - f"# Test include: {rel_source}:{test_include.start_line} paths={','.join(test_include.paths)} file={rel_test}" - ) - lines.append(f"bun test {rel_test}") - lines.append("") - return "\n".join(lines).rstrip() + "\n" - - def build_manifest(self, blocks: List[CodeBlock], test_includes: List[TestInclude]) -> dict: - discovered_paths_by_file = {} - for path, result in self.file_cache.items(): - paths = sorted({p for b in result.code_blocks for p in b.paths} | {p for t in result.test_includes for p in t.paths}) - discovered_paths_by_file[path.relative_to(self.repo_root).as_posix()] = paths - - return { - "name": self.definition.get("name"), - "main_file": self.main_file.relative_to(self.repo_root).as_posix(), - "sources": self.sources, - "options": self.definition.get("options", {}), - "context": self.definition.get("context", {}), - "visited_files": sorted([p.relative_to(self.repo_root).as_posix() for p in self.visited]), - "recursion_edges": [ - { - "from": Path(src).relative_to(self.repo_root).as_posix() if Path(src).is_absolute() else src, - "to": Path(dst).relative_to(self.repo_root).as_posix() if Path(dst).is_absolute() else dst, - "kind": kind, - } - for src, dst, kind in self.recursion_edges - ], - "discovered_paths_by_file": discovered_paths_by_file, - "selected_blocks": [ - { - "file": b.file_path.relative_to(self.repo_root).as_posix(), - "line": b.start_line, - "language": b.language, - "paths": b.paths, - "hidden": b.hidden, - "preview": b.content.splitlines()[0] if b.content.splitlines() else "", - } - for b in blocks - ], - "selected_test_includes": [ - { - "file": t.file_path.relative_to(self.repo_root).as_posix(), - "line": t.start_line, - "paths": t.paths, - "test_file": t.test_file.relative_to(self.repo_root).as_posix(), - } - for t in test_includes - ], - "selected_count": len(blocks), - "selected_test_count": len(test_includes), - } - - -def main() -> int: - parser = argparse.ArgumentParser(description="Generate executable doc test scripts from markdown path selectors.") - parser.add_argument("--definition", required=True, help="Path to JSON test definition file") - parser.add_argument("--repo-root", default=".", help="Workspace root") - parser.add_argument("--output-script", help="Override output script path") - parser.add_argument("--output-manifest", help="Override output manifest path") - args = parser.parse_args() - - repo_root = Path(args.repo_root).resolve() - definition_path = (repo_root / args.definition).resolve() if not Path(args.definition).is_absolute() else Path(args.definition) - - definition = json.loads(definition_path.read_text(encoding="utf-8")) - extractor = Extractor(repo_root=repo_root, definition=definition) - extractor.walk() - - blocks = extractor.select_blocks() - test_includes = extractor.select_test_includes() - script = extractor.build_script(blocks, test_includes) - manifest = extractor.build_manifest(blocks, test_includes) - - output_script = args.output_script or definition.get("output", {}).get("script") - output_manifest = args.output_manifest or definition.get("output", {}).get("manifest") - - if not output_script or not output_manifest: - raise ValueError("Definition must provide output.script and output.manifest, or pass CLI overrides") - - script_path = (repo_root / output_script).resolve() - manifest_path = (repo_root / output_manifest).resolve() - script_path.parent.mkdir(parents=True, exist_ok=True) - manifest_path.parent.mkdir(parents=True, exist_ok=True) - - script_path.write_text(script, encoding="utf-8") - manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") - - print(f"Wrote script: {script_path.relative_to(repo_root)}") - print(f"Wrote manifest: {manifest_path.relative_to(repo_root)}") - print(f"Selected blocks: {len(blocks)}") - print(f"Selected tests: {len(test_includes)}") - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/doc_test_fetch_artifacts.sh b/scripts/doc_test_fetch_artifacts.sh deleted file mode 100755 index 1aadf6f78..000000000 --- a/scripts/doc_test_fetch_artifacts.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash -# Build script that fetches test results and injects test status before Hugo build. -# Used by Vercel during deployment. - -set -e - -REPO="agentgateway/website" -ARTIFACT_NAME="doc-test-results" -RESULTS_DIR="out/tests/generated" -RESULTS_FILE="$RESULTS_DIR/test-results.yaml" - -# Check if results directory already exists with results file -if [ -f "$RESULTS_FILE" ]; then - echo "=== Test results already exist at $RESULTS_FILE, skipping fetch ===" - exit 0 -fi - -echo "=== Fetching latest doc test results ===" - -# Create output directory -mkdir -p "$RESULTS_DIR" - -# Check if we have a GitHub token for API access -if [ -n "$GITHUB_TOKEN" ]; then - echo "Fetching artifact list from GitHub API..." - - # Get the latest completed workflow run on main branch - RUN_INFO=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ - "https://api.github.com/repos/$REPO/actions/workflows/doc-tests.yaml/runs?branch=main&status=completed&per_page=1") - - # Find the most recent completed run on main - RUN_ID=$(echo "$RUN_INFO" | python3 -c " -import sys, json -data = json.load(sys.stdin) -for run in data.get('workflow_runs', []): - if run.get('head_branch') == 'main': - print(run['id']) - break -" 2>/dev/null || echo "") - - if [ -n "$RUN_ID" ]; then - echo "Found workflow run: $RUN_ID" - - # Get artifacts for this run - ARTIFACTS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ - "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts") - - ARTIFACT_URL=$(echo "$ARTIFACTS" | python3 -c " -import sys, json -data = json.load(sys.stdin) -for artifact in data.get('artifacts', []): - if artifact.get('name') == '$ARTIFACT_NAME': - print(artifact['archive_download_url']) - break -" 2>/dev/null || echo "") - - if [ -n "$ARTIFACT_URL" ]; then - echo "Downloading artifact..." - curl -s -L -H "Authorization: token $GITHUB_TOKEN" \ - -o artifact.zip "$ARTIFACT_URL" - - if [ -f artifact.zip ]; then - unzip -o -q artifact.zip -d "$RESULTS_DIR" - rm artifact.zip - echo "Artifact extracted to $RESULTS_DIR" - fi - else - echo "Warning: No $ARTIFACT_NAME artifact found in run $RUN_ID" - fi - else - echo "Warning: No completed workflow runs found" - fi -else - echo "Warning: GITHUB_TOKEN not set, skipping artifact download" -fi - diff --git a/scripts/doc_test_inject_status.py b/scripts/doc_test_inject_status.py deleted file mode 100644 index 73286cb46..000000000 --- a/scripts/doc_test_inject_status.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env python3 -""" -Inject test status metadata into markdown front matter based on test-results.yaml. - -This script reads the test results file and updates the front matter of each -tested document with a `test_status` field indicating whether all tests passed. - -Usage: - python3 scripts/inject_test_status.py [--results-file PATH] [--dry-run] -""" - -import argparse -import re -from pathlib import Path -from typing import Dict, List, Optional - -try: - import yaml -except ModuleNotFoundError: - yaml = None - - -def load_test_results(results_path: Path) -> Dict: - """Load test results from YAML file.""" - if yaml is None: - raise RuntimeError("PyYAML is required. Install it with: pip install pyyaml") - - if not results_path.exists(): - print(f"Warning: Test results file not found: {results_path}") - return {"tested_documents": [], "tests": {}} - - return yaml.safe_load(results_path.read_text(encoding="utf-8")) or {} - - -def compute_document_status(doc_path: str, tests: Dict) -> Optional[str]: - """ - Compute the overall test status for a document. - - Returns: - "passed" if all tests for this document passed - "failed" if any test failed - None if no tests exist for this document - """ - doc_tests = {k: v for k, v in tests.items() if k.startswith(f"{doc_path}::")} - - if not doc_tests: - return None - - all_passed = all(t.get("status") == "passed" for t in doc_tests.values()) - return "passed" if all_passed else "failed" - - -def parse_front_matter(content: str) -> tuple[Optional[str], Optional[Dict], str]: - """ - Parse YAML front matter from markdown content. - - Returns: - Tuple of (raw_front_matter, parsed_dict, body_content) - """ - if yaml is None: - raise RuntimeError("PyYAML is required. Install it with: pip install pyyaml") - - match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL) - if not match: - return None, None, content - - raw_fm = match.group(1) - body = content[match.end():] - - try: - parsed = yaml.safe_load(raw_fm) - if not isinstance(parsed, dict): - parsed = {} - except yaml.YAMLError: - parsed = {} - - return raw_fm, parsed, body - - -def update_front_matter(content: str, test_status: Optional[str]) -> str: - """ - Update the front matter with test_status field. - - If test_status is None, removes any existing test_status field. - """ - if yaml is None: - raise RuntimeError("PyYAML is required. Install it with: pip install pyyaml") - - raw_fm, parsed_fm, body = parse_front_matter(content) - - if raw_fm is None or parsed_fm is None: - return content - - if test_status is None: - if "test_status" in parsed_fm: - del parsed_fm["test_status"] - else: - parsed_fm["test_status"] = test_status - - new_fm = yaml.safe_dump(parsed_fm, sort_keys=False, default_flow_style=False, allow_unicode=True, width=1000) - new_fm = new_fm.rstrip("\n") - - return f"---\n{new_fm}\n---\n{body}" - - -def process_documents( - repo_root: Path, - results: Dict, - dry_run: bool = False, - verbose: bool = True -) -> Dict[str, str]: - """ - Process all tested documents and update their front matter. - - Returns: - Dict mapping document paths to their test status - """ - tested_docs = results.get("tested_documents", []) - tests = results.get("tests", {}) - - status_map: Dict[str, str] = {} - - for doc_path in tested_docs: - full_path = repo_root / doc_path - - if not full_path.exists(): - if verbose: - print(f"Warning: Document not found: {doc_path}") - continue - - status = compute_document_status(doc_path, tests) - - if status is None: - if verbose: - print(f"Skipping {doc_path}: no test results") - continue - - status_map[doc_path] = status - - if verbose: - icon = "✓" if status == "passed" else "✗" - print(f"{icon} {doc_path}: {status}") - - if dry_run: - continue - - content = full_path.read_text(encoding="utf-8") - updated = update_front_matter(content, status) - - if content != updated: - full_path.write_text(updated, encoding="utf-8") - - return status_map - - -def main() -> int: - parser = argparse.ArgumentParser( - description="Inject test status metadata into markdown front matter." - ) - parser.add_argument( - "--repo-root", - default=".", - help="Repository root directory (default: current directory)" - ) - parser.add_argument( - "--results-file", - default="out/tests/generated/test-results.yaml", - help="Path to test results YAML file" - ) - parser.add_argument( - "--dry-run", - action="store_true", - help="Show what would be done without making changes" - ) - parser.add_argument( - "--quiet", - action="store_true", - help="Suppress verbose output" - ) - args = parser.parse_args() - - if yaml is None: - print("Error: PyYAML is required. Install it with: pip install pyyaml") - return 1 - - repo_root = Path(args.repo_root).resolve() - results_path = repo_root / args.results_file - - if not results_path.exists(): - print(f"Results file not found: {results_path}") - print("Skipping test status injection.") - return 0 - - if args.dry_run: - print("=== DRY RUN MODE ===\n") - - results = load_test_results(results_path) - status_map = process_documents( - repo_root, - results, - dry_run=args.dry_run, - verbose=not args.quiet - ) - - passed = sum(1 for s in status_map.values() if s == "passed") - failed = sum(1 for s in status_map.values() if s == "failed") - - print(f"\nSummary: {len(status_map)} documents processed, {passed} passed, {failed} failed") - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/doc_test_run.py b/scripts/doc_test_run.py deleted file mode 100644 index 0b7a28a98..000000000 --- a/scripts/doc_test_run.py +++ /dev/null @@ -1,819 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import json -import logging -import os -import re -import shutil -import subprocess -import sys -import tempfile -import time -from dataclasses import dataclass -from pathlib import Path -from typing import Dict, List, Optional, Tuple - -try: - import yaml # type: ignore[import-not-found] -except ModuleNotFoundError: - yaml = None - -from doc_test_extract import Extractor - -logger = logging.getLogger(__name__) - -DEFAULT_OPTIONS = { - "follow_reuse": True, - "follow_include": True, - "follow_internal_links": True, - "skip_tabs_without_paths": True, - "max_depth": 8, -} - - -@dataclass -class TestCase: - document: Path - name: str - sources: List[Dict[str, str]] - script_path: Path - manifest_path: Path - - -def parse_front_matter(markdown_path: Path) -> Dict: - if yaml is None: - raise RuntimeError("PyYAML is required. Install it with: pip install pyyaml") - - text = markdown_path.read_text(encoding="utf-8") - match = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL) - if not match: - return {} - front_matter = match.group(1) - data = yaml.safe_load(front_matter) or {} - if not isinstance(data, dict): - return {} - return data - - -def sanitize_name(value: str) -> str: - return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") - - -def infer_version_from_sources(sources: List[Dict[str, str]], fallback: str) -> str: - """Extract the link-version token (e.g. 'latest', 'main') from source file paths. - - Source files live under paths like: - content/docs/kubernetes/latest/install/helm.md - content/docs/kubernetes/main/security/cors.md - - The segment after the product directory (kubernetes/standalone) is the - link version used inside {{< version include-if="..." >}} blocks. - """ - pattern = re.compile(r"(?:kubernetes|standalone)/([^/]+)/") - for src in sources: - file_path = src.get("file", "") - m = pattern.search(file_path) - if m: - return m.group(1) - return fallback - - -def version_path_tokens(doc_rel_path: str) -> Dict[str, str]: - """Build the path-substitution tokens for a page's `test:` metadata. - - Derived from the declaring page's repo-relative path - (e.g. "content/docs/standalone/main/configuration/backends.md"): - - ${version} -> the version dir segment, e.g. "main" / "latest" - ${versionRoot} -> the prefix up to and including the version dir, - e.g. "content/docs/standalone/main" - - These let `file:` values reference paths without hardcoding the version - directory, which rotates every release. Resolution is anchored to the page - that declares the test, so a page copied from main/ to latest/ needs no - metadata edit. Entries that intentionally target another version just write - the literal path (no token). Returns an empty dict for paths that don't - match the content/docs/
// layout, leaving `file:` - unchanged. - - The ${...} syntax (not {...}) is deliberate: a YAML value starting with - "{" is parsed as a flow mapping, so a leading {versionRoot} would be a - parse error unless quoted. A leading "$" is a plain scalar, so ${versionRoot} - is valid unquoted at the start of a `file:` value. - """ - parts = doc_rel_path.replace("\\", "/").split("/") - try: - idx = parts.index("docs") - section = parts[idx + 1] - version = parts[idx + 2] - except (ValueError, IndexError): - return {} - if section not in ("kubernetes", "standalone"): - return {} - version_root = "/".join(parts[: idx + 3]) - return {"${versionRoot}": version_root, "${version}": version} - - -def build_test_cases_from_file( - repo_root: Path, - md_file: Path, - generated_dir: Path, - filter_test_name: Optional[str] = None, -) -> Tuple[List[TestCase], List[str]]: - """Build test cases from a single markdown file, optionally filtered to one test name.""" - test_cases: List[TestCase] = [] - tested_documents: List[str] = [] - - if not md_file.is_file(): - return test_cases, tested_documents - - metadata = parse_front_matter(md_file) - tests = metadata.get("test") - if tests == "skip": - tested_documents.append(md_file.relative_to(repo_root).as_posix()) - return test_cases, tested_documents - if not isinstance(tests, dict) or not tests: - return test_cases, tested_documents - - rel_doc = md_file.relative_to(repo_root).as_posix() - tested_documents.append(rel_doc) - - doc_slug = sanitize_name(str(md_file.relative_to(repo_root).with_suffix(""))) - for test_name, entries in tests.items(): - if not isinstance(test_name, str) or not test_name: - continue - if filter_test_name and test_name != filter_test_name: - continue - if not isinstance(entries, list): - continue - - sources: List[Dict[str, str]] = [] - tokens = version_path_tokens(rel_doc) - for entry in entries: - if not isinstance(entry, dict): - continue - source_path = entry.get("path") - if not source_path: - continue - # `file` defaults to the page that declares the test, and supports - # ${version}/${versionRoot} placeholders resolved against that page's - # path. This lets entries reference a path without hardcoding the - # version dir (which rotates every release), so a page copied from - # main/ to latest/ needs no metadata edit -- the version context is - # still inferred from the resolved path. Entries that intentionally - # point at another version (e.g. a latest/ page reusing main/'s code - # blocks) just write the literal path with no token. - source_file = entry.get("file") or rel_doc - for token, value in tokens.items(): - source_file = source_file.replace(token, value) - sources.append({"file": source_file, "path": source_path}) - - if not sources: - continue - - test_slug = sanitize_name(test_name) - script_name = f"{doc_slug}-{test_slug}.sh" - manifest_name = f"{doc_slug}-{test_slug}.manifest.json" - - test_cases.append( - TestCase( - document=md_file, - name=test_name, - sources=sources, - script_path=generated_dir / script_name, - manifest_path=generated_dir / manifest_name, - ) - ) - - return test_cases, sorted(set(tested_documents)) - - -def _version_key(doc_path: str) -> str: - """Extract 'product/version' from a path like content/docs/kubernetes/main/...""" - parts = doc_path.replace("\\", "/").split("/") - try: - idx = parts.index("docs") - return "/".join(parts[idx + 1 : idx + 3]) - except (ValueError, IndexError): - return "unknown" - - -def build_test_cases( - repo_root: Path, - docs_glob: str, - generated_dir: Path, -) -> Tuple[List[TestCase], List[str], Dict[str, int], int]: - test_cases: List[TestCase] = [] - tested_documents: List[str] = [] - total_by_version: Dict[str, int] = {} - total_documents = 0 - - for md_file in sorted(repo_root.glob(docs_glob)): - rel = md_file.relative_to(repo_root).as_posix() - parts = rel.replace("\\", "/").split("/") - try: - idx = parts.index("docs") - version_segment = parts[idx + 2] if len(parts) > idx + 2 else "" - except ValueError: - version_segment = "" - if version_segment not in ("latest", "main"): - continue - vk = _version_key(rel) - total_by_version[vk] = total_by_version.get(vk, 0) + 1 - total_documents += 1 - cases, docs = build_test_cases_from_file(repo_root, md_file, generated_dir) - test_cases.extend(cases) - tested_documents.extend(docs) - - return test_cases, sorted(set(tested_documents)), total_by_version, total_documents - - -def generate_script_and_manifest(repo_root: Path, definition: Dict, script_path: Path, manifest_path: Path) -> None: - if yaml is None: - raise RuntimeError("PyYAML is required. Install it with: pip install pyyaml") - - extractor = Extractor(repo_root=repo_root, definition=definition) - extractor.walk() - - blocks = extractor.select_blocks() - test_includes = extractor.select_test_includes() - script = extractor.build_script(blocks, test_includes) - manifest = extractor.build_manifest(blocks, test_includes) - - script_path.parent.mkdir(parents=True, exist_ok=True) - manifest_path.parent.mkdir(parents=True, exist_ok=True) - script_path.write_text(script, encoding="utf-8") - manifest_path.write_text(yaml.safe_dump(manifest, sort_keys=False), encoding="utf-8") - - -def run_command(command: List[str], cwd: Path) -> Tuple[int, str]: - logger.debug("$ %s", " ".join(command)) - - proc = subprocess.Popen( - command, - cwd=str(cwd), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - ) - - output_lines: List[str] = [] - if proc.stdout is not None: - for line in proc.stdout: - output_lines.append(line) - logger.debug("%s", line.rstrip()) - - return_code = proc.wait() - return return_code, "".join(output_lines) - - -def collect_cluster_context(cluster_name: str, context_dir: Path) -> None: - """Collect Kubernetes diagnostics from a kind cluster into context_dir. - - Mirrors the procgen server-status action: gathers pod logs, failed pods, - events, nodes, CRDs, services, deployments, and helm values for every - namespace, saving everything under context_dir//. - - Uses the kubeconfig exported from kind directly to avoid --context flag - ordering issues with kubectl plugins/multi-resource commands. - """ - # Export the kubeconfig for this cluster into a temp env var so we never - # need --context anywhere (avoids "flags cannot be placed before plugin name"). - kubeconfig_result = subprocess.run( - ["kind", "get", "kubeconfig", "--name", cluster_name], - capture_output=True, text=True, timeout=30, - ) - kubeconfig_content = kubeconfig_result.stdout - kubeconfig_env = {**os.environ, "KUBECONFIG": ""} - - # Write kubeconfig to a temp file so subprocesses can share it - with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as kf: - kf.write(kubeconfig_content) - kubeconfig_path = kf.name - - kubeconfig_env["KUBECONFIG"] = kubeconfig_path - base_cmd = ["kubectl"] - - def run(args: List[str], out_file: Optional[Path] = None) -> str: - cmd = base_cmd + args - logger.debug(" $ %s", " ".join(cmd)) - try: - result = subprocess.run(cmd, capture_output=True, text=True, timeout=60, env=kubeconfig_env) - output = result.stdout + result.stderr - except Exception as exc: - output = str(exc) - if out_file: - out_file.parent.mkdir(parents=True, exist_ok=True) - out_file.write_text(output, encoding="utf-8") - return output - - def run_raw(cmd: List[str], timeout: int = 30) -> subprocess.CompletedProcess: - return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, env=kubeconfig_env) - - try: - logger.info("Collecting cluster context: %s -> %s", cluster_name, context_dir) - context_dir.mkdir(parents=True, exist_ok=True) - - # Overview: pods, services, deployments across all namespaces (separate calls to avoid - # the "flags before plugin name" error that comma-joined multi-resource gets can trigger) - run(["get", "po", "-A"], context_dir / "pods.txt") - run(["get", "svc", "-A"], context_dir / "services-overview.txt") - run(["get", "deploy", "-A"], context_dir / "deployments-overview.txt") - - # Failed (non-Running) pods - run(["get", "po", "-A", "--field-selector=status.phase!=Running", "-oyaml"], - context_dir / "failed-pods.yaml") - - # Events sorted by time - run(["get", "events", "-A", "--sort-by=.lastTimestamp"], - context_dir / "events.txt") - - # Nodes - nodes_dir = context_dir / "nodes" - run(["get", "nodes", "-oyaml"], nodes_dir / "nodes.yaml") - run(["describe", "nodes"], nodes_dir / "nodes-describe.log") - - # All custom resources — collect every CRD unconditionally - crds_dir = context_dir / "crds" - crd_list_output = run(["get", "crd", "--no-headers"]) - for line in crd_list_output.splitlines(): - parts = line.split() - crd = parts[0] if parts else "" - if not crd: - continue - run(["get", crd, "-A", "-oyaml"], crds_dir / f"{crd}.yaml") - - # Per-namespace pod logs, services, deployments, helm values - ns_output = run_raw( - base_cmd + ["get", "ns", "-o", "jsonpath={range .items[*]}{.metadata.name}{'\\n'}{end}"], - ) - namespaces = [ns for ns in ns_output.stdout.splitlines() if ns.strip()] - - pods_dir = context_dir / "pods" - svcs_dir = context_dir / "services" - deploys_dir = context_dir / "deployments" - helm_dir = context_dir / "helm-values" - - for ns in namespaces: - # Pods - pod_output = run_raw( - base_cmd + ["-n", ns, "get", "po", "-o", - "jsonpath={range .items[*]}{.metadata.name}{'\\n'}{end}"], - ) - for po in pod_output.stdout.splitlines(): - po = po.strip() - if not po: - continue - run(["-n", ns, "describe", "po", po], pods_dir / f"{ns}-{po}-describe.log") - run(["-n", ns, "get", "po", po, "-oyaml"], pods_dir / f"{ns}-{po}-pod.yaml") - # Container logs (previous then current) - containers_out = run_raw( - base_cmd + ["-n", ns, "get", "po", po, "-o", - "jsonpath={range .spec.containers[*]}{.name}{'\\n'}{end}"], - ) - for container in containers_out.stdout.splitlines(): - container = container.strip() - if not container: - continue - prev = run_raw(base_cmd + ["-n", ns, "logs", "-p", "-c", container, po], timeout=60) - if prev.returncode == 0 and prev.stdout.strip(): - log_text = prev.stdout - else: - curr = run_raw(base_cmd + ["-n", ns, "logs", "-c", container, po], timeout=60) - log_text = curr.stdout + curr.stderr - log_file = pods_dir / f"{ns}-{po}-{container}-logs.log" - log_file.parent.mkdir(parents=True, exist_ok=True) - log_file.write_text(log_text, encoding="utf-8") - - # Services - svc_output = run_raw( - base_cmd + ["-n", ns, "get", "svc", "-o", - "jsonpath={range .items[*]}{.metadata.name}{'\\n'}{end}"], - ) - for svc in svc_output.stdout.splitlines(): - svc = svc.strip() - if not svc: - continue - run(["-n", ns, "describe", "svc", svc], svcs_dir / f"{ns}-{svc}-describe.log") - run(["-n", ns, "get", "svc", svc, "-oyaml"], svcs_dir / f"{ns}-{svc}-svc.yaml") - - # Deployments - deploy_output = run_raw( - base_cmd + ["-n", ns, "get", "deploy", "-o", - "jsonpath={range .items[*]}{.metadata.name}{'\\n'}{end}"], - ) - for deploy in deploy_output.stdout.splitlines(): - deploy = deploy.strip() - if not deploy: - continue - run(["-n", ns, "describe", "deploy", deploy], deploys_dir / f"{ns}-{deploy}-describe.log") - run(["-n", ns, "get", "deploy", deploy, "-oyaml"], deploys_dir / f"{ns}-{deploy}-deploy.yaml") - - # Helm values - helm_output = subprocess.run( - ["helm", "list", "-n", ns, "-q"], - capture_output=True, text=True, timeout=30, env=kubeconfig_env, - ) - for chart in helm_output.stdout.splitlines(): - chart = chart.strip() - if not chart: - continue - helm_vals = subprocess.run( - ["helm", "get", "values", "-n", ns, chart, "-o", "yaml"], - capture_output=True, text=True, timeout=30, env=kubeconfig_env, - ) - out_file = helm_dir / f"{ns}-{chart}.yaml" - out_file.parent.mkdir(parents=True, exist_ok=True) - out_file.write_text(helm_vals.stdout + helm_vals.stderr, encoding="utf-8") - - logger.info("Context collection complete: %s", context_dir) - finally: - os.unlink(kubeconfig_path) - - -# Number of times to retry "kind create cluster" when it fails pulling the node -# image from Docker Hub. These are transient registry timeouts/rate-limits, not -# test failures, so a short retry avoids spurious red runs (see nightly run flakes -# where 13 unrelated tests all died on "failed to pull image kindest/node"). -CLUSTER_CREATE_ATTEMPTS = 3 -CLUSTER_CREATE_RETRY_DELAY_SECONDS = 15 - -# Substrings that mark a cluster-creation failure as a transient image-pull issue -# rather than a real problem with the test or cluster config. Matching is -# case-insensitive. We deliberately keep this narrow so genuine failures fail fast. -_TRANSIENT_PULL_MARKERS = ( - "failed to pull image", - "registry-1.docker.io", - "context deadline exceeded", - "request canceled while waiting for connection", - "i/o timeout", - "tls handshake timeout", -) - - -def _is_transient_pull_failure(output: str) -> bool: - lowered = output.lower() - return any(marker in lowered for marker in _TRANSIENT_PULL_MARKERS) - - -def create_cluster_with_retries(cluster_name: str, repo_root: Path) -> Tuple[int, str]: - """Create a kind cluster, retrying only on transient Docker Hub image-pull failures. - - The first successful pull seeds the local Docker image cache, so later clusters - in the same job reuse it. A failed attempt may leave a partial cluster behind, - so we delete by name before retrying. Returns the last (code, output) pair. - """ - create_code, create_output = 0, "" - for attempt in range(1, CLUSTER_CREATE_ATTEMPTS + 1): - create_code, create_output = run_command(["kind", "create", "cluster", "--name", cluster_name], repo_root) - if create_code == 0: - return create_code, create_output - if attempt == CLUSTER_CREATE_ATTEMPTS or not _is_transient_pull_failure(create_output): - break - logger.warning( - "Transient image-pull failure creating cluster '%s' (attempt %d/%d); retrying in %ds", - cluster_name, attempt, CLUSTER_CREATE_ATTEMPTS, CLUSTER_CREATE_RETRY_DELAY_SECONDS, - ) - # Clean up any partial cluster so the retry starts from a clean slate. - run_command(["kind", "delete", "cluster", "--name", cluster_name], repo_root) - time.sleep(CLUSTER_CREATE_RETRY_DELAY_SECONDS) - return create_code, create_output - - -PORT_FORWARD_RE = re.compile(r"\bkubectl\s+port-forward\b") - - -def contains_port_forward(script_content: str) -> bool: - """Report whether the script would actually run `kubectl port-forward`. - - Comment-only lines are ignored. A hidden `{{< doc-test >}}` block often - documents *why* a test avoids port-forwarding, and naming the command in that - comment used to reject the test even though nothing ran it. A line whose first - non-whitespace character is `#` is never executed by bash, so skipping those - cannot hide a real invocation. Trailing comments are deliberately left in - scope: telling a real `#` from one inside a quoted string or heredoc needs a - shell parser, and over-reporting is the safe direction here. - """ - for line in script_content.splitlines(): - if line.lstrip().startswith("#"): - continue - if PORT_FORWARD_RE.search(line): - return True - return False - - -def run_test_case(repo_root: Path, test_case: TestCase, cluster_prefix: str, context_base_dir: Optional[Path] = None, pause: bool = False, keep_cluster: bool = False) -> Dict: - test_slug = sanitize_name(test_case.name) - cluster_name = f"{cluster_prefix}-{test_slug}"[:50] - - # Build a unique context dir slug from the full report key (doc_rel::test_name), - # e.g. content/docs/kubernetes/main/security/csrf.md::default -> - # content-docs-kubernetes-main-security-csrf--default - # This avoids collisions when the same test name appears in multiple doc versions. - doc_rel = test_case.document.relative_to(repo_root).as_posix() - context_slug = sanitize_name(f"{doc_rel.removesuffix('.md')}--{test_case.name}") - - checks: List[str] = [] - status = "failed" - error: Optional[str] = None - collected_context_dir: Optional[Path] = None - - logger.info('\n') - logger.info("=== Running test: %s (%s) ===", test_case.name, doc_rel) - - script_content = test_case.script_path.read_text(encoding="utf-8") - if contains_port_forward(script_content): - logger.warning("SKIPPED (port-forward): %s", doc_rel) - return { - "status": "failed", - "checks": checks, - "error": "Test shell script contains 'kubectl port-forward', which is not supported in automated tests.", - } - - create_code, create_output = create_cluster_with_retries(cluster_name, repo_root) - if create_code != 0: - # Best-effort context collection even if cluster creation partially failed - if context_base_dir is not None: - collected_context_dir = context_base_dir / context_slug - collected_context_dir.mkdir(parents=True, exist_ok=True) - (collected_context_dir / "test-execution.log").write_text(create_output, encoding="utf-8") - try: - collect_cluster_context(cluster_name, collected_context_dir) - except Exception as exc: - logger.warning("Context collection skipped: %s", exc) - return { - "status": "failed", - "checks": checks, - "error": create_output.strip(), - "cluster": cluster_name, - **({"context_dir": str(collected_context_dir.relative_to(repo_root))} if collected_context_dir else {}), - } - - verbose = logger.isEnabledFor(logging.DEBUG) - already_running = subprocess.run(["pgrep", "-x", "cloud-provider-kind"], capture_output=True).returncode == 0 - if already_running: - logger.info("cloud-provider-kind already running, skipping start") - cloud_provider = None - else: - cloud_provider = subprocess.Popen( - ["cloud-provider-kind", "--gateway-channel", "disabled"], - cwd=str(repo_root), - stdout=None, - stderr=None if verbose else subprocess.DEVNULL, - text=True, - ) - - # Run the script from a scratch directory rather than the repo root. Guides write - # their config files with relative paths (`cat < config.yaml`), so running - # from the repo root drops those files into the working tree — 58 scenarios write - # a bare `config.yaml`, which is why .gitignore has an entry for it. Nothing in a - # generated script reads a repo path relatively, so cwd is free to move. The - # directory is recreated per scenario, so a stale file cannot mask a guide that - # forgets to write one. - work_dir = repo_root / "out" / "tests" / "work" / context_slug - if work_dir.exists(): - shutil.rmtree(work_dir) - work_dir.mkdir(parents=True, exist_ok=True) - - try: - time.sleep(2) - test_code, output = run_command(["bash", test_case.script_path.as_posix()], work_dir) - checks = [line.strip() for line in output.splitlines() if line.strip().startswith("✓ ")] - status = "passed" if test_code == 0 else "failed" - if test_code != 0: - error = output.strip() - # Collect cluster diagnostics before the cluster is deleted - if context_base_dir is not None: - collected_context_dir = context_base_dir / context_slug - collected_context_dir.mkdir(parents=True, exist_ok=True) - (collected_context_dir / "test-execution.log").write_text(output, encoding="utf-8") - try: - collect_cluster_context(cluster_name, collected_context_dir) - except Exception as exc: - logger.warning("Context collection error: %s", exc) - finally: - if keep_cluster: - # Leave the cluster up for an external capture step (e.g. port-forward + Playwright). - # cloud-provider-kind is still stopped: the pods are up and port-forward to the proxy - # deployment works without it, and leaving the daemon running would leak a process. - if cloud_provider is not None: - cloud_provider.terminate() - try: - cloud_provider.wait(timeout=10) - except subprocess.TimeoutExpired: - cloud_provider.kill() - logger.info( - "--keep-cluster set: cluster '%s' left running (kubeconfig context 'kind-%s'). " - "Clean up with: kind delete cluster --name %s", - cluster_name, cluster_name, cluster_name, - ) - else: - if pause: - logger.info("--pause set: cluster '%s' is kept running. Press Ctrl+C to clean up and exit.", cluster_name) - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - logger.info("Interrupted — deleting cluster '%s'...", cluster_name) - - if cloud_provider is not None: - cloud_provider.terminate() - try: - cloud_provider.wait(timeout=10) - except subprocess.TimeoutExpired: - cloud_provider.kill() - - delete_code, delete_output = run_command(["kind", "delete", "cluster", "--name", cluster_name], repo_root) - if delete_code != 0 and not error: - error = delete_output.strip() - status = "failed" - - result = { - "status": status, - "checks": checks, - "cluster": cluster_name, - } - if error: - result["error"] = error - if collected_context_dir is not None: - result["context_dir"] = str(collected_context_dir.relative_to(repo_root)) - return result - - -def write_report( - report_path: Path, - tested_documents: List[str], - test_results: Dict[str, Dict], - total_documents: int = 0, - total_by_version: Optional[Dict[str, int]] = None, -) -> None: - if yaml is None: - raise RuntimeError("PyYAML is required. Install it with: pip install pyyaml") - - report = { - "tested_documents": tested_documents, - "total_documents": total_documents, - "total_documents_by_version": total_by_version or {}, - "tests": test_results, - } - report_path.parent.mkdir(parents=True, exist_ok=True) - report_path.write_text(yaml.safe_dump(report, sort_keys=False), encoding="utf-8") - - -def main() -> int: - parser = argparse.ArgumentParser(description="Generate and run doc tests from page YAML front matter metadata.") - parser.add_argument("--repo-root", default=".", help="Workspace root") - parser.add_argument("--docs-glob", default="content/docs/**/*.md", help="Glob to discover markdown docs") - parser.add_argument("--version", default="2.2.x", help="Default context.version") - parser.add_argument("--product", default="kubernetes", help="Default context.product") - parser.add_argument( - "--generated-dir", - default="out/tests/generated", - help="Directory where generated scripts/manifests are written", - ) - parser.add_argument( - "--report-file", - default="out/tests/generated/test-results.yaml", - help="YAML report file path", - ) - parser.add_argument("--cluster-prefix", default="doc-test", help="Kind cluster name prefix") - parser.add_argument( - "--verbose", - action=argparse.BooleanOptionalAction, - default=True, - help="Stream all command output (default: enabled)", - ) - parser.add_argument("--generate-only", action="store_true", help="Only generate scripts/manifests, do not run tests") - parser.add_argument("--list-tests", action="store_true", help="Print discovered test cases as JSON to stdout and exit") - parser.add_argument("--file", nargs="+", default=None, metavar="FILE", help="Path(s) to one or more markdown files to test (relative to repo root or absolute)") - parser.add_argument("--test", default=None, help="Name of a specific test scenario to run (only used when --file specifies a single file)") - parser.add_argument("--pause", action="store_true", help="After the test, keep the cluster running until Ctrl+C, then clean up") - parser.add_argument( - "--keep-cluster", - action="store_true", - help="After the test, leave the kind cluster running and exit (non-blocking) instead of deleting it. " - "Use for screenshot capture: an external step can port-forward the proxy and run Playwright against " - "the kept cluster (kubeconfig context 'kind-'), then run 'kind delete cluster --name '.", - ) - parser.add_argument( - "--keep-cluster-file", - default=None, - metavar="PATH", - help="With --keep-cluster, write the kept cluster name(s) to PATH (one per line) so CI can port-forward and later delete them.", - ) - args = parser.parse_args() - - logging.basicConfig( - level=logging.DEBUG if args.verbose else logging.INFO, - format="%(levelname)s: %(message)s", - stream=sys.stderr, - ) - - repo_root = Path(args.repo_root).resolve() - generated_dir = (repo_root / args.generated_dir).resolve() - report_path = (repo_root / args.report_file).resolve() - - if args.file: - filter_test_name = args.test if len(args.file) == 1 else None - test_cases = [] - tested_docs: List[str] = [] - for f in args.file: - md_file = Path(f) - if not md_file.is_absolute(): - md_file = repo_root / md_file - cases, docs = build_test_cases_from_file(repo_root, md_file, generated_dir, filter_test_name=filter_test_name) - tested_docs.extend(docs) - if not cases: - if args.test and len(args.file) == 1: - logger.error("No test named '%s' found in %s", args.test, f) - return 1 - else: - logger.warning("No test metadata found in '%s'.", f) - continue - test_cases.extend(cases) - _, all_tested_documents, total_by_version, total_documents = build_test_cases(repo_root, args.docs_glob, generated_dir) - tested_documents = sorted(set(tested_docs) | set(all_tested_documents)) - else: - test_cases, tested_documents, total_by_version, total_documents = build_test_cases(repo_root, args.docs_glob, generated_dir) - - if args.list_tests: - entries = [ - {"file": tc.document.relative_to(repo_root).as_posix(), "test": tc.name} - for tc in test_cases - ] - print(json.dumps(entries)) - return 0 - - if not test_cases: - logger.info("No docs with test metadata found.") - write_report(report_path, tested_documents, {}, total_documents, total_by_version) - return 0 - - for test_case in test_cases: - logger.debug("Generating script for %s::%s", test_case.document.relative_to(repo_root).as_posix(), test_case.name) - inferred_version = infer_version_from_sources(test_case.sources, args.version) - definition = { - "name": sanitize_name(f"{test_case.document.stem}-{test_case.name}"), - "main_file": test_case.document.relative_to(repo_root).as_posix(), - "context": { - "version": inferred_version, - "product": args.product, - }, - "options": DEFAULT_OPTIONS, - "sources": [{"file": src["file"], "paths": [src["path"]]} for src in test_case.sources], - "output": { - "script": test_case.script_path.relative_to(repo_root).as_posix(), - "manifest": test_case.manifest_path.relative_to(repo_root).as_posix(), - }, - } - generate_script_and_manifest(repo_root, definition, test_case.script_path, test_case.manifest_path) - - if args.generate_only: - write_report(report_path, tested_documents, {}, total_documents, total_by_version) - logger.info("Generated %d scripts from metadata", len(test_cases)) - logger.info("Wrote report scaffold: %s", report_path.relative_to(repo_root)) - return 0 - - context_base_dir = generated_dir / "context" - - logger.info("Running %d test scenario(s)", len(test_cases)) - if args.keep_cluster and len(test_cases) > 1: - logger.warning("--keep-cluster with %d scenarios will leave multiple clusters running.", len(test_cases)) - test_results: Dict[str, Dict] = {} - kept_clusters: List[str] = [] - exit_code = 0 - for test_case in test_cases: - doc_rel = test_case.document.relative_to(repo_root).as_posix() - key = f"{doc_rel}::{test_case.name}" - result = run_test_case(repo_root, test_case, args.cluster_prefix, context_base_dir=context_base_dir, pause=args.pause, keep_cluster=args.keep_cluster) - status_icon = "PASSED" if result.get("status") == "passed" else "FAILED" - logger.info("%s: %s", status_icon, key) - test_results[key] = result - if args.keep_cluster and result.get("cluster"): - kept_clusters.append(result["cluster"]) - if result.get("status") != "passed": - exit_code = 1 - - if args.keep_cluster and args.keep_cluster_file and kept_clusters: - kept_path = Path(args.keep_cluster_file) - if not kept_path.is_absolute(): - kept_path = repo_root / kept_path - kept_path.parent.mkdir(parents=True, exist_ok=True) - kept_path.write_text("\n".join(kept_clusters) + "\n", encoding="utf-8") - logger.info("Wrote kept cluster name(s) to %s", kept_path) - - write_report(report_path, tested_documents, test_results, total_documents, total_by_version) - logger.info("================= Test Results =================") - logger.info("Wrote report: %s", report_path.relative_to(repo_root)) - passed_count = sum(1 for r in test_results.values() if r['status'] == 'passed') - failed_count = sum(1 for r in test_results.values() if r['status'] != 'passed') - logger.info("Test results: %d total, %d passed, %d failed", len(test_cases), passed_count, failed_count) - if failed_count > 0: - logger.info("Failed test results:") - logger.debug("%s", yaml.safe_dump(test_results, sort_keys=False)) - return exit_code - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/list_untested_docs.py b/scripts/list_untested_docs.py deleted file mode 100644 index c6d02a3bf..000000000 --- a/scripts/list_untested_docs.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -"""List markdown files in content/docs that have no test coverage. - -A file is considered covered if its front matter contains a 'test:' key -(either a scenario dict or 'test: skip'). Files with no 'test:' key at all -are written to the output file as candidates for adding tests. - -Usage: - python3 scripts/list_untested_docs.py \ - --docs-dir content/docs \ - --exclude content/docs/kubernetes/2.2.x \ - --output out/tests/generated/untested-docs.txt -""" - -import argparse -import sys -from pathlib import Path - -try: - import yaml # type: ignore[import-not-found] -except ModuleNotFoundError: - print("PyYAML is required. Install it with: pip install pyyaml", file=sys.stderr) - sys.exit(1) - - -def parse_front_matter(path: Path) -> dict: - """Return the YAML front matter dict from a markdown file, or {}.""" - try: - text = path.read_text(encoding="utf-8") - except OSError: - return {} - if not text.startswith("---"): - return {} - end = text.find("\n---", 3) - if end == -1: - return {} - try: - return yaml.safe_load(text[3:end]) or {} - except yaml.YAMLError: - return {} - - -def main() -> int: - parser = argparse.ArgumentParser(description="List markdown files without doc test coverage") - parser.add_argument("--docs-dir", default="content/docs", help="Directory to scan") - parser.add_argument( - "--exclude", - action="append", - default=[], - metavar="PATH", - help="Path prefix to exclude (may be repeated)", - ) - parser.add_argument( - "--output", - default="out/tests/generated/untested-docs.txt", - help="Output file path", - ) - args = parser.parse_args() - - docs_dir = Path(args.docs_dir) - if not docs_dir.is_dir(): - print(f"Directory not found: {docs_dir}", file=sys.stderr) - return 1 - - exclude_paths = [Path(e) for e in args.exclude] - - untested: list[str] = [] - for md_file in sorted(docs_dir.rglob("*.md")): - if any(md_file.is_relative_to(ex) for ex in exclude_paths): - continue - fm = parse_front_matter(md_file) - if "test" not in fm: - untested.append(md_file.as_posix()) - - output_path = Path(args.output) - output_path.parent.mkdir(parents=True, exist_ok=True) - output_path.write_text("\n".join(untested) + ("\n" if untested else ""), encoding="utf-8") - - print(f"{len(untested)} file(s) without test coverage written to {output_path}") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/merge_test_results.py b/scripts/merge_test_results.py deleted file mode 100644 index 57d22c6f1..000000000 --- a/scripts/merge_test_results.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -"""Merge multiple test-results.yaml files into a single unified report.""" - -import sys -from pathlib import Path - -try: - import yaml # type: ignore[import-not-found] -except ModuleNotFoundError: - print("PyYAML is required. Install it with: pip install pyyaml", file=sys.stderr) - sys.exit(1) - - -def merge(input_dir: Path, output_path: Path) -> None: - tested_documents: set[str] = set() - tests: dict = {} - total_documents: int = 0 - total_by_version: dict = {} - - result_files = sorted(input_dir.rglob("test-results.yaml")) - for results_file in result_files: - with open(results_file) as f: - report = yaml.safe_load(f) or {} - for doc in report.get("tested_documents", []): - tested_documents.add(doc) - for key, result in report.get("tests", {}).items(): - tests[key] = result - # Take the max total_documents seen across shards (all shards scan the same glob) - shard_total = report.get("total_documents", 0) - if shard_total > total_documents: - total_documents = shard_total - for version, count in report.get("total_documents_by_version", {}).items(): - if count > total_by_version.get(version, 0): - total_by_version[version] = count - - merged = { - "tested_documents": sorted(tested_documents), - "total_documents": total_documents, - "total_documents_by_version": total_by_version, - "tests": tests, - } - output_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_path, "w") as f: - yaml.safe_dump(merged, f, sort_keys=False) - - print(f"Merged {len(tests)} test result(s) from {len(result_files)} file(s)") - - -if __name__ == "__main__": - if len(sys.argv) != 3: - print(f"Usage: {sys.argv[0]} ", file=sys.stderr) - sys.exit(1) - merge(Path(sys.argv[1]), Path(sys.argv[2])) diff --git a/scripts/report_summary.py b/scripts/report_summary.py deleted file mode 100755 index 930d76805..000000000 --- a/scripts/report_summary.py +++ /dev/null @@ -1,423 +0,0 @@ -#!/usr/bin/env python3 -"""Generate a GitHub Actions Job Summary (Markdown) from test-results.yaml. - -Usage: - python3 scripts/report_summary.py out/tests/generated/test-results.yaml >> "$GITHUB_STEP_SUMMARY" - python3 scripts/report_summary.py --slack out/tests/generated/test-results.yaml # Slack Block Kit JSON -""" - -import json -import sys -from pathlib import Path - -try: - import yaml # type: ignore[import-not-found] -except ModuleNotFoundError: - print("PyYAML is required. Install it with: pip install pyyaml", file=sys.stderr) - sys.exit(1) - -# Slack Block Kit limits -_SLACK_TEXT_LIMIT = 3000 -_SLACK_MAX_BLOCKS = 50 - - -def _status_icon(status: str) -> str: - return "\u2705" if status == "passed" else "\u274c" - - -def _escape_md_table(text: str) -> str: - """Escape pipe characters so they don't break Markdown table cells.""" - return text.replace("|", "\\|").replace("\n", " ") - - -def _format_checks(checks: list) -> str: - if not checks: - return "\u2014" - n = len(checks) - return f"{n} check{'s' if n != 1 else ''} passed" - - -def _format_checks_count(n: int) -> str: - if n == 0: - return "\u2014" - return f"{n} check{'s' if n != 1 else ''} passed" - - -def _coverage_rows(tested_documents: list, total_by_version: dict, total_documents: int) -> list[tuple[str, int, int]]: - """Return sorted rows of (version, tested_count, total_count) for coverage table.""" - tested_by_version: dict[str, int] = {} - for doc in tested_documents: - vk = _extract_version(doc) - tested_by_version[vk] = tested_by_version.get(vk, 0) + 1 - - versions = sorted(set(list(total_by_version.keys()) + list(tested_by_version.keys()))) - rows = [] - for v in versions: - total = total_by_version.get(v, 0) - rows.append((v, tested_by_version.get(v, 0), total)) - return rows - - -def _coverage_section_md(tested_documents: list, total_by_version: dict, total_documents: int) -> list[str]: - """Build Markdown lines for a coverage section.""" - if total_documents == 0: - return [] - lines: list[str] = [] - rows = _coverage_rows(tested_documents, total_by_version, total_documents) - adjusted_total = sum(total for _, _, total in rows) - adjusted_tested = sum(tested for _, tested, _ in rows) - pct = int(adjusted_tested / adjusted_total * 100) if adjusted_total else 0 - lines.append(f"### Coverage \u2014 {adjusted_tested} / {adjusted_total} pages tested ({pct}%)") - lines.append("") - if rows: - lines.append("| Product/Version | Tested | Total | Coverage |") - lines.append("|:---|---:|---:|---:|") - for version, tested, total in rows: - row_pct = f"{int(tested / total * 100)}%" if total else "—" - lines.append(f"| `{version}` | {tested} | {total} | {row_pct} |") - lines.append(f"| **Total** | **{adjusted_tested}** | **{adjusted_total}** | **{pct}%** |") - lines.append("") - return lines - - -def generate_summary(report: dict) -> str: - lines: list[str] = [] - - tests: dict = report.get("tests", {}) - tested_documents: list = report.get("tested_documents", []) - total_documents: int = report.get("total_documents", 0) - total_by_version: dict = report.get("total_documents_by_version", {}) - - if not tests: - lines.append("## Doc Test Results") - lines.append("") - lines.append("No test results found.") - return "\n".join(lines) - - doc_groups = _group_by_document(tests) - skipped = len([d for d in tested_documents if d not in doc_groups]) - actual_passed = sum(1 for g in doc_groups.values() if g["status"] == "passed") - failed = len(doc_groups) - actual_passed - passed = actual_passed + skipped - total = len(doc_groups) + skipped - - # Header - if failed == 0: - lines.append(f"## \u2705 Doc Test Results \u2014 {passed} passed | {total} total") - else: - lines.append(f"## \u274c Doc Test Results \u2014 {passed} passed | {failed} failed | {total} total") - lines.append("") - - # Coverage section - lines.extend(_coverage_section_md(tested_documents, total_by_version, total_documents)) - - # Results table — one row per document - lines.append("| Status | Test | Document | Checks |") - lines.append("|:------:|------|----------|--------|") - - failed_tests: list[tuple[str, dict]] = [] - - for doc, group in doc_groups.items(): - status = group["status"] - icon = _status_icon(status) - test_label = _escape_md_table(_format_test_names(group["tests"])) - check_count = group["check_count"] - checks_str = _format_checks_count(check_count) - - lines.append(f"| {icon} | `{test_label}` | `{_escape_md_table(doc)}` | {checks_str} |") - - for key, result in tests.items(): - if result.get("status") != "passed" and result.get("error"): - failed_tests.append((key, result)) - - # Failed test details - if failed_tests: - lines.append("") - lines.append("### Failed Tests") - lines.append("") - - for key, result in failed_tests: - parts = key.split("::", 1) - doc = parts[0] if len(parts) > 0 else key - test_name = parts[1] if len(parts) > 1 else key - version = _extract_version(doc) - title = f"{test_name} ({version})" if version else test_name - error = result.get("error", "No error output captured.") - - # Show individual checks if any passed before failure - checks = result.get("checks", []) - - lines.append(f"
") - lines.append(f"{_escape_md_table(title)}") - lines.append("") - - if checks: - lines.append("**Checks:**") - for check in checks: - lines.append(f"- {check}") - lines.append("") - - lines.append("**Error output:**") - lines.append("") - lines.append("```") - lines.append(error) - lines.append("```") - lines.append("") - lines.append("
") - lines.append("") - - return "\n".join(lines) - - -def _truncate(text: str, limit: int = _SLACK_TEXT_LIMIT, suffix: str = "\n... (truncated)") -> str: - """Truncate text to fit within Slack's field character limit.""" - if len(text) <= limit: - return text - return text[: limit - len(suffix)] + suffix - - -def _truncate_tail(text: str, limit: int = _SLACK_TEXT_LIMIT // 2, prefix: str = "(truncated) ...\n") -> str: - """Keep the tail of text, truncating from the beginning.""" - if len(text) <= limit: - return text - return prefix + text[-(limit - len(prefix)):] - - -def _group_by_document(tests: dict) -> dict: - """Collapse per-test results into per-document groups. - - Returns an ordered dict keyed by doc path, each value being a dict with: - - status: "passed" if all tests passed, else "failed" - - tests: list of test names in order - - check_count: total number of checks across all tests - """ - groups: dict = {} - for key, result in tests.items(): - parts = key.split("::", 1) - doc = parts[0] if len(parts) > 0 else key - test_name = parts[1] if len(parts) > 1 else key - if doc not in groups: - groups[doc] = {"status": "passed", "tests": [], "check_count": 0} - groups[doc]["tests"].append(test_name) - groups[doc]["check_count"] += len(result.get("checks", [])) - if result.get("status") != "passed": - groups[doc]["status"] = "failed" - return groups - - -def _format_test_names(tests: list) -> str: - """Format a list of test names as 'first' or 'first + N more'.""" - if not tests: - return "" - if len(tests) == 1: - return tests[0] - return f"{tests[0]} + {len(tests) - 1} more" - - -def _extract_version(doc_path: str) -> str: - """Extract the version segment from a doc path. - - Expects paths like ``content/docs/kubernetes/main/...`` and returns - the two segments immediately following ``docs/``, e.g. ``kubernetes/main``. - """ - path_parts = doc_path.replace("\\", "/").split("/") - try: - idx = path_parts.index("docs") - return "/".join(path_parts[idx + 1 : idx + 3]) - except (ValueError, IndexError): - return "" - - -def _run_url_block(run_url: str) -> dict: - """Build a context block with a link to the GitHub Actions run.""" - return { - "type": "context", - "elements": [ - {"type": "mrkdwn", "text": f"<{run_url}|View workflow run>"} - ], - } - - -def _coverage_slack_text(tested_documents: list, total_by_version: dict, total_documents: int) -> str | None: - """Build a compact Slack mrkdwn string for coverage, or None if no data.""" - if total_documents == 0: - return None - rows = _coverage_rows(tested_documents, total_by_version, total_documents) - adjusted_total = sum(total for _, _, total in rows) - adjusted_tested = sum(tested for _, tested, _ in rows) - pct = int(adjusted_tested / adjusted_total * 100) if adjusted_total else 0 - lines = [f"*Coverage \u2014 {adjusted_tested} / {adjusted_total} pages tested ({pct}%)*"] - for version, tested, total in rows: - row_pct = f"{int(tested / total * 100)}%" if total else "—" - lines.append(f" `{version}`: {tested}/{total} ({row_pct})") - return "\n".join(lines) - - -def generate_slack_blocks(report: dict, run_url: str | None = None) -> tuple[dict, dict | None]: - """Generate a Slack Block Kit payload from test results. - - Returns a tuple of ``(main_payload, thread_payload)`` where each payload - has ``text`` and ``blocks`` keys ready for chat.postMessage. - ``thread_payload`` is ``None`` when there are no failures. - """ - tests: dict = report.get("tests", {}) - tested_documents: list = report.get("tested_documents", []) - total_documents: int = report.get("total_documents", 0) - total_by_version: dict = report.get("total_documents_by_version", {}) - - # --- empty results --- - if not tests: - fallback = "Doc Test Results — no test results found." - blocks: list[dict] = [ - {"type": "header", "text": {"type": "plain_text", "text": "Doc Test Results"}}, - {"type": "section", "text": {"type": "mrkdwn", "text": "No test results found."}}, - ] - if run_url: - blocks.append(_run_url_block(run_url)) - return {"text": fallback, "blocks": blocks}, None - - doc_groups = _group_by_document(tests) - skipped = len([d for d in tested_documents if d not in doc_groups]) - actual_passed = sum(1 for g in doc_groups.values() if g["status"] == "passed") - failed = len(doc_groups) - actual_passed - passed = actual_passed + skipped - total = len(doc_groups) + skipped - - # --- header --- - if failed == 0: - header_text = f"\u2705 Doc Test Results \u2014 {passed} passed | {total} total" - else: - header_text = f"\u274c Doc Test Results \u2014 {passed} passed | {failed} failed | {total} total" - - # Header block text is limited to 150 chars and plain_text only - blocks = [ - {"type": "header", "text": {"type": "plain_text", "text": header_text[:150]}}, - ] - - # --- coverage block --- - coverage_text = _coverage_slack_text(tested_documents, total_by_version, total_documents) - if coverage_text: - blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": _truncate(coverage_text)}}) - - # --- split results into failed and passed lists --- - failed_lines: list[str] = [] - passed_lines: list[str] = [] - failed_tests: list[tuple[str, dict]] = [] - - for doc, group in doc_groups.items(): - status = group["status"] - icon = _status_icon(status) - test_label = _format_test_names(group["tests"]) - checks_str = _format_checks_count(group["check_count"]) - line = f"{icon} `{test_label}` \u2014 {checks_str} (_`{doc}`_)" - if status == "passed": - passed_lines.append(line) - else: - failed_lines.append(line) - - for key, result in tests.items(): - if result.get("status") != "passed" and result.get("error"): - failed_tests.append((key, result)) - - # Main body: failed docs only (or all-passed note) - if failed_lines: - main_body = _truncate("\n".join(failed_lines)) - else: - main_body = "\u2705 All documents passed." - blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": main_body}}) - - # --- workflow run link --- - if run_url and len(blocks) < _SLACK_MAX_BLOCKS: - blocks.append(_run_url_block(run_url)) - - main_payload = {"text": header_text, "blocks": blocks} - - # --- thread reply: failed details + passed tests --- - if not failed_tests and not passed_lines: - return main_payload, None - - thread_blocks: list[dict] = [] - - if failed_tests: - thread_blocks.append( - {"type": "section", "text": {"type": "mrkdwn", "text": f"*Failed Tests ({len(failed_tests)})*"}} - ) - for key, result in failed_tests: - if len(thread_blocks) >= _SLACK_MAX_BLOCKS - 1: - thread_blocks.append( - {"type": "section", "text": {"type": "mrkdwn", "text": "_... additional failures omitted (block limit reached)_"}} - ) - break - - parts = key.split("::", 1) - doc = parts[0] if len(parts) > 0 else key - test_name = parts[1] if len(parts) > 1 else key - version = _extract_version(doc) - title = f"{test_name} ({version})" if version else test_name - error = result.get("error", "No error output captured.") - checks = result.get("checks", []) - - detail_parts: list[str] = [f"*`{title}`*"] - if checks: - detail_parts.append("*Checks:* " + ", ".join(checks)) - detail_parts.append(f"```{_truncate_tail(error)}```") - - thread_blocks.append( - {"type": "section", "text": {"type": "mrkdwn", "text": _truncate("\n".join(detail_parts))}} - ) - - if passed_lines and len(thread_blocks) < _SLACK_MAX_BLOCKS - 1: - thread_blocks.append( - {"type": "section", "text": {"type": "mrkdwn", "text": f"*Passed Tests ({passed})*"}} - ) - thread_blocks.append( - {"type": "section", "text": {"type": "mrkdwn", "text": _truncate("\n".join(passed_lines))}} - ) - - thread_payload = {"text": f"Failed Tests ({len(failed_tests)})", "blocks": thread_blocks} - - return main_payload, thread_payload - - -def main() -> int: - slack_mode = "--slack" in sys.argv - run_url: str | None = None - - # Extract --run-url value - argv = list(sys.argv[1:]) - filtered: list[str] = [] - i = 0 - while i < len(argv): - if argv[i] == "--run-url" and i + 1 < len(argv): - run_url = argv[i + 1] - i += 2 - elif argv[i] == "--slack": - i += 1 - else: - filtered.append(argv[i]) - i += 1 - - if len(filtered) < 1: - print(f"Usage: {sys.argv[0]} [--slack] [--run-url URL] ", file=sys.stderr) - return 1 - - results_path = Path(filtered[0]) - if not results_path.exists(): - print(f"File not found: {results_path}", file=sys.stderr) - return 1 - - with open(results_path, encoding="utf-8") as f: - report = yaml.safe_load(f) or {} - - if slack_mode: - main_payload, thread_payload = generate_slack_blocks(report, run_url=run_url) - print(json.dumps({"main": main_payload, "thread": thread_payload})) - else: - summary = generate_summary(report) - print(summary) - - return 0 - - -if __name__ == "__main__": - sys.exit(main())