might give up on this soon #8
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 and update workflow action references | ||
| on: | ||
| schedule: | ||
| - cron: '0 0 * * 1' # Run weekly on Mondays | ||
| workflow_dispatch: # Allow manual trigger | ||
| jobs: | ||
| check-and-update-workflows: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| actions: write | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 | ||
| - name: Setup Python | ||
| uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 | ||
| with: | ||
| python-version: '3.10' | ||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install PyGithub | ||
| - name: Check and update action references | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| python << 'EOF' | ||
| import os | ||
| import re | ||
| import glob | ||
| from github import Github | ||
| # Initialize GitHub client | ||
| g = Github(os.environ['GITHUB_TOKEN']) | ||
| repo = g.get_repo(os.environ['GITHUB_REPOSITORY']) | ||
| # Find all workflow files | ||
| workflow_files = glob.glob('.github/workflows/*.yml') | ||
| # Initialize counts | ||
| updated_files = 0 | ||
| updates = 0 | ||
| for workflow_file in workflow_files: | ||
| with open(workflow_file, 'r') as f: | ||
| content = f.read() | ||
| # Find action references: uses: owner/repo@ref | ||
| action_pattern = r'uses:\s+([a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+)@([a-zA-Z0-9_.-]+)(\s+#.*)?' | ||
| matches = re.findall(action_pattern, content) | ||
| updated_content = content | ||
| file_updated = False | ||
| for match in matches: | ||
| action, version, comment = match | ||
| # Skip if already pinned to a SHA | ||
| if len(version) == 40 and all(c in '0123456789abcdef' for c in version.lower()): | ||
| continue | ||
| try: | ||
| # Get the SHA for the tag/branch | ||
| action_repo = g.get_repo(action) | ||
| if version.startswith('v'): | ||
| # It's likely a tag | ||
| try: | ||
| ref = action_repo.get_git_ref(f"tags/{version}") | ||
| sha = ref.object.sha | ||
| # For annotated tags, we need to get the commit SHA | ||
| if ref.object.type == 'tag': | ||
| tag = action_repo.get_git_tag(sha) | ||
| sha = tag.object.sha | ||
| except Exception as e: | ||
| print(f"Error resolving tag {version} for {action}: {str(e)}") | ||
| continue | ||
| else: | ||
| # It's likely a branch | ||
| try: | ||
| ref = action_repo.get_git_ref(f"heads/{version}") | ||
| sha = ref.object.sha | ||
| except Exception as e: | ||
| print(f"Error resolving branch {version} for {action}: {str(e)}") | ||
| continue | ||
| # Create updated reference with SHA | ||
| old_ref = f"uses: {action}@{version}" | ||
| new_ref = f"uses: {action}@{sha} # {version}" | ||
| # Update the content | ||
| updated_content = updated_content.replace(old_ref, new_ref) | ||
| print(f"Updating {workflow_file}: {old_ref} -> {new_ref}") | ||
| file_updated = True | ||
| updates += 1 | ||
| except Exception as e: | ||
| print(f"Error processing {action}@{version}: {str(e)}") | ||
| # Write updated content back to the file | ||
| if file_updated: | ||
| with open(workflow_file, 'w') as f: | ||
| f.write(updated_content) | ||
| updated_files += 1 | ||
| # Create a PR if there were updates | ||
| if updated_files > 0: | ||
| try: | ||
| branch_name = "action-sha-updates" | ||
| base_branch = repo.default_branch | ||
| # Check if branch exists and delete if it does | ||
| try: | ||
| repo.get_git_ref(f"heads/{branch_name}") | ||
| repo.get_git_ref(f"heads/{branch_name}").delete() | ||
| except: | ||
| pass | ||
| # Create branch | ||
| sb = repo.get_branch(base_branch) | ||
| repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=sb.commit.sha) | ||
| # Commit changes | ||
| commit_message = f"Update action references with SHA pins\n\nUpdated {updates} references in {updated_files} workflow files" | ||
| # Create a commit with all changes | ||
| element_list = [] | ||
| for workflow_file in workflow_files: | ||
| with open(workflow_file, 'r') as f: | ||
| content = f.read() | ||
| element = { | ||
| "path": workflow_file, | ||
| "mode": "100644", | ||
| "type": "blob", | ||
| "content": content | ||
| } | ||
| element_list.append(element) | ||
| base_tree = repo.get_git_tree(sb.commit.sha) | ||
| tree = repo.create_git_tree(element_list, base_tree) | ||
| parent = repo.get_git_commit(sb.commit.sha) | ||
| commit = repo.create_git_commit(commit_message, tree, [parent]) | ||
| ref = repo.get_git_ref(f"heads/{branch_name}") | ||
| ref.edit(commit.sha) | ||
| # Create a PR | ||
| pr = repo.create_pull( | ||
| title="Update GitHub Action references with SHA pins", | ||
| body="This PR updates GitHub Action references to use SHA pins for better security.", | ||
| base=base_branch, | ||
| head=branch_name | ||
| ) | ||
| print(f"Created PR #{pr.number}") | ||
| except Exception as e: | ||
| print(f"Error creating PR: {str(e)}") | ||
| exit(1) | ||
| else: | ||
| print("No updates needed.") | ||
| EOF | ||