Skip to content

Commit ff400ed

Browse files
committed
🔄 created local '.github/workflows/validate-properties-links.yml' from remote '.releases/validate-properties-links.yml'
1 parent cdf5198 commit ff400ed

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
name: Validate Properties Links
2+
3+
# This workflow validates all URLs in modified .properties files when a PR is created or edited
4+
# Works for both modules/*.properties and releases.properties files
5+
6+
on:
7+
pull_request:
8+
types: [opened, synchronize, edited, reopened]
9+
paths:
10+
- 'modules/*.properties'
11+
- 'releases.properties'
12+
13+
jobs:
14+
validate-links:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout PR branch
19+
uses: actions/checkout@v4
20+
with:
21+
ref: ${{ github.event.pull_request.head.sha }}
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: '3.11'
27+
28+
- name: Install dependencies
29+
run: |
30+
pip install requests
31+
32+
- name: Get changed properties files
33+
id: changed_files
34+
run: |
35+
# Get list of changed .properties files
36+
git fetch origin ${{ github.event.pull_request.base.ref }}
37+
CHANGED_FILES=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...HEAD | grep '\.properties$' || true)
38+
39+
if [ -z "$CHANGED_FILES" ]; then
40+
echo "No properties files changed"
41+
echo "files=" >> $GITHUB_OUTPUT
42+
else
43+
echo "Changed files:"
44+
echo "$CHANGED_FILES"
45+
# Convert to comma-separated list
46+
FILES_LIST=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//')
47+
echo "files=$FILES_LIST" >> $GITHUB_OUTPUT
48+
fi
49+
50+
- name: Validate all links in properties files
51+
if: steps.changed_files.outputs.files != ''
52+
env:
53+
CHANGED_FILES: ${{ steps.changed_files.outputs.files }}
54+
run: |
55+
python << 'EOF'
56+
import os
57+
import sys
58+
import requests
59+
from urllib.parse import urlparse
60+
import time
61+
62+
# Get changed files
63+
changed_files = os.environ.get('CHANGED_FILES', '').split(',')
64+
changed_files = [f.strip() for f in changed_files if f.strip()]
65+
66+
if not changed_files:
67+
print("No properties files to validate")
68+
sys.exit(0)
69+
70+
print(f"Validating {len(changed_files)} properties file(s)...\n")
71+
72+
all_valid = True
73+
total_urls = 0
74+
valid_urls = 0
75+
invalid_urls = []
76+
77+
for properties_file in changed_files:
78+
if not os.path.exists(properties_file):
79+
print(f"⚠️ File not found: {properties_file}")
80+
continue
81+
82+
print(f"📄 Checking: {properties_file}")
83+
print("-" * 80)
84+
85+
with open(properties_file, 'r', encoding='utf-8') as f:
86+
lines = f.readlines()
87+
88+
file_urls = []
89+
for line_num, line in enumerate(lines, 1):
90+
line = line.strip()
91+
92+
# Skip comments and empty lines
93+
if not line or line.startswith('#'):
94+
continue
95+
96+
# Parse property line
97+
if '=' in line:
98+
key, value = line.split('=', 1)
99+
url = value.strip()
100+
101+
# Check if it's a URL
102+
if url.startswith('http://') or url.startswith('https://'):
103+
file_urls.append({
104+
'line': line_num,
105+
'key': key.strip(),
106+
'url': url
107+
})
108+
109+
print(f"Found {len(file_urls)} URL(s) to validate\n")
110+
111+
# Validate each URL
112+
for item in file_urls:
113+
total_urls += 1
114+
url = item['url']
115+
key = item['key']
116+
line = item['line']
117+
118+
try:
119+
# Send HEAD request first (faster)
120+
response = requests.head(url, timeout=10, allow_redirects=True)
121+
122+
# If HEAD fails, try GET
123+
if response.status_code >= 400:
124+
response = requests.get(url, timeout=10, allow_redirects=True, stream=True)
125+
126+
if response.status_code == 200:
127+
print(f"✅ Line {line}: {key}")
128+
print(f" URL: {url}")
129+
print(f" Status: {response.status_code} OK")
130+
valid_urls += 1
131+
else:
132+
print(f"❌ Line {line}: {key}")
133+
print(f" URL: {url}")
134+
print(f" Status: {response.status_code} {response.reason}")
135+
invalid_urls.append({
136+
'file': properties_file,
137+
'line': line,
138+
'key': key,
139+
'url': url,
140+
'status': response.status_code,
141+
'reason': response.reason
142+
})
143+
all_valid = False
144+
145+
except requests.exceptions.Timeout:
146+
print(f"⏱️ Line {line}: {key}")
147+
print(f" URL: {url}")
148+
print(f" Error: Request timeout (>10s)")
149+
invalid_urls.append({
150+
'file': properties_file,
151+
'line': line,
152+
'key': key,
153+
'url': url,
154+
'status': 'TIMEOUT',
155+
'reason': 'Request timeout'
156+
})
157+
all_valid = False
158+
159+
except requests.exceptions.RequestException as e:
160+
print(f"❌ Line {line}: {key}")
161+
print(f" URL: {url}")
162+
print(f" Error: {str(e)}")
163+
invalid_urls.append({
164+
'file': properties_file,
165+
'line': line,
166+
'key': key,
167+
'url': url,
168+
'status': 'ERROR',
169+
'reason': str(e)
170+
})
171+
all_valid = False
172+
173+
print()
174+
175+
# Small delay to avoid rate limiting
176+
time.sleep(0.5)
177+
178+
print()
179+
180+
# Summary
181+
print("=" * 80)
182+
print("VALIDATION SUMMARY")
183+
print("=" * 80)
184+
print(f"Total URLs checked: {total_urls}")
185+
print(f"Valid URLs: {valid_urls}")
186+
print(f"Invalid URLs: {len(invalid_urls)}")
187+
print()
188+
189+
if invalid_urls:
190+
print("❌ INVALID URLS FOUND:")
191+
print("-" * 80)
192+
for item in invalid_urls:
193+
print(f"\nFile: {item['file']}")
194+
print(f"Line: {item['line']}")
195+
print(f"Key: {item['key']}")
196+
print(f"URL: {item['url']}")
197+
print(f"Status: {item['status']} - {item['reason']}")
198+
print()
199+
print("=" * 80)
200+
print("❌ VALIDATION FAILED - Please fix the invalid URLs above")
201+
print("=" * 80)
202+
sys.exit(1)
203+
else:
204+
print("=" * 80)
205+
print("✅ ALL URLS ARE VALID")
206+
print("=" * 80)
207+
sys.exit(0)
208+
209+
EOF
210+
211+
- name: Comment on PR with validation results
212+
if: failure()
213+
uses: actions/github-script@v7
214+
with:
215+
github-token: ${{ secrets.GITHUB_TOKEN }}
216+
script: |
217+
const fs = require('fs');
218+
219+
// Create a comment on the PR
220+
await github.rest.issues.createComment({
221+
owner: context.repo.owner,
222+
repo: context.repo.repo,
223+
issue_number: context.issue.number,
224+
body: `## ❌ Link Validation Failed
225+
226+
Some URLs in the modified properties files are not accessible. Please check the workflow logs for details.
227+
228+
**Action Required:**
229+
- Review the invalid URLs listed in the [workflow logs](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})
230+
- Fix or remove the invalid URLs
231+
- Push the changes to trigger a new validation
232+
233+
The PR cannot be merged until all URLs are valid.`
234+
});
235+
236+
- name: Comment on PR with success
237+
if: success() && steps.changed_files.outputs.files != ''
238+
uses: actions/github-script@v7
239+
with:
240+
github-token: ${{ secrets.GITHUB_TOKEN }}
241+
script: |
242+
await github.rest.issues.createComment({
243+
owner: context.repo.owner,
244+
repo: context.repo.repo,
245+
issue_number: context.issue.number,
246+
body: `## ✅ Link Validation Passed
247+
248+
All URLs in the modified properties files have been validated and are accessible.
249+
250+
This PR is ready for review and merge.`
251+
});

0 commit comments

Comments
 (0)