Skip to content

Commit 5ab15af

Browse files
Antony BaileyAntony Bailey
authored andcommitted
try and deal with dependant actions
1 parent 4da0930 commit 5ab15af

File tree

1 file changed

+162
-119
lines changed

1 file changed

+162
-119
lines changed

.github/workflows/sha-pins.yml

Lines changed: 162 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,167 @@
11
name: Check and update workflow action references
22

33
on:
4-
schedule:
5-
- cron: '0 0 * * 1' # Run weekly on Mondays
6-
workflow_dispatch: # Allow manual trigger
4+
schedule:
5+
- cron: '0 0 * * 1' # Run weekly on Mondays
6+
workflow_dispatch: # Allow manual trigger
77

88
jobs:
9-
check-and-update-workflows:
10-
runs-on: ubuntu-latest
11-
permissions:
12-
contents: read
13-
pull-requests: write
14-
actions: write
15-
16-
steps:
17-
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
18-
19-
- name: Set up Python
20-
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
21-
with:
22-
python-version: '3.11'
23-
24-
- name: Install dependencies
25-
run: |
26-
python -m pip install --upgrade pip
27-
pip install PyGithub pyyaml
28-
29-
- name: Check workflows and update action references
30-
env:
31-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32-
run: |
33-
python - <<'EOF'
34-
import os
35-
import re
36-
import yaml
37-
import requests
38-
from github import Github
39-
40-
# Initialize GitHub client
41-
g = Github(os.environ["GITHUB_TOKEN"])
42-
repo = g.get_repo(os.environ["GITHUB_REPOSITORY"])
43-
44-
def get_latest_sha(action_owner, action_name, version):
45-
# Get the latest SHA for the given action and version
46-
url = f"https://api.github.com/repos/{action_owner}/{action_name}/commits/{version}"
47-
headers = {"Accept": "application/vnd.github.v3+json"}
48-
if "GITHUB_TOKEN" in os.environ:
49-
headers["Authorization"] = f"token {os.environ['GITHUB_TOKEN']}"
50-
51-
response = requests.get(url, headers=headers)
52-
if response.status_code == 200:
53-
return response.json()["sha"]
54-
return None
55-
56-
# Get all workflow files
57-
workflow_dir = ".github/workflows"
58-
updates = {}
59-
60-
for filename in os.listdir(workflow_dir):
61-
if filename.endswith((".yml", ".yaml")) and filename != "sha.yml":
62-
filepath = os.path.join(workflow_dir, filename)
63-
with open(filepath, 'r') as f:
64-
content = f.read()
65-
66-
# Look for uses: statements that don't include SHA
67-
lines = content.split('\n')
68-
updated_lines = lines.copy()
69-
needs_update = False
70-
71-
for i, line in enumerate(lines):
72-
# Match pattern like: uses: actions/checkout@v3 or uses: owner/repo@v1.2.3
73-
matches = re.search(r'^\s*uses:\s+([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)@(v\d+[^\s#]*)(?:\s+#.*)?$', line)
74-
if matches:
75-
action = matches.group(1)
76-
version = matches.group(2)
77-
# Skip if line already has SHA comment
78-
if not re.search(r'#\s*[a-f0-9]{40}', line):
79-
sha = get_latest_sha(action.split('/')[0], action.split('/')[1], version)
80-
if sha:
81-
# Update line with SHA as comment
82-
updated_line = re.sub(r'@(v\d+[^\s#]*)(?:\s+#.*)?$', f'@{sha[:40]} # {version}', line)
83-
updated_lines[i] = updated_line
84-
needs_update = True
85-
print(f"Updating {filename}: {line.strip()} -> {updated_line.strip()}")
86-
87-
if needs_update:
88-
updates[filepath] = '\n'.join(updated_lines)
89-
90-
if updates:
91-
branch_name = f"action-sha-updates-{os.environ.get('GITHUB_RUN_ID', '')}"
92-
default_branch = repo.default_branch
93-
94-
# Create a new branch
95-
ref = f"refs/heads/{branch_name}"
96-
try:
97-
repo.create_git_ref(ref=ref, sha=repo.get_branch(default_branch).commit.sha)
98-
except Exception as e:
99-
print(f"Error creating branch: {e}")
100-
exit(1)
101-
102-
# Update files
103-
commit_message = "chore: update GitHub Action references with SHA pins"
104-
for filepath, content in updates.items():
105-
try:
106-
file = repo.get_contents(filepath, ref=branch_name)
107-
repo.update_file(filepath, commit_message, content, file.sha, branch=branch_name)
108-
except Exception as e:
109-
print(f"Error updating {filepath}: {e}")
110-
111-
# Create PR
112-
try:
113-
pr = repo.create_pull(
114-
title="Update GitHub Action references with SHA pins",
115-
body="This PR updates GitHub Action references to include SHA pins for better security and reproducibility.",
116-
head=branch_name,
117-
base=default_branch
118-
)
119-
print(f"Created PR #{pr.number}: {pr.html_url}")
120-
except Exception as e:
121-
print(f"Error creating PR: {e}")
122-
else:
123-
print("No updates needed - all action references already use SHA pins.")
124-
EOF
9+
check-and-update-workflows:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
pull-requests: write
14+
actions: write
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
19+
20+
- name: Setup Python
21+
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
22+
with:
23+
python-version: '3.10'
24+
25+
- name: Install dependencies
26+
run: |
27+
python -m pip install --upgrade pip
28+
pip install PyGithub
29+
30+
- name: Check and update action references
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
run: |
34+
python << 'EOF'
35+
import os
36+
import re
37+
import glob
38+
from github import Github
39+
40+
# Initialize GitHub client
41+
g = Github(os.environ['GITHUB_TOKEN'])
42+
repo = g.get_repo(os.environ['GITHUB_REPOSITORY'])
43+
44+
# Find all workflow files
45+
workflow_files = glob.glob('.github/workflows/*.yml')
46+
47+
# Initialize counts
48+
updated_files = 0
49+
updates = 0
50+
51+
for workflow_file in workflow_files:
52+
with open(workflow_file, 'r') as f:
53+
content = f.read()
54+
55+
# Find action references: uses: owner/repo@ref
56+
action_pattern = r'uses:\s+([a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+)@([a-zA-Z0-9_.-]+)(\s+#.*)?'
57+
matches = re.findall(action_pattern, content)
58+
59+
updated_content = content
60+
file_updated = False
61+
62+
for match in matches:
63+
action, version, comment = match
64+
# Skip if already pinned to a SHA
65+
if len(version) == 40 and all(c in '0123456789abcdef' for c in version.lower()):
66+
continue
67+
68+
try:
69+
# Get the SHA for the tag/branch
70+
action_repo = g.get_repo(action)
71+
if version.startswith('v'):
72+
# It's likely a tag
73+
try:
74+
ref = action_repo.get_git_ref(f"tags/{version}")
75+
sha = ref.object.sha
76+
77+
# For annotated tags, we need to get the commit SHA
78+
if ref.object.type == 'tag':
79+
tag = action_repo.get_git_tag(sha)
80+
sha = tag.object.sha
81+
except Exception as e:
82+
print(f"Error resolving tag {version} for {action}: {str(e)}")
83+
continue
84+
else:
85+
# It's likely a branch
86+
try:
87+
ref = action_repo.get_git_ref(f"heads/{version}")
88+
sha = ref.object.sha
89+
except Exception as e:
90+
print(f"Error resolving branch {version} for {action}: {str(e)}")
91+
continue
92+
93+
# Create updated reference with SHA
94+
old_ref = f"uses: {action}@{version}"
95+
new_ref = f"uses: {action}@{sha} # {version}"
96+
97+
# Update the content
98+
updated_content = updated_content.replace(old_ref, new_ref)
99+
print(f"Updating {workflow_file}: {old_ref} -> {new_ref}")
100+
file_updated = True
101+
updates += 1
102+
103+
except Exception as e:
104+
print(f"Error processing {action}@{version}: {str(e)}")
105+
106+
# Write updated content back to the file
107+
if file_updated:
108+
with open(workflow_file, 'w') as f:
109+
f.write(updated_content)
110+
updated_files += 1
111+
112+
# Create a PR if there were updates
113+
if updated_files > 0:
114+
try:
115+
branch_name = "action-sha-updates"
116+
base_branch = repo.default_branch
117+
118+
# Check if branch exists and delete if it does
119+
try:
120+
repo.get_git_ref(f"heads/{branch_name}")
121+
repo.get_git_ref(f"heads/{branch_name}").delete()
122+
except:
123+
pass
124+
125+
# Create branch
126+
sb = repo.get_branch(base_branch)
127+
repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=sb.commit.sha)
128+
129+
# Commit changes
130+
commit_message = f"Update action references with SHA pins\n\nUpdated {updates} references in {updated_files} workflow files"
131+
132+
# Create a commit with all changes
133+
element_list = []
134+
for workflow_file in workflow_files:
135+
with open(workflow_file, 'r') as f:
136+
content = f.read()
137+
138+
element = {
139+
"path": workflow_file,
140+
"mode": "100644",
141+
"type": "blob",
142+
"content": content
143+
}
144+
element_list.append(element)
145+
146+
commit = repo.create_git_tree(element_list, base_tree=sb.commit.tree)
147+
parent = repo.get_git_commit(sb.commit.sha)
148+
commit = repo.create_git_commit(commit_message, commit, [parent])
149+
ref = repo.get_git_ref(f"heads/{branch_name}")
150+
ref.edit(commit.sha)
151+
152+
# Create a PR
153+
pr = repo.create_pull(
154+
title="Update GitHub Action references with SHA pins",
155+
body="This PR updates GitHub Action references to use SHA pins for better security.",
156+
base=base_branch,
157+
head=branch_name
158+
)
159+
print(f"Created PR #{pr.number}")
160+
161+
except Exception as e:
162+
print(f"Error creating PR: {str(e)}")
163+
exit(1)
164+
165+
else:
166+
print("No updates needed.")
167+
EOF

0 commit comments

Comments
 (0)