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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 63 additions & 41 deletions .github/workflows/update-idp-openapi-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,58 +35,74 @@
- name: Install dependencies
run: uv sync

- name: Check current IdP OpenAPI spec info
id: current-spec
run: |
if [ -f "resources/idp-openapi-spec.json" ]; then
CURRENT_VERSION=$(jq -r '.info.version // "unknown"' resources/idp-openapi-spec.json)
CURRENT_PATHS=$(jq '.paths | length' resources/idp-openapi-spec.json)
echo "current-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "current-paths=$CURRENT_PATHS" >> $GITHUB_OUTPUT
echo "Current IdP spec version: $CURRENT_VERSION, paths: $CURRENT_PATHS"
else
echo "current-version=none" >> $GITHUB_OUTPUT
echo "current-paths=0" >> $GITHUB_OUTPUT
echo "No existing IdP OpenAPI spec found"
fi

- name: Update IdP OpenAPI specification
id: update-spec
run: |
echo "Updating IdP OpenAPI specification..."
uv run python scripts/update_idp_openapi_spec.py

# Check if IdP OpenAPI spec was changed
if git diff --quiet resources/idp-openapi-spec.json resources/idp-openapi-spec.yaml; then
echo "has-changes=false" >> $GITHUB_OUTPUT
echo "No changes detected in IdP OpenAPI specification"
- name: Determine IdP spec branch name
id: branch-info
run: |
SPEC_HASH="${{ steps.update-spec.outputs.spec-sha256 }}"
if [ -z "$SPEC_HASH" ] || [ "$SPEC_HASH" = "unknown" ]; then
if [ -f "resources/idp-openapi-spec.json" ]; then
SPEC_HASH=$(sha256sum resources/idp-openapi-spec.json | cut -d' ' -f1)
else
SPEC_HASH="${{ github.run_id }}"
fi
fi
BRANCH="update-idp-openapi-spec-${SPEC_HASH:0:12}"
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
echo "TARGET_BRANCH=$BRANCH" >> $GITHUB_ENV
echo "Using branch $BRANCH for this update"

- name: Check for existing IdP spec PR
id: existing-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ steps.update-spec.outputs.has-changes }}" != "true" ]; then
echo "exists=false" >> $GITHUB_OUTPUT
exit 0
fi

BRANCH="${{ steps.branch-info.outputs.branch }}"
if [ -z "$BRANCH" ]; then
echo "exists=false" >> $GITHUB_OUTPUT
exit 0
fi

if ! command -v gh >/dev/null 2>&1; then
echo "GitHub CLI not available - assuming no existing PR"
echo "exists=false" >> $GITHUB_OUTPUT
exit 0
fi

PR_COUNT=$(gh pr list --state open --head "$BRANCH" --repo "$GITHUB_REPOSITORY" --json number --jq 'length')
if [ "$PR_COUNT" -gt 0 ]; then
echo "An open PR already exists for $BRANCH (count: $PR_COUNT)"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "has-changes=true" >> $GITHUB_OUTPUT
echo "Changes detected in IdP OpenAPI specification"

# Get new spec info
NEW_VERSION=$(jq -r '.info.version // "unknown"' resources/idp-openapi-spec.json)
NEW_PATHS=$(jq '.paths | length' resources/idp-openapi-spec.json)
echo "new-version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "new-paths=$NEW_PATHS" >> $GITHUB_OUTPUT
echo "New IdP spec version: $NEW_VERSION, paths: $NEW_PATHS"
echo "exists=false" >> $GITHUB_OUTPUT
fi

- name: Create Pull Request
if: steps.update-spec.outputs.has-changes == 'true'
if: steps.update-spec.outputs.has-changes == 'true' && steps.existing-pr.outputs.exists != 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: |
chore: update Authlete IdP OpenAPI specification

- Updated from Authlete IdP API (https://login.authlete.com/v3/api-docs)
- Version: ${{ steps.current-spec.outputs.current-version }} → ${{ steps.update-spec.outputs.new-version }}
- Paths: ${{ steps.current-spec.outputs.current-paths }} → ${{ steps.update-spec.outputs.new-paths }}
- Added both YAML and JSON formats for reference
- Version: ${{ steps.update-spec.outputs.current-version }} → ${{ steps.update-spec.outputs.new-version }}
- Paths: ${{ steps.update-spec.outputs.current-paths }} → ${{ steps.update-spec.outputs.new-paths }}
- Generated YAML and JSON specifications for reference
- Content hash: ${{ steps.update-spec.outputs.spec-sha256 }}

Auto-generated by update-idp-openapi-spec workflow
branch: update-idp-openapi-spec-${{ github.run_number }}
branch: ${{ steps.branch-info.outputs.branch }}
delete-branch: true
title: "chore: update Authlete IdP OpenAPI specification to v${{ steps.update-spec.outputs.new-version }}"
body: |
Expand All @@ -95,10 +111,11 @@
This PR updates the Authlete IdP OpenAPI specification from the latest IdP API documentation.

**Changes:**
- **Version:** `${{ steps.current-spec.outputs.current-version }}` → \
- **Version:** `${{ steps.update-spec.outputs.current-version }}` → \
`${{ steps.update-spec.outputs.new-version }}`
- **API Endpoints:** `${{ steps.current-spec.outputs.current-paths }}` → \
- **API Endpoints:** `${{ steps.update-spec.outputs.current-paths }}` → \
`${{ steps.update-spec.outputs.new-paths }}`
- **Content Hash:** `${{ steps.update-spec.outputs.spec-sha256 }}`
- **Source:** https://login.authlete.com/v3/api-docs

**Files Updated:**
Expand Down Expand Up @@ -139,12 +156,17 @@
- name: Summary
run: |
if [ "${{ steps.update-spec.outputs.has-changes }}" = "true" ]; then
echo "✅ Authlete IdP OpenAPI spec updated successfully"
echo "📊 Version: ${{ steps.current-spec.outputs.current-version }} → \
${{ steps.update-spec.outputs.new-version }}"
echo "🔧 Paths: ${{ steps.current-spec.outputs.current-paths }} → ${{ steps.update-spec.outputs.new-paths }}"
echo "📋 PR created with both YAML and JSON formats"
echo "🔧 Available for reference in future IdP API integrations"
if [ "${{ steps.existing-pr.outputs.exists }}" = "true" ]; then
echo "ℹ️ IdP spec update matches an existing open PR."
echo "🔁 Reusing branch ${{ steps.branch-info.outputs.branch }}"
else
echo "✅ Authlete IdP OpenAPI spec updated successfully"
echo "📊 Version: ${{ steps.update-spec.outputs.current-version }} → \
${{ steps.update-spec.outputs.new-version }}"
echo "🔧 Paths: ${{ steps.update-spec.outputs.current-paths }} → ${{ steps.update-spec.outputs.new-paths }}"

Check warning on line 166 in .github/workflows/update-idp-openapi-spec.yml

View workflow job for this annotation

GitHub Actions / test / lint-yaml

166:121 [line-length] line too long (121 > 120 characters)

Check warning on line 166 in .github/workflows/update-idp-openapi-spec.yml

View workflow job for this annotation

GitHub Actions / lint-yaml

166:121 [line-length] line too long (121 > 120 characters)
echo "📋 PR created with refreshed YAML/JSON spec"
echo "🔧 Available for reference in future IdP API integrations"
fi
else
echo "ℹ️ Authlete IdP OpenAPI spec is already up to date"
fi
101 changes: 72 additions & 29 deletions scripts/update_idp_openapi_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
"""

import asyncio
import hashlib
import json
import os
import sys
from pathlib import Path
from typing import Any

import httpx
import yaml
Expand All @@ -20,6 +23,12 @@
IDP_OPENAPI_SPEC_JSON_FILE = RESOURCES_DIR / "idp-openapi-spec.json"


def compute_spec_hash(spec_data: dict) -> str:
"""Compute a stable hash of the specification for change detection."""
normalized = json.dumps(spec_data, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()


async def download_idp_openapi_spec() -> dict:
"""Download the IdP OpenAPI spec JSON from Authlete IdP API."""
print(f"Downloading IdP OpenAPI spec from {AUTHLETE_IDP_SPEC_URL}...")
Expand Down Expand Up @@ -54,39 +63,46 @@ def save_idp_openapi_spec(spec_data: dict) -> None:
print(f"IdP OpenAPI spec saved to {IDP_OPENAPI_SPEC_JSON_FILE} and {IDP_OPENAPI_SPEC_FILE}")


def check_for_changes(new_spec: dict) -> bool:
"""Check if the downloaded IdP spec is different from the existing one."""
if not IDP_OPENAPI_SPEC_JSON_FILE.exists():
print("No existing IdP OpenAPI spec found - will create new file")
return True
def load_existing_spec() -> dict | None:
"""Load the previously stored IdP spec if it exists."""
if IDP_OPENAPI_SPEC_JSON_FILE.exists():
loader = json.load
source = IDP_OPENAPI_SPEC_JSON_FILE
elif IDP_OPENAPI_SPEC_FILE.exists():
loader = yaml.safe_load
source = IDP_OPENAPI_SPEC_FILE
else:
return None

try:
with open(IDP_OPENAPI_SPEC_JSON_FILE, encoding="utf-8") as f:
existing_spec = json.load(f)
with open(source, encoding="utf-8") as handle:
return loader(handle)
except (json.JSONDecodeError, yaml.YAMLError, OSError):
return None

# Compare versions if available
existing_version = existing_spec.get("info", {}).get("version")
new_version = new_spec.get("info", {}).get("version")

if existing_version and new_version:
if existing_version != new_version:
print(f"Version change detected: {existing_version} -> {new_version}")
return True
def extract_metadata(spec_data: dict | None) -> tuple[str, int]:
"""Return version and path count metadata."""
if not isinstance(spec_data, dict):
return ("unknown", 0)

# Compare paths count as a simple check
existing_paths = len(existing_spec.get("paths", {}))
new_paths = len(new_spec.get("paths", {}))
info = spec_data.get("info") or {}
version = info.get("version", "unknown")
paths_count = len(spec_data.get("paths", {}) or {})
return (version, paths_count)

if existing_paths != new_paths:
print(f"Path count changed: {existing_paths} -> {new_paths}")
return True

print("No significant changes detected")
return False
def emit_github_outputs(outputs: dict[str, Any]) -> None:
"""Emit outputs for GitHub Actions, if available."""
output_path = os.environ.get("GITHUB_OUTPUT")
if not output_path:
return

except (json.JSONDecodeError, FileNotFoundError):
print("Error reading existing spec - will update")
return True
with open(output_path, "a", encoding="utf-8") as handle:
for key, value in outputs.items():
if value is None:
continue
handle.write(f"{key}={value}\n")


async def main():
Expand All @@ -102,25 +118,52 @@ async def main():
print("WARNING: Downloaded data doesn't appear to be an OpenAPI spec")
print(f"Keys found: {list(spec_data.keys())}")

# Check for changes
has_changes = check_for_changes(spec_data)
existing_spec = load_existing_spec()
new_spec_hash = compute_spec_hash(spec_data)
existing_spec_hash = compute_spec_hash(existing_spec) if existing_spec else ""
current_version, current_paths = extract_metadata(existing_spec)
new_version, new_paths = extract_metadata(spec_data)

has_changes = new_spec_hash != existing_spec_hash

if not has_changes:
print("No changes detected - IdP OpenAPI spec is up to date")
emit_github_outputs(
{
"has-changes": "false",
"current-version": current_version,
"current-paths": current_paths,
"new-version": new_version,
"new-paths": new_paths,
"spec-sha256": new_spec_hash,
}
)
return

# Save the spec
save_idp_openapi_spec(spec_data)

# Print some info about the spec
info = spec_data.get("info", {})

print("Updated IdP OpenAPI spec:")
print(f" Title: {info.get('title', 'Unknown')}")
print(f" Version: {info.get('version', 'Unknown')}")
print(f" Paths: {len(spec_data.get('paths', {}))}")
print(f" Version: {new_version}")
print(f" Paths: {new_paths}")

print("IdP OpenAPI spec update completed successfully!")

emit_github_outputs(
{
"has-changes": "true",
"current-version": current_version,
"current-paths": current_paths,
"new-version": new_version,
"new-paths": new_paths,
"spec-sha256": new_spec_hash,
}
)

except Exception as e:
print(f"ERROR: Failed to update IdP OpenAPI spec: {e}")
sys.exit(1)
Expand Down
Loading