Skip to content

[AUTO] Import OpenAPI to Jentic Public APIs: #8148

[AUTO] Import OpenAPI to Jentic Public APIs:

[AUTO] Import OpenAPI to Jentic Public APIs: #8148

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