mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 09:14:58 +00:00
Remove python files from pr-checks
This commit is contained in:
3
pr-checks/.gitignore
vendored
3
pr-checks/.gitignore
vendored
@@ -1,4 +1 @@
|
|||||||
env
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Sync-back script to automatically update action versions in source templates
|
|
||||||
from the generated workflow files after Dependabot updates.
|
|
||||||
|
|
||||||
This script scans the generated workflow files (.github/workflows/__*.yml) to find
|
|
||||||
all external action versions used, then updates:
|
|
||||||
1. Hardcoded action versions in pr-checks/sync.py
|
|
||||||
2. Action version references in template files in pr-checks/checks/
|
|
||||||
|
|
||||||
The script automatically detects all actions used in generated workflows and
|
|
||||||
preserves version comments (e.g., # v1.2.3) when syncing versions.
|
|
||||||
|
|
||||||
This ensures that when Dependabot updates action versions in generated workflows,
|
|
||||||
those changes are properly synced back to the source templates. Regular workflow
|
|
||||||
files are updated directly by Dependabot and don't need sync-back.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import glob
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
|
|
||||||
def scan_generated_workflows(workflow_dir: str) -> Dict[str, str]:
|
|
||||||
"""
|
|
||||||
Scan generated workflow files to extract the latest action versions.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
workflow_dir: Path to .github/workflows directory
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary mapping action names to their latest versions (including comments)
|
|
||||||
"""
|
|
||||||
action_versions = {}
|
|
||||||
generated_files = glob.glob(os.path.join(workflow_dir, "__*.yml"))
|
|
||||||
|
|
||||||
for file_path in generated_files:
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Find all action uses in the file, including potential comments
|
|
||||||
# This pattern captures: action_name@version_with_possible_comment
|
|
||||||
pattern = r'uses:\s+([^/\s]+/[^@\s]+)@([^@\n]+)'
|
|
||||||
matches = re.findall(pattern, content)
|
|
||||||
|
|
||||||
for action_name, version_with_comment in matches:
|
|
||||||
# Only track non-local actions (those with / but not starting with ./)
|
|
||||||
if not action_name.startswith('./'):
|
|
||||||
# Assume that version numbers are consistent (this should be the case on a Dependabot update PR)
|
|
||||||
action_versions[action_name] = version_with_comment.rstrip()
|
|
||||||
|
|
||||||
return action_versions
|
|
||||||
|
|
||||||
|
|
||||||
def update_sync_py(sync_py_path: str, action_versions: Dict[str, str]) -> bool:
|
|
||||||
"""
|
|
||||||
Update hardcoded action versions in pr-checks/sync.py
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sync_py_path: Path to sync.py file
|
|
||||||
action_versions: Dictionary of action names to versions (may include comments)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if file was modified, False otherwise
|
|
||||||
"""
|
|
||||||
if not os.path.exists(sync_py_path):
|
|
||||||
raise FileNotFoundError(f"Could not find {sync_py_path}")
|
|
||||||
|
|
||||||
with open(sync_py_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
original_content = content
|
|
||||||
|
|
||||||
# Update hardcoded action versions
|
|
||||||
for action_name, version_with_comment in action_versions.items():
|
|
||||||
# Extract just the version part (before any comment) for sync.py
|
|
||||||
version = version_with_comment.split('#')[0].strip() if '#' in version_with_comment else version_with_comment.strip()
|
|
||||||
|
|
||||||
# Look for patterns like 'uses': 'actions/setup-node@v4'
|
|
||||||
# Note that this will break if we store an Action uses reference in a
|
|
||||||
# variable - that's a risk we're happy to take since in that case the
|
|
||||||
# PR checks will just fail.
|
|
||||||
pattern = rf"('uses':\s*'){re.escape(action_name)}@(?:[^']+)(')"
|
|
||||||
replacement = rf"\1{action_name}@{version}\2"
|
|
||||||
content = re.sub(pattern, replacement, content)
|
|
||||||
|
|
||||||
if content != original_content:
|
|
||||||
with open(sync_py_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
print(f"Updated {sync_py_path}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"No changes needed in {sync_py_path}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def update_template_files(checks_dir: str, action_versions: Dict[str, str]) -> List[str]:
|
|
||||||
"""
|
|
||||||
Update action versions in template files in pr-checks/checks/
|
|
||||||
|
|
||||||
Args:
|
|
||||||
checks_dir: Path to pr-checks/checks directory
|
|
||||||
action_versions: Dictionary of action names to versions (may include comments)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of files that were modified
|
|
||||||
"""
|
|
||||||
modified_files = []
|
|
||||||
template_files = glob.glob(os.path.join(checks_dir, "*.yml"))
|
|
||||||
|
|
||||||
for file_path in template_files:
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
original_content = content
|
|
||||||
|
|
||||||
# Update action versions
|
|
||||||
for action_name, version_with_comment in action_versions.items():
|
|
||||||
# Look for patterns like 'uses: actions/setup-node@v4' or 'uses: actions/setup-node@sha # comment'
|
|
||||||
pattern = rf"(uses:\s+{re.escape(action_name)})@(?:[^@\n]+)"
|
|
||||||
replacement = rf"\1@{version_with_comment}"
|
|
||||||
content = re.sub(pattern, replacement, content)
|
|
||||||
|
|
||||||
if content != original_content:
|
|
||||||
with open(file_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
modified_files.append(file_path)
|
|
||||||
print(f"Updated {file_path}")
|
|
||||||
|
|
||||||
return modified_files
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description="Sync action versions from generated workflows back to templates")
|
|
||||||
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Get the repository root (assuming script is in pr-checks/)
|
|
||||||
script_dir = Path(__file__).parent
|
|
||||||
repo_root = script_dir.parent
|
|
||||||
|
|
||||||
workflow_dir = repo_root / ".github" / "workflows"
|
|
||||||
checks_dir = script_dir / "checks"
|
|
||||||
sync_py_path = script_dir / "sync.py"
|
|
||||||
|
|
||||||
print("Scanning generated workflows for latest action versions...")
|
|
||||||
action_versions = scan_generated_workflows(str(workflow_dir))
|
|
||||||
|
|
||||||
if args.verbose:
|
|
||||||
print("Found action versions:")
|
|
||||||
for action, version in action_versions.items():
|
|
||||||
print(f" {action}@{version}")
|
|
||||||
|
|
||||||
if not action_versions:
|
|
||||||
print("No action versions found in generated workflows")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Update files
|
|
||||||
print("\nUpdating source files...")
|
|
||||||
modified_files = []
|
|
||||||
|
|
||||||
# Update sync.py
|
|
||||||
if update_sync_py(str(sync_py_path), action_versions):
|
|
||||||
modified_files.append(str(sync_py_path))
|
|
||||||
|
|
||||||
# Update template files
|
|
||||||
template_modified = update_template_files(str(checks_dir), action_versions)
|
|
||||||
modified_files.extend(template_modified)
|
|
||||||
|
|
||||||
if modified_files:
|
|
||||||
print(f"\nSync completed. Modified {len(modified_files)} files:")
|
|
||||||
for file_path in modified_files:
|
|
||||||
print(f" {file_path}")
|
|
||||||
else:
|
|
||||||
print("\nNo files needed updating - all action versions are already in sync")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Tests for the sync_back.py script
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import sync_back
|
|
||||||
|
|
||||||
|
|
||||||
class TestSyncBack(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Set up temporary directories and files for testing"""
|
|
||||||
self.test_dir = tempfile.mkdtemp()
|
|
||||||
self.workflow_dir = os.path.join(self.test_dir, ".github", "workflows")
|
|
||||||
self.checks_dir = os.path.join(self.test_dir, "pr-checks", "checks")
|
|
||||||
os.makedirs(self.workflow_dir)
|
|
||||||
os.makedirs(self.checks_dir)
|
|
||||||
|
|
||||||
# Create sync.py file
|
|
||||||
self.sync_py_path = os.path.join(self.test_dir, "pr-checks", "sync.py")
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Clean up temporary directories"""
|
|
||||||
shutil.rmtree(self.test_dir)
|
|
||||||
|
|
||||||
def test_scan_generated_workflows_basic(self):
|
|
||||||
"""Test basic workflow scanning functionality"""
|
|
||||||
# Create a test generated workflow file
|
|
||||||
workflow_content = """
|
|
||||||
name: Test Workflow
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v5
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(os.path.join(self.workflow_dir, "__test.yml"), 'w') as f:
|
|
||||||
f.write(workflow_content)
|
|
||||||
|
|
||||||
result = sync_back.scan_generated_workflows(self.workflow_dir)
|
|
||||||
|
|
||||||
self.assertEqual(result['actions/checkout'], 'v4')
|
|
||||||
self.assertEqual(result['actions/setup-node'], 'v5')
|
|
||||||
self.assertEqual(result['actions/setup-go'], 'v6')
|
|
||||||
|
|
||||||
def test_scan_generated_workflows_with_comments(self):
|
|
||||||
"""Test scanning workflows with version comments"""
|
|
||||||
workflow_content = """
|
|
||||||
name: Test Workflow
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0
|
|
||||||
- uses: actions/setup-python@v6 # Latest Python
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(os.path.join(self.workflow_dir, "__test.yml"), 'w') as f:
|
|
||||||
f.write(workflow_content)
|
|
||||||
|
|
||||||
result = sync_back.scan_generated_workflows(self.workflow_dir)
|
|
||||||
|
|
||||||
self.assertEqual(result['actions/checkout'], 'v4')
|
|
||||||
self.assertEqual(result['ruby/setup-ruby'], '44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0')
|
|
||||||
self.assertEqual(result['actions/setup-python'], 'v6 # Latest Python')
|
|
||||||
|
|
||||||
def test_scan_generated_workflows_ignores_local_actions(self):
|
|
||||||
"""Test that local actions (starting with ./) are ignored"""
|
|
||||||
workflow_content = """
|
|
||||||
name: Test Workflow
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: ./.github/actions/local-action
|
|
||||||
- uses: ./another-local-action@v1
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(os.path.join(self.workflow_dir, "__test.yml"), 'w') as f:
|
|
||||||
f.write(workflow_content)
|
|
||||||
|
|
||||||
result = sync_back.scan_generated_workflows(self.workflow_dir)
|
|
||||||
|
|
||||||
self.assertEqual(result['actions/checkout'], 'v4')
|
|
||||||
self.assertNotIn('./.github/actions/local-action', result)
|
|
||||||
self.assertNotIn('./another-local-action', result)
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_sync_py(self):
|
|
||||||
"""Test updating sync.py file"""
|
|
||||||
sync_py_content = """
|
|
||||||
steps = [
|
|
||||||
{
|
|
||||||
'uses': 'actions/setup-node@v4',
|
|
||||||
'with': {'node-version': '16'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'uses': 'actions/setup-go@v5',
|
|
||||||
'with': {'go-version': '1.19'}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(self.sync_py_path, 'w') as f:
|
|
||||||
f.write(sync_py_content)
|
|
||||||
|
|
||||||
action_versions = {
|
|
||||||
'actions/setup-node': 'v5',
|
|
||||||
'actions/setup-go': 'v6'
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sync_back.update_sync_py(self.sync_py_path, action_versions)
|
|
||||||
self.assertTrue(result)
|
|
||||||
|
|
||||||
with open(self.sync_py_path, 'r') as f:
|
|
||||||
updated_content = f.read()
|
|
||||||
|
|
||||||
self.assertIn("'uses': 'actions/setup-node@v5'", updated_content)
|
|
||||||
self.assertIn("'uses': 'actions/setup-go@v6'", updated_content)
|
|
||||||
|
|
||||||
def test_update_sync_py_with_comments(self):
|
|
||||||
"""Test updating sync.py file when versions have comments"""
|
|
||||||
sync_py_content = """
|
|
||||||
steps = [
|
|
||||||
{
|
|
||||||
'uses': 'actions/setup-node@v4',
|
|
||||||
'with': {'node-version': '16'}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(self.sync_py_path, 'w') as f:
|
|
||||||
f.write(sync_py_content)
|
|
||||||
|
|
||||||
action_versions = {
|
|
||||||
'actions/setup-node': 'v5 # Latest version'
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sync_back.update_sync_py(self.sync_py_path, action_versions)
|
|
||||||
self.assertTrue(result)
|
|
||||||
|
|
||||||
with open(self.sync_py_path, 'r') as f:
|
|
||||||
updated_content = f.read()
|
|
||||||
|
|
||||||
# sync.py should get the version without comment
|
|
||||||
self.assertIn("'uses': 'actions/setup-node@v5'", updated_content)
|
|
||||||
self.assertNotIn("# Latest version", updated_content)
|
|
||||||
|
|
||||||
def test_update_template_files(self):
|
|
||||||
"""Test updating template files"""
|
|
||||||
template_content = """
|
|
||||||
name: Test Template
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_path = os.path.join(self.checks_dir, "test.yml")
|
|
||||||
with open(template_path, 'w') as f:
|
|
||||||
f.write(template_content)
|
|
||||||
|
|
||||||
action_versions = {
|
|
||||||
'actions/checkout': 'v4',
|
|
||||||
'actions/setup-node': 'v5 # Latest'
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sync_back.update_template_files(self.checks_dir, action_versions)
|
|
||||||
self.assertEqual(len(result), 1)
|
|
||||||
self.assertIn(template_path, result)
|
|
||||||
|
|
||||||
with open(template_path, 'r') as f:
|
|
||||||
updated_content = f.read()
|
|
||||||
|
|
||||||
self.assertIn("uses: actions/checkout@v4", updated_content)
|
|
||||||
self.assertIn("uses: actions/setup-node@v5 # Latest", updated_content)
|
|
||||||
|
|
||||||
def test_update_template_files_preserves_comments(self):
|
|
||||||
"""Test that updating template files preserves version comments"""
|
|
||||||
template_content = """
|
|
||||||
name: Test Template
|
|
||||||
steps:
|
|
||||||
- uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb # v1.256.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_path = os.path.join(self.checks_dir, "test.yml")
|
|
||||||
with open(template_path, 'w') as f:
|
|
||||||
f.write(template_content)
|
|
||||||
|
|
||||||
action_versions = {
|
|
||||||
'ruby/setup-ruby': '55511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sync_back.update_template_files(self.checks_dir, action_versions)
|
|
||||||
self.assertEqual(len(result), 1)
|
|
||||||
|
|
||||||
with open(template_path, 'r') as f:
|
|
||||||
updated_content = f.read()
|
|
||||||
|
|
||||||
self.assertIn("uses: ruby/setup-ruby@55511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0", updated_content)
|
|
||||||
|
|
||||||
def test_no_changes_needed(self):
|
|
||||||
"""Test that functions return False/empty when no changes are needed"""
|
|
||||||
# Test sync.py with no changes needed
|
|
||||||
sync_py_content = """
|
|
||||||
steps = [
|
|
||||||
{
|
|
||||||
'uses': 'actions/setup-node@v5',
|
|
||||||
'with': {'node-version': '16'}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(self.sync_py_path, 'w') as f:
|
|
||||||
f.write(sync_py_content)
|
|
||||||
|
|
||||||
action_versions = {
|
|
||||||
'actions/setup-node': 'v5'
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sync_back.update_sync_py(self.sync_py_path, action_versions)
|
|
||||||
self.assertFalse(result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
Reference in New Issue
Block a user