blob: fd90b1b9f0e5eb8d09ba3cb91d48aacda1b0bbab [file] [log] [blame]
#!/usr/bin/env python3
import argparse
import re
import sys
from pathlib import Path
from typing import Any, Dict, Optional
import yaml
from typing_extensions import TypedDict
Step = Dict[str, Any]
class Script(TypedDict):
extension: str
script: str
def extract(step: Step) -> Optional[Script]:
run = step.get('run')
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
shell = step.get('shell', 'bash')
extension = {
'bash': '.sh',
'pwsh': '.ps1',
'python': '.py',
'sh': '.sh',
'cmd': '.cmd',
'powershell': '.ps1',
}.get(shell)
is_gh_script = step.get('uses', '').startswith('actions/github-script@')
gh_script = step.get('with', {}).get('script')
if run is not None and extension is not None:
script = {
'bash': f'#!/usr/bin/env bash\nset -eo pipefail\n{run}',
'sh': f'#!/usr/bin/env sh\nset -e\n{run}',
}.get(shell, run)
return {'extension': extension, 'script': script}
elif is_gh_script and gh_script is not None:
return {'extension': '.js', 'script': gh_script}
else:
return None
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('--out', required=True)
args = parser.parse_args()
out = Path(args.out)
if out.exists():
sys.exit(f'{out} already exists; aborting to avoid overwriting')
gha_expressions_found = False
for p in Path('.github/workflows').iterdir():
with open(p) as f:
workflow = yaml.safe_load(f)
for job_name, job in workflow['jobs'].items():
job_dir = out / p / job_name
steps = job['steps']
index_chars = len(str(len(steps) - 1))
for i, step in enumerate(steps, start=1):
extracted = extract(step)
if extracted:
script = extracted['script']
step_name = step.get('name', '')
if '${{' in script:
gha_expressions_found = True
print(
f'{p} job `{job_name}` step {i}: {step_name}',
file=sys.stderr
)
job_dir.mkdir(parents=True, exist_ok=True)
sanitized = re.sub(
'[^a-zA-Z_]+', '_',
f'_{step_name}',
).rstrip('_')
extension = extracted['extension']
filename = f'{i:0{index_chars}}{sanitized}{extension}'
(job_dir / filename).write_text(script)
if gha_expressions_found:
sys.exit(
'Each of the above scripts contains a GitHub Actions '
'${{ <expression> }} which must be replaced with an `env` variable'
' for security reasons.'
)
if __name__ == '__main__':
main()