Why: Gitea Docker runners inject an HTTP server URL, while ordinary transport rules intentionally reject HTTP sources. Evidence: SELF observed the fixed-runner failure, added a real local Git clone regression with RED then GREEN, and passed 31 transport/composition cases. Impact: Accept only inherited mirror aliases matching the injected server, owner and repository; keep configured HTTP sources, credentials and foreign aliases rejected without global Git changes. Refs: R7-R8, R14; dependency-workspace U5; Dev PR 537 runs 3029/3030
89 lines
4.6 KiB
Python
89 lines
4.6 KiB
Python
"""Process-local module transport; configuration contains no credentials."""
|
|
import json
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
from urllib.parse import urlsplit, unquote
|
|
|
|
|
|
def _url(value):
|
|
if not isinstance(value, str) or any(c.isspace() for c in value):
|
|
raise ValueError('invalid module transport URL')
|
|
parsed = urlsplit(value)
|
|
if parsed.scheme not in {'https', 'ssh', 'git+ssh', 'file'} or parsed.password:
|
|
raise ValueError('unsupported transport or credentials in URL')
|
|
if parsed.scheme == 'https' and parsed.username:
|
|
raise ValueError('transport URL must not contain credentials')
|
|
if parsed.scheme == 'file' and parsed.netloc not in {'', 'localhost'}:
|
|
raise ValueError('file transport must be local')
|
|
if parsed.query or parsed.fragment or not parsed.path:
|
|
raise ValueError('transport URL must identify a repository path')
|
|
return value
|
|
|
|
|
|
def _mirror_source(value, target, env):
|
|
"""Allow injected HTTP only as a verified, credential-free mirror alias."""
|
|
if isinstance(value, str) and value.startswith('http://'):
|
|
server = env.get('GITHUB_SERVER_URL', '').rstrip('/')
|
|
parsed = urlsplit(server)
|
|
owner = env.get('GITHUB_REPOSITORY', '').partition('/')[0]
|
|
if (parsed.scheme not in {'http', 'https'} or not parsed.netloc or
|
|
parsed.username or parsed.password or parsed.query or parsed.fragment or
|
|
not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*', owner) or
|
|
not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*\.git', target.name) or
|
|
value != server + '/' + owner + '/' + target.name):
|
|
raise ValueError('HTTP mirror source must match the injected server and repository')
|
|
return value
|
|
return _url(value)
|
|
|
|
|
|
def environment(root=None):
|
|
root = Path(root) if root is not None else Path(__file__).resolve().parents[2]
|
|
path = root / 'components.json'
|
|
config = json.loads(path.read_text()).get('transport', {}) if path.exists() else {}
|
|
env = os.environ.copy()
|
|
env.update(GOWORK='off', GOTOOLCHAIN='local')
|
|
private = config.get('go_private', [])
|
|
if not isinstance(private, list) or any(not isinstance(p, str) or not p or any(c.isspace() for c in p) for p in private):
|
|
raise ValueError('invalid private module prefixes')
|
|
if private:
|
|
env.update(GOPRIVATE=','.join(private), GONOPROXY=','.join(private), GONOSUMDB=','.join(private))
|
|
rewrites = config.get('git_rewrites', [])
|
|
if not isinstance(rewrites, list):
|
|
raise ValueError('invalid Git transport mappings')
|
|
if rewrites:
|
|
inherited = []
|
|
marker = env.get('CI_DEPENDENCY_MIRRORS')
|
|
if marker:
|
|
mirrors = Path(marker)
|
|
if not mirrors.is_absolute() or not mirrors.is_dir() or mirrors.is_symlink():
|
|
raise ValueError('invalid CI dependency mirror identity')
|
|
mirrors = mirrors.resolve()
|
|
try:
|
|
count = int(env.get('GIT_CONFIG_COUNT', '0'))
|
|
except ValueError as error:
|
|
raise ValueError('invalid process Git configuration') from error
|
|
for i in range(count):
|
|
key, value = env.get(f'GIT_CONFIG_KEY_{i}', ''), env.get(f'GIT_CONFIG_VALUE_{i}', '')
|
|
if (key, value) in {('protocol.file.allow', 'always'), ('core.hooksPath', '/dev/null'), ('credential.helper', '')}:
|
|
inherited.append((key, value))
|
|
elif key.startswith('url.file://') and key.endswith('.insteadOf'):
|
|
target = Path(unquote(urlsplit(key[4:-10]).path)).resolve()
|
|
if mirrors not in target.parents or not target.is_dir():
|
|
raise ValueError('CI mirror source escapes its registered directory')
|
|
inherited.append((key, _mirror_source(value, target, env)))
|
|
# Do not inherit temporary authentication configuration into candidate code.
|
|
for key in list(env):
|
|
if key == 'GIT_CONFIG_COUNT' or key.startswith(('GIT_CONFIG_KEY_', 'GIT_CONFIG_VALUE_')):
|
|
del env[key]
|
|
entries = list(inherited)
|
|
for rewrite in rewrites:
|
|
if not isinstance(rewrite, dict) or set(rewrite) != {'from', 'to'}:
|
|
raise ValueError('invalid Git transport mapping')
|
|
source, target = _url(rewrite['from']), _url(rewrite['to'])
|
|
entries.append((f'url.{target}.insteadOf', source))
|
|
for i, (key, value) in enumerate(entries):
|
|
env[f'GIT_CONFIG_KEY_{i}'], env[f'GIT_CONFIG_VALUE_{i}'] = key, value
|
|
env['GIT_CONFIG_COUNT'] = str(len(entries))
|
|
return env
|