diff --git a/.github/workflows/combine-schema.v3.2.yml b/.github/workflows/combine-schema.v3.2.yml index 206451e..fa7fa25 100644 --- a/.github/workflows/combine-schema.v3.2.yml +++ b/.github/workflows/combine-schema.v3.2.yml @@ -15,7 +15,7 @@ on: paths: - '3.2/**' - 'scripts/**' - - '.github/workflows/combine-schema.yml' + - '.github/workflows/combine-schema.v3.2.yml' jobs: combine-schema: @@ -30,61 +30,79 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Git + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' - - name: Install Python dependencies - run: pip install -r scripts/requirements.txt - - - name: Install xmlstarlet - run: sudo apt-get update && sudo apt-get install -y xmlstarlet - - - name: Combine XSD schema into single file - run: python scripts/combine_schema.py $INPUT_FILE $OUTPUT_FILE - - - name: Beautify and normalize XSD with xmlstarlet + - name: Install dependencies run: | - xmlstarlet fo --omit-decl $OUTPUT_FILE > tmp.xml && mv tmp.xml $OUTPUT_FILE - xmlstarlet ed -P -L -u "//xs:documentation/text()" -x "normalize-space(.)" $OUTPUT_FILE - rm -f tmp.xml + sudo apt-get update && sudo apt-get install -y xmlstarlet + pip install -r scripts/requirements.txt - - name: Set artifact path - id: vars - run: echo "path=$OUTPUT_FILE" >> "$GITHUB_OUTPUT" + - name: Combine and clean XSD schema + run: | + set -e + echo "Combining input schema: $INPUT_FILE" + python scripts/combine_schema.py $INPUT_FILE $OUTPUT_FILE - - name: Upload merged XSD as artifact - uses: actions/upload-artifact@v4 - with: - name: combined-xsd - path: ${{ steps.vars.outputs.path }} + echo "Cleaning and formatting schema file..." + xmlstarlet ed -N xs="http://www.w3.org/2001/XMLSchema" \ + -u '//xs:documentation' -x 'normalize-space(.)' "$OUTPUT_FILE" | \ + xmlstarlet fo > "${OUTPUT_FILE}.tmp" + mv "${OUTPUT_FILE}.tmp" "$OUTPUT_FILE" - - name: Commit combined schema - id: commit + - name: Show file content before pull request run: | - git config --global user.name "GitHub Action" - git config --global user.email "action@github.com" - git checkout -b auto/schema-update - git add $OUTPUT_FILE || echo "No changes to add" - git status - if ! git diff --cached --quiet; then - git commit -m "Auto-update combined schema" - git push origin auto/schema-update - echo "changes_present=true" >> "$GITHUB_OUTPUT" - else - echo "No changes to commit." - echo "changes_present=false" >> "$GITHUB_OUTPUT" - fi + echo "Head:" + head -n 20 "$OUTPUT_FILE" + echo "Tail:" + tail -n 20 "$OUTPUT_FILE" - - name: Create pull request with updated schema - if: success() && steps.commit.outputs.changes_present == 'true' - uses: peter-evans/create-pull-request@v5 + - name: Create pull request + id: create-pull-request + uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GITHUB_TOKEN }} - commit-message: Auto-update combined schema - branch: auto/schema-update - title: 'Auto-update combined schema' - body: 'This PR was automatically created by the Combine XSD Schema workflow.' + commit-message: "Auto: Update itop_design.xsd" + committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> + branch: auto/update-schema base: main + title: "Auto: Update itop_design.xsd" + body: "This PR was automatically created by a GitHub Action." + labels: auto-update + draft: false + delete-branch: true + + - name: Enable automerge + uses: "peter-evans/enable-pull-request-automerge@v3" + with: + token: ${{ secrets.GITHUB_TOKEN }} + pull-request-number: ${{ steps.create-pull-request.outputs.pull-request-number }} + merge-method: squash + + - name: Delete branch after merge + if: ${{ steps.create-pull-request.outputs.pull-request-operation == 'updated' || steps.create-pull-request.outputs.pull-request-operation == 'created' }} + run: | + echo "Waiting for merge..." + for i in {1..30}; do + MERGED_AT=$(gh pr view ${{ steps.create-pull-request.outputs.pull-request-url }} --json mergedAt --jq .mergedAt) + if [ "$MERGED_AT" != "null" ]; then + echo "PR merged at $MERGED_AT, deleting branch..." + git push origin --delete auto/update-schema + break + fi + echo "Not merged yet, retrying in 10s... ($i/30)" + sleep 10 + done + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.vscode/settings.json b/.vscode/settings.json index 3dd5224..904da8e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,7 @@ "dashlet", "dashlets", "datamodels", + "evans", "extkey", "fileref", "finalclass", diff --git a/scripts/combine_schema.py b/scripts/combine_schema.py index fa4ad46..56a6334 100644 --- a/scripts/combine_schema.py +++ b/scripts/combine_schema.py @@ -1,18 +1,14 @@ from lxml import etree +from datetime import datetime import os import sys +import tempfile +import shutil XSD_NS = "http://www.w3.org/2001/XMLSchema" NSMAP = {"xs": XSD_NS} INCLUDE_TAG = f"{{{XSD_NS}}}include" -if len(sys.argv) != 3: - print("Usage: python combine_schema.py ") - sys.exit(1) - -MASTER_FILE = sys.argv[1] -OUTPUT_FILE = sys.argv[2] - included_files = set() def resolve_includes(tree, base_path): @@ -24,44 +20,67 @@ def resolve_includes(tree, base_path): continue included_path = os.path.normpath(os.path.join(base_path, href)) if included_path in included_files: + print(f"Already included: {included_path}") root.remove(include) continue - print(f"Including: {included_path}") + print(f"Parsing and including: {included_path}") included_tree = etree.parse(included_path, etree.XMLParser(remove_blank_text=True)) resolve_includes(included_tree, os.path.dirname(included_path)) included_root = included_tree.getroot() for child in included_root: - root.append(child) + if child.tag != INCLUDE_TAG: + root.append(child) root.remove(include) included_files.add(included_path) -os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True) +def main(): + if len(sys.argv) != 3: + print("Usage: python combine_schema.py ") + sys.exit(1) -parser = etree.XMLParser(remove_blank_text=True) -master_tree = etree.parse(MASTER_FILE, parser) -resolve_includes(master_tree, os.path.dirname(MASTER_FILE)) + input_file = sys.argv[1] + output_file = sys.argv[2] -# Add project comment before root -root = master_tree.getroot() -comment = etree.Comment( - """ + print(f"Input file: {input_file}") + print(f"Output file: {output_file}") + + os.makedirs(os.path.dirname(output_file), exist_ok=True) + + parser = etree.XMLParser(remove_blank_text=True) + print("Parsing master schema...") + master_tree = etree.parse(input_file, parser) + resolve_includes(master_tree, os.path.dirname(input_file)) + + root = master_tree.getroot() + comment = etree.Comment( + f""" This schema file was automatically generated from modular XSD components. Provided by the iTop-schema project by Björn Rudner. Project website: https://rudnerbjoern.github.io/iTop-schema/ GitHub repository: https://github.com/rudnerbjoern/iTop-schema + Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC + Use this file to validate your iTop datamodels with confidence and consistency. """ -) -root.addprevious(comment) + ) + root.addprevious(comment) + + # Write to a temporary file first + with tempfile.NamedTemporaryFile("wb", delete=False) as tmp: + temp_path = tmp.name + print(f"Writing combined schema to temporary file: {temp_path}") + master_tree.write( + tmp, + pretty_print=True, + xml_declaration=True, + encoding="UTF-8" + ) -# Write formatted output -etree.ElementTree(root).write( - OUTPUT_FILE, - pretty_print=True, - xml_declaration=True, - encoding="UTF-8" -) + print(f"Moving temporary file to final destination: {output_file}") + shutil.move(temp_path, output_file) + print(f"✅ Combined schema written to: {output_file}") -print(f"✅ Combined schema written to: {OUTPUT_FILE}") +if __name__ == "__main__": + main()