Files
jobtrackingapp/docs/redesign/phase-9/p9-requal-01/linux-run.py
T
cesnimda 5ac11549b2
MariaDB persistence qualification / mariadb (push) Failing after 7s
test: harden database qualification startup and cleanup evidence
2026-10-24 14:04:57 +02:00

142 lines
9.0 KiB
Python

"""Disposable local Linux renderer; no host daemon is exposed to the API container."""
import datetime, json, os, re, subprocess, sys, time, urllib.request, uuid
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / '.artifacts/p9-requal-01/linux'
IMAGE = 'jobtracker-p9-requal-api:security-check'
DB = 'jobtracker-it-p9-requal-mariadb-test-1'
PIN = 'mariadb:12.2.2@sha256:42a27baaba3e8f3f0159d16381d5a7156661aa91dcc65f663cabb6a6f9503c00'
VERSION = 'p9-requal-linux-2b26953'
def guard_schema(name):
if not re.fullmatch(r'jt_it_[0-9a-f]{32}', name):
raise ValueError('Only a generated qualification schema is allowed.')
return name
def command(args, **kwargs):
return subprocess.run(['rtk', 'proxy', *args], check=True, capture_output=True, text=True, encoding='utf-8', **kwargs).stdout.strip()
def sql(query):
return command(['docker', 'exec', '-e', 'MYSQL_PWD=jt-integration-only', DB,
'mariadb', '-uroot', '--batch', '--skip-column-names', '-e', query])
def safe_error(error):
# CalledProcessError includes command arguments, including development-only credentials.
return str(error).replace('jt-integration-only','[test-password]').replace('E2ePassword123!','[test-password]').replace('e2e-only-key-at-least-thirty-two-characters-long','[test-key]')
def main():
if '--self-test' in sys.argv:
assert guard_schema('jt_it_'+'a'*32)
for name in ['jobtracker', 'jt_it_abc', 'jt_it_'+'a'*32+';DROP DATABASE x']:
try: guard_schema(name)
except ValueError: continue
raise AssertionError(name)
print('Linux qualification schema guards: PASS')
return
OUT.mkdir(parents=True, exist_ok=True)
if (OUT/'process.json').exists() or (OUT.parent/'linux-renderer.json').exists():
raise ValueError('Retain prior evidence; select a fresh run directory.')
owned = json.loads(command(['docker','inspect',DB]))[0]
assert owned['Config']['Image'] == PIN
assert owned['Config']['Labels']['com.docker.compose.project'] == 'jobtracker-it-p9-requal'
assert owned['Config']['Labels']['com.docker.compose.service'] == 'mariadb-test'
assert '/var/lib/mysql' in owned['HostConfig'].get('Tmpfs',{})
assert not owned['HostConfig'].get('Privileged') and owned['HostConfig'].get('NetworkMode')!='host'
assert owned['State']['Running'] and owned['State']['Health']['Status']=='healthy'
assert owned['HostConfig']['PortBindings']['3306/tcp'] == [{'HostIp':'127.0.0.1','HostPort':'33308'}]
actual = sql('SELECT VERSION()')
assert actual.startswith('12.2.2-MariaDB'), actual
name=guard_schema('jt_it_'+uuid.uuid4().hex)
suffix=uuid.uuid4().hex[:12]
api='jobtracker-p9-requal-linux-'+suffix
network='jobtracker-p9-requal-linux-'+suffix
record={'startedUtc':datetime.datetime.now(datetime.timezone.utc).isoformat(), 'schema':name,
'serverVersion':actual,'imageTag':IMAGE,'apiContainer':api,'network':network,'cleanup':{},'exitCode':1}
created=attached=network_created=api_created=False
try:
assert sql(f"SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='{name}'")=='0'
sql(f'CREATE DATABASE `{name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci')
created=True
assert sql(f"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='{name}'")=='0'
record['imageId']=command(['docker','image','inspect','--format','{{.Id}}',IMAGE])
record['chromium']=command(['docker','run','--rm','--network','none','--entrypoint','/usr/bin/chromium',IMAGE,'--version'])
record['runtime']=command(['docker','run','--rm','--network','none','--entrypoint','dotnet',IMAGE,'--info'])
command(['docker','network','create','--label','task=p9-requal-01',network]); network_created=True
command(['docker','network','connect','--alias','mariadb',network,DB]); attached=True
variables={
'ASPNETCORE_ENVIRONMENT':'Development','Database__Provider':'mariadb','Database__ServerVersion':'12.2.2',
'ConnectionStrings__JobTracker':f'Server=mariadb;Port=3306;Database={name};User ID=root;Password=jt-integration-only;SslMode=None',
'Auth__Require':'true','Auth__JwtKey':'e2e-only-key-at-least-thirty-two-characters-long',
'Auth__AdminEmail':'e2e@example.test','Auth__AdminPassword':'E2ePassword123!', 'Auth__AllowRegistration':'false',
'RateLimits__AuthLoginPermitLimit':'1000','Cors__Origins__0':'http://localhost:3300',
'Workers__AiOperationsEnabled':'false','Workers__SavedJobSearchAlertsEnabled':'false',
'HttpsRedirection__Enabled':'false','App__Version':VERSION,'App__CommitSha':'2b26953',
'Data__Root':'/data','Logging__LogLevel__Default':'Warning'}
args=['docker','create','--name',api,'--label','task=p9-requal-01','--network',network,
'-p','127.0.0.1:5302:8080','--tmpfs','/data:rw,nosuid,size=512m']
for key,value in variables.items(): args+=['-e',key+'='+value]
command(args+[IMAGE]); api_created=True
assert command(['docker','inspect','--format','{{.Image}}',api])==record['imageId']
command(['docker','start',api])
deadline=time.monotonic()+120
while True:
try:
with urllib.request.urlopen('http://localhost:5302/health',timeout=3) as response:
health=json.load(response)
assert health['version']==VERSION, health
with urllib.request.urlopen('http://localhost:5302/ready',timeout=3) as response:
assert json.load(response)['status']=='ready'
break
except Exception:
if time.monotonic()>=deadline: raise
time.sleep(1)
record['health']=health
record['migrationCount']=int(sql(f'SELECT COUNT(*) FROM `{name}`.`__EFMigrationsHistory`'))
assert record['migrationCount']>0
env={**os.environ,'P9_REQUAL_LINUX_READY':VERSION,'PLAYWRIGHT_STATIC_EXPORT':'true',
'PLAYWRIGHT_PREBUILT_EXPORT':'true','NEXT_PUBLIC_API_BASE_URL':'http://localhost:5302/api',
'CV_FIDELITY_OUTPUT':str(OUT/'fidelity'),'CV_SETTINGS_FIDELITY_OUTPUT':str(OUT/'settings-fidelity'),
'PLAYWRIGHT_JSON_OUTPUT_NAME':str(OUT.parent/'linux-renderer.json')}
test=['node','node_modules/@playwright/test/cli.js','test',
'--config=../docs/redesign/phase-9/p9-requal-01/linux/playwright.config.ts',
'--retries=0','--reporter=list,json','--output='+str(OUT/'test-results')]
with (OUT/'playwright.log').open('w',encoding='utf-8') as log:
record['browserExitCode']=subprocess.run(['rtk','proxy',*test],cwd=ROOT/'job-tracker-ui',env=env,stdout=log,stderr=subprocess.STDOUT).returncode
record['exitCode']=record['browserExitCode']
record['migrationCountAfter']=int(sql(f'SELECT COUNT(*) FROM `{name}`.`__EFMigrationsHistory`'))
assert record['migrationCountAfter']==record['migrationCount']
except Exception as error:
record['exitCode']=1
record['error']=safe_error(error)
finally:
if api_created:
try:
(OUT/'api.log').write_text(safe_error(command(['docker','logs',api])),encoding='utf-8')
except Exception as error: record['cleanup']['logError']=safe_error(error)
try:
command(['docker','rm','--force',api]); record['cleanup']['apiRemoved']=True
except Exception as error: record['cleanup']['apiError']=str(error)
if attached:
try: command(['docker','network','disconnect',network,DB]); record['cleanup']['databaseDisconnected']=True
except Exception as error: record['cleanup']['disconnectError']=str(error)
if network_created:
try: command(['docker','network','rm',network]); record['cleanup']['networkRemoved']=True
except Exception as error: record['cleanup']['networkError']=str(error)
if created and (not api_created or record['cleanup'].get('apiRemoved')):
try:
guard_schema(name); sql(f'DROP DATABASE `{name}`')
assert sql(f"SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='{name}'")=='0'
record['cleanup']['schemaRemoved']=True
except Exception as error: record['cleanup']['schemaError']=str(error)
elif created:
record['cleanup']['schemaError']='Schema retained because API removal was not confirmed.'
if any(key.endswith('Error') for key in record['cleanup']): record['exitCode']=1
record['finishedUtc']=datetime.datetime.now(datetime.timezone.utc).isoformat()
(OUT/'process.json').write_text(json.dumps(record,indent=2)+'\n',encoding='utf-8')
(OUT.parent/'linux-renderer-exit.json').write_text(json.dumps(record,indent=2)+'\n',encoding='utf-8')
print(json.dumps(record))
if record['exitCode'] or any(key.endswith('Error') for key in record['cleanup']): raise SystemExit(1)
if __name__=='__main__': main()