[AUTO] Import OpenAPI to Jentic Public APIs: #8148
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Import OpenAPI Spec to Public APIs | |
| on: | |
| issues: | |
| types: [ opened, reopened ] | |
| workflow_dispatch: | |
| inputs: | |
| url: | |
| description: 'URL of OpenAPI spec to import' | |
| required: true | |
| vendor: | |
| description: 'Vendor name to use for the API' | |
| required: true | |
| api: | |
| description: 'API name (optional)' | |
| required: false | |
| default: 'main' # Default to 'main' if not provided | |
| reject_invalid_server_urls: | |
| description: 'Reject specs with invalid/relative server URLs (optional, default: true)' | |
| required: false | |
| type: boolean | |
| default: true | |
| reject_invalid_security: | |
| description: 'Reject specs with invalid/missing security fields (optional, default: true)' | |
| required: false | |
| type: boolean | |
| default: true | |
| jobs: | |
| generate-repo-structure: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # To push the new branch and commit files | |
| pull-requests: write # To create the pull request | |
| issues: write # To comment on the issue (if needed) | |
| steps: | |
| - name: Check out the repository | |
| uses: actions/checkout@v4 | |
| - name: Extract Info from inputs | |
| # Only run if not coming from issue | |
| if: github.event_name == 'workflow_dispatch' | |
| id: extract_info_from_inputs | |
| run: | | |
| # set -x # Enable debugging output | |
| URL="${{ inputs.url }}" | |
| echo "URL: $URL" | |
| if [ -z "$URL" ]; then | |
| echo "::error::Could not extract required 'import_oas_url:' field from input." | |
| echo "extract_failed=true" >> $GITHUB_OUTPUT | |
| echo "error_message=Could not extract required 'import_oas_url:' field from input." >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Extract Vendor Name (Required) | |
| VENDOR_NAME=${{ inputs.vendor }} | |
| echo "Vendor Name: $VENDOR_NAME" | |
| if [ -z "$VENDOR_NAME" ]; then | |
| echo "::error::Could not extract required 'vendor_name:' field from input." | |
| echo "extract_failed=true" >> $GITHUB_OUTPUT | |
| echo "error_message=Could not extract required 'vendor_name:' field from input." >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Extract API Name | |
| API_NAME=${{ inputs.api }} | |
| echo "API Name: $API_NAME" | |
| # Set the Apitools Label (always the vendor name now) | |
| APITOOLS_LABEL="${VENDOR_NAME}/${API_NAME}" | |
| echo "Using provided Vendor Name as Apitools label: $APITOOLS_LABEL" | |
| # Extract reject_invalid_server_urls (optional, defaults to true) | |
| REJECT_INVALID_SERVER_URLS="${{ inputs.reject_invalid_server_urls }}" | |
| echo "Reject Invalid Server URLs: $REJECT_INVALID_SERVER_URLS" | |
| # Extract reject_invalid_security (optional, defaults to true) | |
| REJECT_INVALID_SECURITY="${{ inputs.reject_invalid_security }}" | |
| echo "Reject Invalid secuirty: $REJECT_INVALID_SECURITY" | |
| echo "Extracted URL: $URL" | |
| echo "Apitools Label: $APITOOLS_LABEL" | |
| echo "import_oas_url=$URL" >> $GITHUB_OUTPUT | |
| echo "apitools_label=$APITOOLS_LABEL" >> $GITHUB_OUTPUT | |
| echo "reject_invalid_server_urls=$REJECT_INVALID_SERVER_URLS" >> $GITHUB_OUTPUT | |
| echo "reject_invalid_security=$REJECT_INVALID_SECURITY" >> $GITHUB_OUTPUT | |
| - name: Extract Info from Issue | |
| # Only run if the issue body contains the trigger string | |
| if: contains(github.event.issue.body, 'import_oas_url:') | |
| id: extract_info_from_issue | |
| run: | | |
| # set -x # Enable debugging output | |
| echo "Parsing REQUIRED OpenAPI URL and Vendor Name from issue..." | |
| BODY="${{ github.event.issue.body }}" | |
| BODY=$(echo "$BODY" | tr -d '\r') | |
| # Strip leading whitespace from each line to allow indented fields | |
| BODY_STRIPPED=$(echo "$BODY" | sed 's/^[[:space:]]*//') | |
| # Extract URL (Required) | |
| URL=$(echo "$BODY_STRIPPED" | grep -oP '(?<=^import_oas_url:).*' | head -n 1 | xargs) | |
| echo "URL after grep/xargs: $URL" | |
| if [ -z "$URL" ]; then | |
| echo "::error::Could not extract required 'import_oas_url:' field from issue body." | |
| echo "extract_failed=true" >> $GITHUB_OUTPUT | |
| echo "error_message=Could not extract required 'import_oas_url:' field from issue body. Please ensure your issue contains a line with 'import_oas_url: <your-url>'." >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Extract Vendor Name (Required) | |
| VENDOR_NAME=$(echo "$BODY_STRIPPED" | grep -oP '(?<=^vendor_name:).*' | head -n 1 | xargs) | |
| echo "Vendor Name after grep/xargs: $VENDOR_NAME" | |
| if [ -z "$VENDOR_NAME" ]; then | |
| echo "::error::Could not extract required 'vendor_name:' field from issue body." | |
| echo "extract_failed=true" >> $GITHUB_OUTPUT | |
| echo "error_message=Could not extract required 'vendor_name:' field from issue body. Please ensure your issue contains a line with 'vendor_name: <your-vendor-name>'." >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Extract reject_invalid_server_urls (optional, defaults to true) | |
| REJECT_INVALID_SERVER_URLS=$(echo "$BODY_STRIPPED" | grep -oP '(?<=^reject_invalid_server_urls:).*' | head -n 1 | xargs || true) | |
| if [ -z "$REJECT_INVALID_SERVER_URLS" ]; then | |
| REJECT_INVALID_SERVER_URLS="true" | |
| fi | |
| echo "Reject Invalid Server URLs: $REJECT_INVALID_SERVER_URLS" | |
| # Extract reject_invalid_security (optional, defaults to true) | |
| REJECT_INVALID_SECURITY=$(echo "$BODY_STRIPPED" | grep -oP '(?<=^reject_invalid_security:).*' | head -n 1 | xargs || true) | |
| if [ -z "$REJECT_INVALID_SECURITY" ]; then | |
| REJECT_INVALID_SECURITY="true" | |
| fi | |
| echo "Reject Invalid security: $REJECT_INVALID_SECURITY" | |
| # Set the Apitools Label (always the vendor name now) | |
| APITOOLS_LABEL="$VENDOR_NAME" | |
| echo "Using provided Vendor Name as Apitools label: $APITOOLS_LABEL" | |
| echo "Extracted URL: $URL" | |
| echo "Apitools Label: $APITOOLS_LABEL" | |
| echo "import_oas_url=$URL" >> $GITHUB_OUTPUT | |
| echo "apitools_label=$APITOOLS_LABEL" >> $GITHUB_OUTPUT | |
| echo "reject_invalid_server_urls=$REJECT_INVALID_SERVER_URLS" >> $GITHUB_OUTPUT | |
| echo "reject_invalid_security=$REJECT_INVALID_SECURITY" >> $GITHUB_OUTPUT | |
| - name: Consolidate extracted info | |
| if: always() | |
| id: extract_info | |
| run: | | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| echo "import_oas_url=${{ steps.extract_info_from_inputs.outputs.import_oas_url }}" >> $GITHUB_OUTPUT | |
| echo "apitools_label=${{ steps.extract_info_from_inputs.outputs.apitools_label }}" >> $GITHUB_OUTPUT | |
| echo "reject_invalid_server_urls=${{ steps.extract_info_from_inputs.outputs.reject_invalid_server_urls }}" >> $GITHUB_OUTPUT | |
| echo "reject_invalid_security=${{ steps.extract_info_from_inputs.outputs.reject_invalid_security }}" >> $GITHUB_OUTPUT | |
| echo "extract_failed=${{ steps.extract_info_from_inputs.outputs.extract_failed }}" >> $GITHUB_OUTPUT | |
| echo "error_message=${{ steps.extract_info_from_inputs.outputs.error_message }}" >> $GITHUB_OUTPUT | |
| LABEL="${{ steps.extract_info_from_inputs.outputs.apitools_label }}" | |
| SAFE_LABEL=$(echo "$LABEL" | tr '/' '-') | |
| echo "pr_ref=$LABEL" >> $GITHUB_OUTPUT | |
| echo "branch_name=feat/import-oas-$SAFE_LABEL" >> $GITHUB_OUTPUT | |
| echo "closes_line=" >> $GITHUB_OUTPUT | |
| echo "has_issue=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "import_oas_url=${{ steps.extract_info_from_issue.outputs.import_oas_url }}" >> $GITHUB_OUTPUT | |
| echo "apitools_label=${{ steps.extract_info_from_issue.outputs.apitools_label }}" >> $GITHUB_OUTPUT | |
| echo "reject_invalid_server_urls=${{ steps.extract_info_from_issue.outputs.reject_invalid_server_urls }}" >> $GITHUB_OUTPUT | |
| echo "reject_invalid_security=${{ steps.extract_info_from_issue.outputs.reject_invalid_security }}" >> $GITHUB_OUTPUT | |
| echo "extract_failed=${{ steps.extract_info_from_issue.outputs.extract_failed }}" >> $GITHUB_OUTPUT | |
| echo "error_message=${{ steps.extract_info_from_issue.outputs.error_message }}" >> $GITHUB_OUTPUT | |
| ISSUE_NUM="${{ github.event.issue.number }}" | |
| echo "pr_ref=Issue #${ISSUE_NUM}" >> $GITHUB_OUTPUT | |
| echo "branch_name=feat/import-oas-${ISSUE_NUM}" >> $GITHUB_OUTPUT | |
| echo "closes_line=Closes #${ISSUE_NUM}" >> $GITHUB_OUTPUT | |
| echo "has_issue=true" >> $GITHUB_OUTPUT | |
| echo "issue_number=${ISSUE_NUM}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Install jentic-apitools-cli | |
| run: pip install --quiet jentic-apitools-cli | |
| - name: Run Apitools Import CLI and Produce ZIP | |
| id: call_apitools | |
| env: | |
| # Untrusted inputs (label/URL originate from the issue body) are passed | |
| # via env and quoted in the script — never interpolated into run: directly. | |
| LABEL: ${{ steps.extract_info.outputs.apitools_label }} | |
| IMPORT_URL: ${{ steps.extract_info.outputs.import_oas_url }} | |
| REJECT_INVALID_SERVER_URLS: ${{ steps.extract_info.outputs.reject_invalid_server_urls }} | |
| REJECT_INVALID_SECURITY: ${{ steps.extract_info.outputs.reject_invalid_security }} | |
| GITHUB_REPOSITORY_SLUG: ${{ github.repository }} | |
| LLM_PROVIDER: ${{ vars.LLM_PROVIDER }} | |
| LIGHT_LLM_PROVIDER: ${{ vars.LIGHT_LLM_PROVIDER }} | |
| LLM_LIGHT_MODEL: ${{ vars.LLM_LIGHT_MODEL }} | |
| AWS_REGION: ${{ vars.AWS_REGION }} | |
| AWS_BEARER_TOKEN_BEDROCK: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }} | |
| run: | | |
| echo "Importing URL: $IMPORT_URL" | |
| echo "Using Apitools Label: $LABEL" | |
| # The deployed service derives the ZIP filename from the label as | |
| # "<vendor>:<api>.zip"; reproduce that so the unzip step's subpath | |
| # logic (replacing ':' with '/') keeps working unchanged. | |
| SAFE_LABEL=$(echo "$LABEL" | tr '/' ':') | |
| # A single-segment label (issue path) maps to "<vendor>/main" in the | |
| # pipeline, so the ZIP filename must be "<vendor>:main.zip" to match. | |
| case "$SAFE_LABEL" in | |
| *:*) : ;; # already vendor:api | |
| *) SAFE_LABEL="$SAFE_LABEL:main" ;; | |
| esac | |
| OUTPUT_ZIP="${SAFE_LABEL}.zip" | |
| # Map the optional reject flags to the CLI's boolean toggles. | |
| REJECT_SERVER_FLAG="--reject-invalid-server-urls" | |
| if [ "$REJECT_INVALID_SERVER_URLS" = "false" ]; then | |
| REJECT_SERVER_FLAG="--no-reject-invalid-server-urls" | |
| fi | |
| REJECT_SECURITY_FLAG="--reject-invalid-security" | |
| if [ "$REJECT_INVALID_SECURITY" = "false" ]; then | |
| REJECT_SECURITY_FLAG="--no-reject-invalid-security" | |
| fi | |
| # --skip-bundle is intentional: the CLI maps bundled_spec = not skip_bundle, | |
| # and the pipeline only bundles when bundled_spec is false. The deployed | |
| # service sends bundled_spec=false, so --skip-bundle reproduces its bundling. | |
| set +e | |
| CLI_STDERR_FILE=$(mktemp) | |
| jentic-apitools import "$IMPORT_URL" \ | |
| --archive "$OUTPUT_ZIP" \ | |
| --label "$LABEL" \ | |
| --skip-bundle \ | |
| --enable-llm-analysis \ | |
| "$REJECT_SERVER_FLAG" \ | |
| "$REJECT_SECURITY_FLAG" \ | |
| --canonical-source-url "$IMPORT_URL" \ | |
| --canonical-artifacts-base-url "https://raw.githubusercontent.com/${GITHUB_REPOSITORY_SLUG}/refs/heads/main/apis/openapi" \ | |
| --canonical-artifacts-base-url-ui "https://github.com/${GITHUB_REPOSITORY_SLUG}/tree/main/apis/openapi" \ | |
| --overwrite \ | |
| -q 1>cli_stdout.txt 2>"$CLI_STDERR_FILE" | |
| CLI_EXIT_CODE=$? | |
| set -e | |
| CLI_STDERR=$(cat "$CLI_STDERR_FILE") | |
| rm -f "$CLI_STDERR_FILE" | |
| echo "CLI stdout:" | |
| cat cli_stdout.txt | |
| echo "CLI stderr: $CLI_STDERR" | |
| if [ "$CLI_EXIT_CODE" -ne 0 ]; then | |
| echo "::error::jentic-apitools import failed with exit code $CLI_EXIT_CODE." | |
| echo "api_failed=true" >> $GITHUB_OUTPUT | |
| echo 'error_response<<EREOF' >> $GITHUB_OUTPUT | |
| echo "${CLI_STDERR:-Import CLI failed with exit code $CLI_EXIT_CODE. No additional error details available.}" >> $GITHUB_OUTPUT | |
| echo 'EREOF' >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| if [ ! -f "$OUTPUT_ZIP" ]; then | |
| echo "::error::Import CLI reported success but ZIP '$OUTPUT_ZIP' was not found on disk." | |
| echo "api_failed=true" >> $GITHUB_OUTPUT | |
| echo "error_response=Import CLI succeeded but the expected ZIP '$OUTPUT_ZIP' was not produced." >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Verify zip file is not empty/tiny (basic check) | |
| if [ $(stat -c%s "$OUTPUT_ZIP") -lt 100 ]; then # Check if file is > 100 bytes | |
| echo "::error::Produced ZIP file seems empty or too small." | |
| ls -l "$OUTPUT_ZIP" | |
| echo "api_failed=true" >> $GITHUB_OUTPUT | |
| echo "error_response=Produced ZIP file from import CLI is empty or too small. The OpenAPI URL may be invalid or inaccessible." >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| echo "Import successful. ZIP file produced at $OUTPUT_ZIP ($(stat -c%s "$OUTPUT_ZIP") bytes)." | |
| # Remove filename extension, Replace all ':' with '/' to point to API endpoint location | |
| FILE_STRUCTURE=${OUTPUT_ZIP%.zip} | |
| ACTUAL_CONTENT_SUBPATH_IN_ZIP=${FILE_STRUCTURE//:/\/} | |
| echo "zip_file=$OUTPUT_ZIP" >> $GITHUB_OUTPUT | |
| echo "actual_content_subpath_in_zip=$ACTUAL_CONTENT_SUBPATH_IN_ZIP" >> $GITHUB_OUTPUT | |
| - name: Unzip Generated Files | |
| id: unzip_files | |
| run: | | |
| ZIP_FILE="${{ steps.call_apitools.outputs.zip_file }}" | |
| ACTUAL_CONTENT_SUBPATH_IN_ZIP="${{ steps.call_apitools.outputs.actual_content_subpath_in_zip }}" | |
| EXTRACT_TEMP_DIR="openapi_temp_extract" | |
| FINAL_OAK_TARGET_BASE="apis/openapi" | |
| echo "Ensuring temporary extraction directory $EXTRACT_TEMP_DIR exists and is empty..." | |
| rm -rf "$EXTRACT_TEMP_DIR" | |
| mkdir -p "$EXTRACT_TEMP_DIR" | |
| echo "Unzipping $ZIP_FILE into $EXTRACT_TEMP_DIR..." | |
| unzip -o "$ZIP_FILE" -d "$EXTRACT_TEMP_DIR" | |
| if [ $? -ne 0 ]; then | |
| echo "::error::Failed to unzip $ZIP_FILE into $EXTRACT_TEMP_DIR." | |
| exit 1 | |
| fi | |
| echo "Successfully unzipped files into temporary directory $EXTRACT_TEMP_DIR." | |
| echo "Contents of $EXTRACT_TEMP_DIR:" | |
| ls -lA "$EXTRACT_TEMP_DIR" | |
| SOURCE_API_CONTENT_PATH="$EXTRACT_TEMP_DIR/$ACTUAL_CONTENT_SUBPATH_IN_ZIP" | |
| OAK_API_CONTENT_PATH="$FINAL_OAK_TARGET_BASE/$ACTUAL_CONTENT_SUBPATH_IN_ZIP" | |
| echo "Determined actual content subpath in zip: $ACTUAL_CONTENT_SUBPATH_IN_ZIP" | |
| echo "Full source API content path (in temp extract): $SOURCE_API_CONTENT_PATH" | |
| echo "Full OAK API content path (target in repo): $OAK_API_CONTENT_PATH" | |
| if [ ! -d "$SOURCE_API_CONTENT_PATH" ]; then | |
| echo "::error::Source API content path '$SOURCE_API_CONTENT_PATH' does not exist after unzipping. Check zip structure and label logic." | |
| echo "Contents of $EXTRACT_TEMP_DIR:" | |
| ls -lA "$EXTRACT_TEMP_DIR" | |
| FIRST_PART_OF_LABEL=$(echo "$ACTUAL_CONTENT_SUBPATH_IN_ZIP" | cut -d'/' -f1) | |
| echo "Contents of $EXTRACT_TEMP_DIR/$FIRST_PART_OF_LABEL:" | |
| ls -lA "$EXTRACT_TEMP_DIR/$FIRST_PART_OF_LABEL" | |
| exit 1 | |
| fi | |
| echo "extract_temp_dir=$EXTRACT_TEMP_DIR" >> $GITHUB_OUTPUT | |
| echo "source_api_content_path=$SOURCE_API_CONTENT_PATH" >> $GITHUB_OUTPUT | |
| echo "oak_api_content_path=$OAK_API_CONTENT_PATH" >> $GITHUB_OUTPUT | |
| - name: Check for Duplicate API Version | |
| id: check_duplicate_version | |
| run: | | |
| SOURCE_API_CONTENT_PATH="${{ steps.unzip_files.outputs.source_api_content_path }}" | |
| OAK_API_CONTENT_PATH="${{ steps.unzip_files.outputs.oak_api_content_path }}" | |
| DUPLICATE_FOUND=false | |
| DUPLICATE_VERSIONS="" | |
| while IFS= read -r -d $'\0' source_version_dir_path; do | |
| version_dir_name=$(basename "$source_version_dir_path") | |
| # Try to read the version from meta/meta.json inside the version directory | |
| VERSION_META="$source_version_dir_path/meta/meta.json" | |
| if [ -f "$VERSION_META" ]; then | |
| VERSION_LABEL=$(jq -r '.info.version // empty' "$VERSION_META") | |
| if [ -z "$VERSION_LABEL" ]; then | |
| echo "::warning::meta/meta.json in '$version_dir_name' does not contain info.version. Using directory name as version identifier." | |
| VERSION_LABEL="$version_dir_name" | |
| fi | |
| else | |
| echo "::warning::meta/meta.json not found in '$version_dir_name'. Using directory name as version identifier." | |
| VERSION_LABEL="$version_dir_name" | |
| fi | |
| echo "Checking version directory '$version_dir_name' (version: $VERSION_LABEL)..." | |
| if [ -d "$OAK_API_CONTENT_PATH/$version_dir_name" ]; then | |
| echo "::error::Duplicate detected: version '$VERSION_LABEL' already exists at '$OAK_API_CONTENT_PATH/$version_dir_name/'." | |
| DUPLICATE_FOUND=true | |
| if [ -n "$DUPLICATE_VERSIONS" ]; then | |
| DUPLICATE_VERSIONS="$DUPLICATE_VERSIONS, $VERSION_LABEL" | |
| else | |
| DUPLICATE_VERSIONS="$VERSION_LABEL" | |
| fi | |
| fi | |
| done < <(find "$SOURCE_API_CONTENT_PATH" -mindepth 1 -maxdepth 1 -type d -print0) | |
| echo "duplicate_detected=$DUPLICATE_FOUND" >> $GITHUB_OUTPUT | |
| echo "duplicate_versions=$DUPLICATE_VERSIONS" >> $GITHUB_OUTPUT | |
| if [ "$DUPLICATE_FOUND" = true ]; then | |
| echo "::error::Import rejected: the following API version(s) already exist in the repository: $DUPLICATE_VERSIONS" | |
| exit 1 | |
| fi | |
| echo "No duplicate versions found. Proceeding with import." | |
| - name: Copy Files to Repository | |
| id: copy_files | |
| run: | | |
| SOURCE_API_CONTENT_PATH="${{ steps.unzip_files.outputs.source_api_content_path }}" | |
| OAK_API_CONTENT_PATH="${{ steps.unzip_files.outputs.oak_api_content_path }}" | |
| echo "Ensuring target OAK API directory '$OAK_API_CONTENT_PATH' exists..." | |
| mkdir -p "$OAK_API_CONTENT_PATH" | |
| echo "Copying version directories from '$SOURCE_API_CONTENT_PATH' to '$OAK_API_CONTENT_PATH'..." | |
| find "$SOURCE_API_CONTENT_PATH" -mindepth 1 -maxdepth 1 -type d -print0 | while IFS= read -r -d $'\0' source_version_dir_path; do | |
| version_dir_name=$(basename "$source_version_dir_path") | |
| echo "Copying version directory: '$version_dir_name'" | |
| rsync -a "$source_version_dir_path/" "$OAK_API_CONTENT_PATH/$version_dir_name/" | |
| if [ $? -ne 0 ]; then | |
| echo "::error::Failed to copy version directory '$version_dir_name' using rsync." | |
| exit 1 | |
| fi | |
| done | |
| echo "Version directories copied successfully." | |
| # Handle api-level meta.json | |
| SOURCE_META_JSON="$SOURCE_API_CONTENT_PATH/meta.json" | |
| OAK_META_JSON="$OAK_API_CONTENT_PATH/meta.json" | |
| echo "Handling meta.json..." | |
| echo "Source meta.json: $SOURCE_META_JSON" | |
| echo "OAK target meta.json: $OAK_META_JSON" | |
| if [ ! -f "$OAK_META_JSON" ]; then | |
| echo "$OAK_META_JSON does not exist in OAK. Copying from source..." | |
| if [ -f "$SOURCE_META_JSON" ]; then | |
| cp "$SOURCE_META_JSON" "$OAK_META_JSON" | |
| echo "Copied new meta.json to $OAK_META_JSON." | |
| else | |
| echo "::warning::meta.json not found in source at $SOURCE_META_JSON. Cannot copy." | |
| fi | |
| else | |
| echo "Existing meta.json found at $OAK_META_JSON. Not overwriting." | |
| fi | |
| - name: Clean Up Temporary Files | |
| if: always() && steps.unzip_files.outcome == 'success' | |
| run: | | |
| EXTRACT_TEMP_DIR="${{ steps.unzip_files.outputs.extract_temp_dir }}" | |
| ZIP_FILE="${{ steps.call_apitools.outputs.zip_file }}" | |
| if [ -n "$EXTRACT_TEMP_DIR" ] && [ -d "$EXTRACT_TEMP_DIR" ]; then | |
| echo "Cleaning up temporary extraction directory $EXTRACT_TEMP_DIR..." | |
| rm -rf "$EXTRACT_TEMP_DIR" | |
| fi | |
| if [ -n "$ZIP_FILE" ] && [ -f "$ZIP_FILE" ]; then | |
| echo "Cleaning up ZIP file $ZIP_FILE..." | |
| rm "$ZIP_FILE" | |
| fi | |
| echo "Cleanup complete." | |
| - name: Check Git Status Before PR Action | |
| run: | | |
| echo "--- Git Status After Unzip ---" | |
| git status | |
| echo "--- End Git Status ---" | |
| - name: Find, Prepare, Read, and Remove Repair Log for Commit | |
| id: prepare_repair_log | |
| run: | | |
| # set -x | |
| # Search within the path where files were copied into the repo for commit | |
| SEARCH_PATH="${{ steps.unzip_files.outputs.oak_api_content_path }}" | |
| if [ -z "$SEARCH_PATH" ]; then | |
| echo "::warning::Search path for repair log (oak_api_content_path) is empty. Skipping log processing." | |
| echo "log_found=false" >> $GITHUB_OUTPUT | |
| echo "log_content_base64=" >> $GITHUB_OUTPUT # Ensure output is set | |
| exit 0 # Exit successfully, as this isn't a fatal error for the whole workflow | |
| fi | |
| echo "Searching for repair_log.txt in $SEARCH_PATH and its subdirectories..." | |
| LOG_FILE_CANDIDATE=$(find "$SEARCH_PATH" -name "repair_log.txt" -print -quit) | |
| if [[ -f "$LOG_FILE_CANDIDATE" ]]; then | |
| echo "Repair log found at: $LOG_FILE_CANDIDATE" | |
| echo "--- Start of Repair Log Content ---" | |
| cat "$LOG_FILE_CANDIDATE" | |
| echo "--- End of Repair Log Content ---" | |
| # Read content, encode to base64, and set as output using multiline syntax | |
| LOG_CONTENT_BASE64=$(base64 -w 0 < "$LOG_FILE_CANDIDATE") | |
| echo 'log_content_base64<<EOF' >> "$GITHUB_OUTPUT" | |
| echo "$LOG_CONTENT_BASE64" >> "$GITHUB_OUTPUT" | |
| echo 'EOF' >> "$GITHUB_OUTPUT" | |
| echo "Removing original $LOG_FILE_CANDIDATE to prevent it from being committed..." | |
| rm "$LOG_FILE_CANDIDATE" | |
| if [ $? -eq 0 ]; then | |
| echo "Original $LOG_FILE_CANDIDATE removed successfully." | |
| else | |
| echo "::warning::Failed to remove original $LOG_FILE_CANDIDATE. It might be committed." | |
| fi | |
| echo "log_found=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "repair_log.txt not found in $SEARCH_PATH." | |
| echo "log_found=false" >> $GITHUB_OUTPUT | |
| echo "log_content_base64=" >> $GITHUB_OUTPUT # Ensure output is set | |
| fi | |
| shell: bash | |
| - name: Create Pull Request | |
| id: create_pr_step | |
| uses: peter-evans/create-pull-request@v6 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} # Make sure this token has write permissions | |
| commit-message: "feat: Import OpenAPI spec from ${{ steps.extract_info.outputs.pr_ref }}" | |
| title: "feat: Import OpenAPI spec from ${{ steps.extract_info.outputs.pr_ref }}" | |
| body: | | |
| This PR adds/updates Jentic Public APIs with information from the OpenAPI URL provided in ${{ steps.extract_info.outputs.pr_ref }}. | |
| **Source URL:** ${{ steps.extract_info.outputs.import_oas_url }} | |
| Files were automatically generated by the Jentic OpenAPI import service. | |
| ${{ steps.extract_info.outputs.closes_line }} | |
| branch: ${{ steps.extract_info.outputs.branch_name }} | |
| base: main | |
| delete-branch: true | |
| - name: Display PR URL | |
| if: steps.create_pr_step.outputs.pull-request-url | |
| env: | |
| PR_URL: ${{ steps.create_pr_step.outputs.pull-request-url }} | |
| run: echo "::notice::Import PR:${PR_URL}" | |
| - name: Post Repair Log Comment to PR | |
| if: steps.prepare_repair_log.outputs.log_found == 'true' && steps.create_pr_step.outputs.pull-request-number | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| let logContentBase64 = '${{ steps.prepare_repair_log.outputs.log_content_base64 }}'; | |
| let logContent = ''; | |
| if (logContentBase64) { | |
| try { | |
| logContent = Buffer.from(logContentBase64, 'base64').toString('utf8'); | |
| } catch (error) { | |
| console.error('Error decoding base64 log content:', error); | |
| logContent = 'Error decoding repair log content.'; | |
| } | |
| } else if ('${{ steps.prepare_repair_log.outputs.log_found }}' === 'true') { | |
| logContent = 'Repair log was found but content is missing or could not be retrieved.'; | |
| } else { | |
| // This part of the script should not be reached if log_found is false due to the step's 'if' condition, | |
| // but as a safeguard, or if the 'if' condition were removed. | |
| console.log('Repair log not found or content unavailable, skipping comment.'); | |
| return; // Exit script gracefully | |
| } | |
| const maxLength = 60000; // GitHub comment character limit is ~65535 | |
| let commentBody = `**OpenAPI Spec Repair Log:**\n\n\`\`\`text\n${logContent}\n\`\`\``; | |
| if (commentBody.length > maxLength) { | |
| const header = `**OpenAPI Spec Repair Log (truncated):**\n\n\`\`\`text\n`; | |
| const footer = `\n...\n(Log was truncated due to length.)\n\`\`\``; | |
| const availableLength = maxLength - header.length - footer.length; | |
| const truncatedContent = logContent.substring(0, availableLength); | |
| commentBody = `${header}${truncatedContent}${footer}`; | |
| } | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: ${{ steps.create_pr_step.outputs.pull-request-number }}, | |
| body: commentBody | |
| }); | |
| - name: Comment on Issue if Import Failed | |
| if: failure() && steps.extract_info.outputs.has_issue == 'true' && (steps.extract_info.outputs.extract_failed == 'true' || steps.call_apitools.outputs.api_failed == 'true' || steps.check_duplicate_version.outputs.duplicate_detected == 'true') | |
| uses: actions/github-script@v7 | |
| env: | |
| API_ERROR_RESPONSE: ${{ steps.call_apitools.outputs.error_response }} | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| let errorMessage = "❌ **Failed to import OpenAPI specification**\n\n"; | |
| // Check which step failed and provide appropriate error message | |
| if ('${{ steps.extract_info.outputs.extract_failed }}' === 'true') { | |
| errorMessage += `**Error:** ${{ steps.extract_info.outputs.error_message }}\n\n`; | |
| errorMessage += "Please check your issue format and ensure it contains the required fields:\n"; | |
| errorMessage += "- \`import_oas_url: <your-openapi-url>\`\n"; | |
| errorMessage += "- \`vendor_name: <your-vendor-name>\`\n\n"; | |
| } else if ('${{ steps.call_apitools.outputs.api_failed }}' === 'true') { | |
| const errorResponse = process.env.API_ERROR_RESPONSE || ''; | |
| errorMessage += `**Error:** Import service failed\n\n`; | |
| try { | |
| const response = JSON.parse(errorResponse); | |
| if (response.title) { | |
| errorMessage += `**${response.status || 'Error'} — ${response.title}**\n\n`; | |
| } | |
| if (response.detail) { | |
| errorMessage += `**Details:** ${response.detail}\n\n`; | |
| } | |
| if (response.error_code) { | |
| errorMessage += `**Error code:** \`${response.error_code}\`\n\n`; | |
| } | |
| } catch (e) { | |
| errorMessage += `**Details:** ${errorResponse}\n\n`; | |
| } | |
| errorMessage += "Please check that your OpenAPI URL is accessible and contains a valid OpenAPI specification.\n\n"; | |
| } else if ('${{ steps.check_duplicate_version.outputs.duplicate_detected }}' === 'true') { | |
| const duplicateVersions = `${{ steps.check_duplicate_version.outputs.duplicate_versions }}`; | |
| errorMessage += `**Error:** Duplicate API version(s) detected\n\n`; | |
| errorMessage += `The following version(s) already exist in the repository: **${duplicateVersions}**\n\n`; | |
| errorMessage += "The import was rejected to prevent overwriting existing API data. "; | |
| errorMessage += "If you intended to update an existing version, please remove the existing version first or contact the development team.\n\n"; | |
| } | |
| errorMessage += "If you believe this is a bug with the import service or need additional support, please contact the development team at Jentic Community Discord (https://discord.gg/yrxmDZWMqB).\n\n"; | |
| errorMessage += "Alternatively create a new issue reporting the bug (https://github.com/jentic/jentic-public-apis/issues/new?template=BLANK_ISSUE)."; | |
| await github.rest.issues.createComment({ | |
| issue_number: ${{ steps.extract_info.outputs.issue_number }}, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: errorMessage | |
| }); | |
| - name: Write Error to Workflow Summary | |
| if: failure() && (steps.extract_info.outputs.extract_failed == 'true' || steps.call_apitools.outputs.api_failed == 'true' || steps.check_duplicate_version.outputs.duplicate_detected == 'true') | |
| env: | |
| API_ERROR_RESPONSE: ${{ steps.call_apitools.outputs.error_response }} | |
| EXTRACT_ERROR: ${{ steps.extract_info.outputs.error_message }} | |
| DUPLICATE_VERSIONS: ${{ steps.check_duplicate_version.outputs.duplicate_versions }} | |
| EXTRACT_FAILED: ${{ steps.extract_info.outputs.extract_failed }} | |
| API_FAILED: ${{ steps.call_apitools.outputs.api_failed }} | |
| DUPLICATE_DETECTED: ${{ steps.check_duplicate_version.outputs.duplicate_detected }} | |
| run: | | |
| echo "## ❌ Import Failed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "$EXTRACT_FAILED" = "true" ]; then | |
| echo "**Error:** $EXTRACT_ERROR" >> $GITHUB_STEP_SUMMARY | |
| elif [ "$API_FAILED" = "true" ]; then | |
| echo "**Error:** Import service returned an error." >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo '```json' >> $GITHUB_STEP_SUMMARY | |
| echo "$API_ERROR_RESPONSE" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| elif [ "$DUPLICATE_DETECTED" = "true" ]; then | |
| echo "**Error:** Duplicate API version(s) already exist: $DUPLICATE_VERSIONS" >> $GITHUB_STEP_SUMMARY | |
| fi |