diff --git a/.github/pages-templates/version-index.html b/.github/pages-templates/version-index.html new file mode 100644 index 0000000000..1911e311ca --- /dev/null +++ b/.github/pages-templates/version-index.html @@ -0,0 +1,46 @@ + + + + + + MTA REST API - Version Index + + + + +
+

API Reference

+

Select an API version to view its interactive documentation:

+

API v1

+

+ The primary API — provides the full set of capabilities to deploy, list and remove MTAs. +

+ +

API v2

+

+ A complementary API that currently only provides an improved listing of MTAs. For all other operations, use v1. +

+ +

+ The MTA REST API follows semantic versioning. A new version is published whenever + the API contract changes. Older versions remain available for reference. +

+
+ + diff --git a/.github/pages-templates/version-page.html b/.github/pages-templates/version-page.html new file mode 100644 index 0000000000..619e0d775f --- /dev/null +++ b/.github/pages-templates/version-page.html @@ -0,0 +1,41 @@ + + + + + + MTA REST API __VERSION__ - API Reference + + + + + +
+ + + + + diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 0000000000..0542d84c32 --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,269 @@ +name: Publish Documentation + +# Publishes the MTA REST API specs (machine-readable YAML + interactive Swagger UI docs) +# to the gh-pages branch. +# +# Versioning: each API version folder is keyed off the `info.version` field inside the +# generated YAML (mtarest.yaml / mtarest_v2.yaml). A new folder appears only when that +# version string changes. If the YAML content changes but the version does NOT, the build +# fails to force a conscious version bump - unless the head commit message contains the +# escape hatch `[docs-republish]` (deliberate same-version overwrite). +# +# Swagger UI is fetched at publish time from the npm registry, pinned to an exact version +# and SHA-256 verified before use (no runtime CDN dependency, no committed vendor blob, +# no pom.xml entry). A hash mismatch fails the build. + +on: + push: + branches: [master] + paths: + - 'multiapps-controller-api/src/main/resources/mtarest.yaml' + - 'multiapps-controller-api/src/main/resources/mtarest_v2.yaml' + +permissions: + contents: write + +env: + # Swagger UI - pinned for reproducibility. To upgrade: bump the version, download the + # tarball, recompute sha256sum, update both values below, and test locally first. + SWAGGER_UI_VERSION: '5.32.11' + SWAGGER_UI_SHA256: '966b7c7ea3bc98af2f5f125dac3a971973df20ed1f9c40707d846200d8b462a6' + V1_YAML: 'multiapps-controller-api/src/main/resources/mtarest.yaml' + V2_YAML: 'multiapps-controller-api/src/main/resources/mtarest_v2.yaml' + +jobs: + publish: + runs-on: ubuntu-24.04 + steps: + - name: Checkout master + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: master + # Full history so we can diff info.version against the previous commit (3b guard). + fetch-depth: 0 + + - name: Checkout gh-pages + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: gh-pages + path: gh-pages + fetch-depth: 1 + + # Extract info.version from each generated YAML + - name: Extract API versions + id: api_versions + run: | + set -euo pipefail + extract_version() { + # info.version is a 2-space-indented, quoted field under the top-level info: block. + local file="$1" + local v + v=$(grep -m1 -E '^ version:' "$file" | sed -E 's/^ version:[[:space:]]*"?([^"[:space:]]+)"?.*/\1/') + if [ -z "$v" ]; then + echo "ERROR: could not extract info.version from $file" >&2 + exit 1 + fi + # Allowlist: the version becomes a directory name and is interpolated into generated + # HTML. Restrict to semver-ish tokens so it can never carry path or HTML metacharacters. + if ! printf '%s' "$v" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*$'; then + echo "ERROR: info.version '$v' in $file is not a valid version string." >&2 + exit 1 + fi + echo "$v" + } + V1_VERSION=$(extract_version "master/${V1_YAML}") + V2_VERSION=$(extract_version "master/${V2_YAML}") + echo "v1_version=$V1_VERSION" >> "$GITHUB_OUTPUT" + echo "v2_version=$V2_VERSION" >> "$GITHUB_OUTPUT" + echo "Detected API v1: $V1_VERSION, v2: $V2_VERSION" + + # 3b guard: fail if API content changed without a version bump + - name: Guard against unbumped API version + env: + V1_VERSION: ${{ steps.api_versions.outputs.v1_version }} + V2_VERSION: ${{ steps.api_versions.outputs.v2_version }} + run: | + set -euo pipefail + cd master + + # Deliberate same-version republish escape hatch. + COMMIT_MSG=$(git log -1 --pretty=%B) + if printf '%s' "$COMMIT_MSG" | grep -qF '[docs-republish]'; then + echo "Escape hatch [docs-republish] present - skipping unbumped-version guard." + exit 0 + fi + + prev_version() { + # Read info.version from the previous commit's copy of the file (empty if new). + local file="$1" + git show "HEAD~1:$file" 2>/dev/null \ + | grep -m1 -E '^ version:' \ + | sed -E 's/^ version:[[:space:]]*"?([^"[:space:]]+)"?.*/\1/' || true + } + + check() { + local file="$1" curr="$2" label="$3" + # Did the file change in this push relative to the previous commit? + if git diff --quiet "HEAD~1" -- "$file" 2>/dev/null; then + echo "$label: unchanged - ok." + return 0 + fi + local prev + prev=$(prev_version "$file") + if [ -z "$prev" ]; then + echo "$label: new file (version $curr) - ok." + return 0 + fi + if [ "$prev" = "$curr" ]; then + echo "ERROR: $label changed but info.version is still '$curr'." >&2 + echo "Bump the version in multiapps-controller-api/pom.xml and regenerate the YAML," >&2 + echo "or add [docs-republish] to the commit message for a deliberate same-version overwrite." >&2 + exit 1 + fi + echo "$label: version bumped $prev -> $curr - ok." + } + + check "${V1_YAML}" "$V1_VERSION" "mtarest.yaml (v1)" + check "${V2_YAML}" "$V2_VERSION" "mtarest_v2.yaml (v2)" + + # Strategy B: fetch + SHA-256 verify Swagger UI, then vendor into gh-pages + - name: Fetch and verify Swagger UI + run: | + set -euo pipefail + TARBALL="swagger-ui-dist-${SWAGGER_UI_VERSION}.tgz" + URL="https://registry.npmjs.org/swagger-ui-dist/-/${TARBALL}" + echo "Downloading $URL" + curl -fsSL "$URL" -o "$TARBALL" + + echo "${SWAGGER_UI_SHA256} ${TARBALL}" | sha256sum -c - + + VENDOR_DIR="gh-pages/vendor/swagger-ui/${SWAGGER_UI_VERSION}" + rm -rf "$VENDOR_DIR" + mkdir -p "$VENDOR_DIR" + tar -xzf "$TARBALL" \ + -C "$VENDOR_DIR" --strip-components=1 \ + package/swagger-ui.css \ + package/swagger-ui-bundle.js \ + package/swagger-ui-standalone-preset.js + echo "Vendored Swagger UI ${SWAGGER_UI_VERSION} into ${VENDOR_DIR}" + + - name: Generate third-party license notice + run: | + set -euo pipefail + mkdir -p gh-pages/vendor + cat > gh-pages/vendor/THIRD-PARTY-LICENSES.txt </dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -Vr | tr '\n' ' ') + V2_VERSIONS=$(ls -1 gh-pages/api/v2/ 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -Vr | tr '\n' ' ') + + cards() { + # $1 space-separated versions, $2 url prefix (e.g. "" or "v2/") + local versions="$1" prefix="$2" latest="" out="" + for v in $versions; do latest="$v"; break; done + for v in $versions; do + local badge="" + if [ "$v" = "$latest" ]; then + badge=' latest' + fi + out="${out}
  • v${v}${badge}
  • "$'\n' + done + printf '%s' "$out" + } + + V1_CARDS=$(cards "$V1_VERSIONS" "") + V2_CARDS=$(cards "$V2_VERSIONS" "v2/") + + # Substitute the card blocks into the template with python3 so the HTML markup + # is inserted literally (never re-interpreted by the shell). + V1_CARDS="$V1_CARDS" V2_CARDS="$V2_CARDS" \ + python3 - "$INDEX_TEMPLATE" gh-pages/api/index.html <<'PY' + import os, sys + tmpl, out = sys.argv[1], sys.argv[2] + html = open(tmpl, encoding='utf-8').read() + html = html.replace('__V1_CARDS__', os.environ['V1_CARDS']) + html = html.replace('__V2_CARDS__', os.environ['V2_CARDS']) + open(out, 'w', encoding='utf-8').write(html) + PY + echo "Written gh-pages/api/index.html" + + # Commit + push (scoped; never git add -A) + - name: Commit and push gh-pages + env: + V1_VERSION: ${{ steps.api_versions.outputs.v1_version }} + V2_VERSION: ${{ steps.api_versions.outputs.v2_version }} + run: | + set -euo pipefail + cd gh-pages + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + touch .nojekyll + git add api/ vendor/ .nojekyll + if git diff --cached --quiet; then + echo "No documentation changes to publish." + else + SHA=$(cd ../master && git rev-parse --short HEAD) + git commit -m "docs: publish API v${V1_VERSION} / v${V2_VERSION} from ${SHA}" + git pull --rebase origin gh-pages + git push + fi diff --git a/README.md b/README.md index 991574a317..fea37d5fc5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The project has also provided [schema support](https://github.com/cloudfoundry-i # Components ## multiapps-controller-api -Contains the Swagger generated definitions of the REST API models and endpoints. The complete Swagger definitions can be found at: https://app.swaggerhub.com/apis/SAP53/mtarest/1.3.0 and https://app.swaggerhub.com/apis/SAP53/mtarest2.0/2.0.0 +Contains the Swagger generated definitions of the REST API models and endpoints. The interactive API reference and the machine-readable OpenAPI specifications are published at: https://cloudfoundry.github.io/multiapps-controller/api/ ## multiapps-controller-client Extends the [Java Client Library for Cloud Foundry](https://github.com/SAP/cf-java-client-sap) with additional domain model objects and attributes, OAuth token providers and retrying functionality.