Check Upstream Releases #78
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: Check Upstream Releases | |
| on: | |
| schedule: | |
| - cron: '0 6 * * *' # Run daily at 6 AM UTC | |
| workflow_dispatch: # Allow manual trigger | |
| jobs: | |
| check-upstream: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| ref: stable | |
| - name: Check upstream tags for all parts | |
| id: check | |
| run: | | |
| python3 << 'PYEOF' | |
| import yaml, re, subprocess, json, os | |
| with open('snap/snapcraft.yaml', 'r') as f: | |
| data = yaml.safe_load(f) | |
| parts = data.get('parts', {}) | |
| updates = [] | |
| for part_name, part_data in parts.items(): | |
| if not part_data: | |
| continue | |
| source_tag = part_data.get('source-tag') | |
| source_url = part_data.get('source', '') | |
| if not source_tag or not source_url: | |
| continue | |
| m = re.match(r'https://github\.com/([^/]+/[^/]+?)(?:\.git)?$', source_url) | |
| if not m: | |
| print(f"Skipping {part_name}: not a plain GitHub URL") | |
| continue | |
| repo = m.group(1) | |
| result = subprocess.run( | |
| ['git', 'ls-remote', '--tags', '--sort=-v:refname', | |
| f'https://github.com/{repo}.git'], | |
| capture_output=True, text=True | |
| ) | |
| tags = [ | |
| line.split('refs/tags/')[-1].strip() | |
| for line in result.stdout.strip().split('\n') | |
| if line and not line.endswith('^{}') | |
| ] | |
| if not tags: | |
| print(f"No tags found for {part_name} ({repo})") | |
| continue | |
| latest = tags[0] | |
| current = str(source_tag) | |
| print(f"{part_name}: current={current}, latest={latest}") | |
| if latest != current: | |
| updates.append({'part': part_name, 'repo': repo, | |
| 'current': current, 'latest': latest}) | |
| with open('/tmp/updates.json', 'w') as f: | |
| json.dump(updates, f) | |
| if updates: | |
| print(f"Updates needed: {len(updates)}") | |
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | |
| f.write('update_needed=true\n') | |
| else: | |
| print("All parts up to date") | |
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | |
| f.write('update_needed=false\n') | |
| PYEOF | |
| - name: Apply updates to snapcraft.yaml | |
| if: steps.check.outputs.update_needed == 'true' | |
| run: | | |
| python3 << 'PYEOF' | |
| import json, re | |
| with open('/tmp/updates.json') as f: | |
| updates = json.load(f) | |
| update_map = {u['part']: u['latest'] for u in updates} | |
| with open('snap/snapcraft.yaml', 'r') as f: | |
| lines = f.readlines() | |
| new_lines = [] | |
| current_part = None | |
| in_parts = False | |
| for line in lines: | |
| stripped = line.lstrip() | |
| indent = len(line) - len(stripped) | |
| if line.startswith('parts:'): | |
| in_parts = True | |
| elif in_parts and indent == 2 and not stripped.startswith('#'): | |
| m = re.match(r'([\w][\w-]*):\s*\n', stripped) | |
| if m: | |
| current_part = m.group(1) | |
| if (in_parts and current_part in update_map | |
| and re.match(r'\s+source-tag:', line) | |
| and not stripped.startswith('#')): | |
| new_tag = update_map[current_part] | |
| if "'" in line: | |
| line = re.sub(r"(source-tag:\s*')[^']*(')", rf"\g<1>{new_tag}\g<2>", line) | |
| elif '"' in line: | |
| line = re.sub(r'(source-tag:\s*")[^"]*(")', rf"\g<1>{new_tag}\g<2>", line) | |
| else: | |
| line = re.sub(r'(source-tag:\s*)\S+', rf'\g<1>{new_tag}', line) | |
| new_lines.append(line) | |
| with open('snap/snapcraft.yaml', 'w') as f: | |
| f.writelines(new_lines) | |
| print("Updated snap/snapcraft.yaml:") | |
| for u in updates: | |
| print(f" {u['part']}: {u['current']} -> {u['latest']}") | |
| PYEOF | |
| - name: Build PR metadata | |
| if: steps.check.outputs.update_needed == 'true' | |
| id: pr_meta | |
| run: | | |
| python3 << 'PYEOF' | |
| import json, os | |
| with open('/tmp/updates.json') as f: | |
| updates = json.load(f) | |
| if len(updates) == 1: | |
| u = updates[0] | |
| title = f"Update {u['part']} to {u['latest']}" | |
| else: | |
| parts_str = ', '.join(u['part'] for u in updates) | |
| title = f"Update upstream releases: {parts_str}" | |
| body_lines = [ | |
| "Automated update to track upstream releases.", | |
| "", | |
| "**Changes:**", | |
| ] | |
| for u in updates: | |
| body_lines.append( | |
| f"- **{u['part']}**: `{u['current']}` \u2192 `{u['latest']}` " | |
| f"([release](https://github.com/{u['repo']}/releases/tag/{u['latest']}))" | |
| ) | |
| body = '\n'.join(body_lines) | |
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | |
| f.write(f"title={title}\n") | |
| f.write(f"body<<PREOF\n{body}\nPREOF\n") | |
| PYEOF | |
| - name: Create Pull Request | |
| if: steps.check.outputs.update_needed == 'true' | |
| uses: peter-evans/create-pull-request@v6 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: "${{ steps.pr_meta.outputs.title }}" | |
| title: "${{ steps.pr_meta.outputs.title }}" | |
| body: "${{ steps.pr_meta.outputs.body }}" | |
| branch: update-upstream-tags | |
| delete-branch: true |