52 lines
2.5 KiB
Python
52 lines
2.5 KiB
Python
"""Recognize approved GitOps repositories through their shared immutable worker."""
|
|
import base64
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
spec = importlib.util.spec_from_file_location('gitops_bridge', ROOT / 'src/scripts/generate_auxiliary_workflow.py')
|
|
bridge = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(bridge)
|
|
DEFINITION = bridge.BRIDGES['gitops']
|
|
REPOSITORIES = {DEFINITION['repository'], *DEFINITION.get('additional_repositories', [])}
|
|
|
|
|
|
def gates():
|
|
return [{'context': 'gitops / validate', 'app_id': 15368}]
|
|
|
|
|
|
def validate(repo, head, branch, request, load_yaml):
|
|
if repo not in REPOSITORIES or branch != 'main':
|
|
raise ValueError('GitOps recognition requires an approved repository on main only')
|
|
|
|
def content(owner, path, ref):
|
|
item = request(f'repos/{owner}/contents/{path}?ref={ref}')
|
|
if item.get('type') != 'file' or item.get('encoding') != 'base64':
|
|
raise ValueError('GitOps workflow must be a regular file')
|
|
return base64.b64decode(item['content']).decode('utf-8').replace('\r\n', '\n')
|
|
|
|
path = DEFINITION['path']
|
|
caller = load_yaml(content(repo, path, head))
|
|
try:
|
|
pin = caller['jobs']['gitops']['uses'].rsplit('@', 1)[1]
|
|
expected = bridge.canonical('gitops', pin)
|
|
except (KeyError, TypeError, IndexError, ValueError, AttributeError):
|
|
raise ValueError('GitOps caller has no immutable shared worker pin') from None
|
|
if caller != expected:
|
|
raise ValueError('GitOps caller differs from the canonical shared bridge')
|
|
tree = request(f'repos/{repo}/git/trees/{head}?recursive=1')
|
|
if tree.get('truncated') is not False or not isinstance(tree.get('tree'), list):
|
|
raise ValueError('GitOps recognition requires the complete source tree')
|
|
for entry in tree['tree']:
|
|
name = entry['path']
|
|
if name == '.circleci/config.yml' or (name.startswith('.github/workflows/')
|
|
and name.endswith(('.yml', '.yaml')) and name != path):
|
|
raise ValueError('GitOps repository has additional orchestration: ' + name)
|
|
commit = request('repos/QRun-IO/qqq-orb/commits/' + pin)
|
|
if not commit['commit']['verification']['verified']:
|
|
raise ValueError('GitOps worker commit is not verified')
|
|
worker = '.github/workflows/gitops.yml'
|
|
if content('QRun-IO/qqq-orb', worker, pin) != (ROOT / worker).read_text(encoding='utf-8'):
|
|
raise ValueError('Pinned GitOps worker differs from the supported contract')
|
|
return pin
|