79 lines
6.7 KiB
Python
79 lines
6.7 KiB
Python
"""Independent QA for service limits and cross-confirmed bootstrap; fixed artefacts stay intact."""
|
|
from pathlib import Path
|
|
import json, datetime, subprocess, os, re, shutil, hashlib
|
|
|
|
R = Path(__file__).resolve().parent.parent
|
|
P = R / 'storage/app/private/investmoz-v2/portal-audit'
|
|
read = lambda p: json.loads(Path(p).read_text(encoding='utf-8-sig'))
|
|
sha = lambda p: hashlib.sha256(Path(p).read_bytes()).hexdigest()
|
|
old = read(P / 'workflow-upgrade-target-backup-20261011.json')
|
|
previous = read(P / 'workflow-upgrade-latest.json')
|
|
assert old['restore_verified'] and old['files_verified'] and len(old['tables']) == 288
|
|
assert sha(old['dump_path']) == old['dump_sha256'] and sha(old['files_path']) == old['files_sha256']
|
|
stamp = datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%d_%H%M%S')
|
|
db = 'workflow_upgrade_' + stamp + '_test'
|
|
source = P / ('workflow-service-followup-source-' + stamp)
|
|
assert re.fullmatch(r'workflow_upgrade_\d{8}_\d{6}_test', db) and not source.exists()
|
|
pg = Path('C:/Program Files/PostgreSQL/17/bin')
|
|
env = dict(os.environ, PGPASSWORD='')
|
|
def run(args):
|
|
result = subprocess.run([str(x) for x in args], env=env, capture_output=True, text=True, timeout=300)
|
|
assert result.returncode == 0, 'Isolated preparation failed; no destination operation'
|
|
return result.stdout.strip()
|
|
assert run([pg/'psql.exe','-h','127.0.0.1','-p','55441','-U','postgres','-d','postgres','-Atc','SELECT inet_server_port()']) == '55441'
|
|
run([pg/'createdb.exe','-h','127.0.0.1','-p','55441','-U','postgres',db])
|
|
run([pg/'pg_restore.exe','-h','127.0.0.1','-p','55441','-U','postgres','-d',db,'--exit-on-error','--no-owner','--no-privileges',old['dump_path']])
|
|
source.mkdir()
|
|
original = Path(previous['source'])
|
|
for folder in ['app','config','routes','database','resources','public']:
|
|
shutil.copytree(original/folder, source/folder, ignore=shutil.ignore_patterns('build-qa-*','*.bin'))
|
|
(source/'bootstrap/cache').mkdir(parents=True)
|
|
for file in ['bootstrap/app.php','bootstrap/providers.php','qa-autoload.php','composer.json','composer.lock','package.json','package-lock.json','vite.config.ts']:
|
|
shutil.copyfile(original/file,source/file)
|
|
for folder in ['storage/app/private','storage/framework/cache/data','storage/framework/sessions','storage/framework/views','storage/logs','tools']:
|
|
(source/folder).mkdir(parents=True,exist_ok=True)
|
|
archive = Path(old['dump_path']).parent
|
|
shutil.copytree(archive/'files-restored/private',source/'storage/app/private',dirs_exist_ok=True)
|
|
runtime = read(archive/'effective-runtime.json')
|
|
text = (archive/'files-restored/runtime/.env').read_text(encoding='utf-8-sig')
|
|
manager = re.search(r'^PORTAL_MANAGER_EMAILS=(.*)$',text,re.M)
|
|
values = {
|
|
'APP_ENV':'testing','APP_DEBUG':'false','APP_URL':'https://portal-investidor.test:18448',
|
|
'APP_KEY':runtime['app_key'],'APP_PREVIOUS_KEYS':','.join(runtime['previous_keys']),'APP_CIPHER':runtime['cipher'],
|
|
'DB_CONNECTION':'pgsql','DB_HOST':'127.0.0.1','DB_PORT':'55441','DB_DATABASE':db,'DB_USERNAME':'postgres','DB_PASSWORD':'','DB_URL':'',
|
|
'SESSION_COOKIE':'investmoz_service_followup_'+stamp,'SESSION_DOMAIN':'null','SESSION_PATH':'/','SESSION_SECURE_COOKIE':'true','SESSION_SAME_SITE':'lax',
|
|
'CACHE_STORE':'database','QUEUE_CONNECTION':'database','DB_QUEUE':'services-followup-qa','DB_QUEUE_RETRY_AFTER':'210',
|
|
'MAIL_MAILER':'log','PORTAL_NOTIFICATIONS_ENABLED':'false','PORTAL_ORGANIZATION_COUNTRY_ENABLED':'true',
|
|
'PORTAL_PROFILE_ACCESS_ENABLED':'false','PORTAL_OPPORTUNITY_DASHBOARD_ACCESS_ENABLED':'false','PORTAL_OPPORTUNITY_SPECIALIZED_DRAFTS_ENABLED':'false',
|
|
'PORTAL_OPPORTUNITY_MODULAR_PROFILES_QA_ENABLED':'false','PORTAL_LEGACY_CONTENT_QA_ENABLED':'false','INVESTOR_PATH_ENABLED':'false',
|
|
'CLAMD_PORT':'13310','INERTIA_SSR_ENABLED':'false',
|
|
'PORTAL_MANAGER_EMAILS': ((manager.group(1).strip("\"'")+',') if manager else '')+'workflow-first-qa@example.invalid,workflow-second-qa@example.invalid',
|
|
}
|
|
for key,value in values.items():
|
|
assert len(re.findall('^'+key+'=',text,re.M)) <= 1
|
|
text = re.sub('^'+key+'=.*$',lambda _:key+'='+value,text,flags=re.M) if re.search('^'+key+'=',text,re.M) else text+'\n'+key+'='+value+'\n'
|
|
(source/'.env').write_text(text,encoding='utf-8')
|
|
subprocess.run(['powershell','-NoProfile','-Command',f"New-Item -ItemType Junction -Path '{source}/vendor' -Target '{R}/vendor' | Out-Null"],check=True,capture_output=True)
|
|
guard = (original/'tools/upgrade-runtime.php').read_text(encoding='utf-8').replace(':18446',':18448')
|
|
(source/'tools/upgrade-runtime.php').write_text(guard,encoding='utf-8')
|
|
shutil.copyfile(original/'public/index.php',source/'public/index.php')
|
|
conf = Path(previous['apache_conf']).read_text(encoding='utf-8')
|
|
conf = conf.replace(original.as_posix(),source.as_posix()).replace(':18446',':18448').replace(':18108',':18110')
|
|
conf = conf.replace('ServerName portal-investidor.test:18446','ServerName portal-investidor.test:18448')
|
|
https_pos = conf.index('<VirtualHost 127.0.0.1:18448>')
|
|
conf = conf[:https_pos] + conf[https_pos:].replace('DocumentRoot ', 'php_admin_value upload_max_filesize 20M\nphp_admin_value post_max_size 24M\nphp_admin_value memory_limit 256M\nphp_admin_value max_execution_time 180\nDocumentRoot ', 1)
|
|
(source/'apache-qa').mkdir()
|
|
(source/'apache-qa/httpd.conf').write_text(conf,encoding='utf-8')
|
|
plan = {**previous,'source':str(source),'target':{'database':db,'port':55441},'base':'https://portal-investidor.test:18448',
|
|
'http_base':'http://portal-investidor.test:18110','apache_conf':str(source/'apache-qa/httpd.conf'),
|
|
'backup_proof':str(P/'workflow-upgrade-target-backup-20261011.json'),'created_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
'previous_candidate':'98a8e0b70892','active_writes':0,'queue':'services-followup-qa'}
|
|
(source/'upgrade-qa.json').write_text(json.dumps(plan,indent=2),encoding='utf-8')
|
|
(P/'service-followup-latest.json').write_text(json.dumps(plan,indent=2),encoding='utf-8')
|
|
snapshot = {'candidate':read(R/'outputs/investmoz-workflow-upgrade-20261011/pacote.json'),
|
|
'candidate_file_hashes':{p.name:sha(p) for p in (R/'outputs/investmoz-workflow-upgrade-20261011').iterdir() if p.is_file()},
|
|
'service_plan_file_hashes':{p.name:sha(p) for p in (R/'outputs/investmoz-workflow-service-plan-20261011').iterdir() if p.is_file()},
|
|
'active_hashes':{p:sha(R/p) for p in ['.env','public/build/manifest.json','app/Services/PortalSuperusers.php','app/Services/PortalProfileInstallation.php','app/Http/Controllers/PortalProfileInstallationController.php','routes/web.php','resources/js/pages/profiles/installation.tsx']}}
|
|
(P/'service-followup-preservation-before.json').write_text(json.dumps(snapshot,indent=2),encoding='utf-8')
|
|
print(json.dumps({'result':'new_isolated_clone_prepared','base':plan['base'],'target':plan['target'],'active_writes':0}))
|