Files
kefu/deploy/ark-chat-config-20260917/04-verify.sh
T
2026-09-21 10:34:06 +08:00

62 lines
4.7 KiB
Bash

set -euo pipefail
release=/home/ps/ark-chat-config-20260917
docker exec -i im-admin-api-1 python -u - <<'PY' > "$release/after.json"
from pathlib import Path
import sqlite3,json,hashlib
from unittest import mock
import admin_backend as backend
c=sqlite3.connect('file:/data/backend.db?mode=ro',uri=True,timeout=30)
counts={t:c.execute('SELECT COUNT(*) FROM '+t).fetchone()[0] for t in ['archive_message','knowledge_item','desktop_accounts','model_providers','users']}
providers=c.execute('SELECT * FROM model_providers ORDER BY id').fetchall()
config=backend.model_test_config({'AI_PROVIDER_TYPE':'openai','AI_API_BASE':'https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks','AI_MODEL':'doubao-seedance-2-5-260628','AI_ENDPOINT_MODE':'exact'}, {})
with mock.patch.object(backend,'_perform_http_request',side_effect=AssertionError('Unexpected network request')) as request:
result=backend.test_model_connection(config)
request.assert_not_called()
assert not result['ok'] and result['http_status'] is None and 'Chat API' in result['message']
print(json.dumps({'counts':counts,'providers_digest':hashlib.sha256(json.dumps(providers,ensure_ascii=True,default=str).encode()).hexdigest(),'video_config_blocked_without_network':True}))
PY
python3 - <<'PY' | tee "$release/verification.json"
from pathlib import Path
import json,hashlib,subprocess,urllib.request,urllib.error
root=Path('/home/ps/ark-chat-config-20260917');target=Path('/opt/im-admin')
manifest=json.loads((root/'manifest.json').read_text())
for row in manifest:
assert hashlib.sha256((target/row['path']).read_bytes()).hexdigest()==row['sha256'],row['path']
expected={row['path']:row['sha256'] for row in manifest if row['path'].endswith('.py') or '/dist/' in row['path']}
code='from pathlib import Path; import json,hashlib,sys; paths=json.load(sys.stdin); print(json.dumps({p:hashlib.sha256((Path("/app")/p).read_bytes()).hexdigest() for p in paths}))'
actual=json.loads(subprocess.check_output(['docker','exec','-i','im-admin-api-1','python','-c',code],input=json.dumps(list(expected)).encode()))
assert expected==actual,'Runtime files differ from release'
dist=root/'payload/admin-web/apps/web-antd/dist'
assets=[str(p.relative_to(dist)) for p in (dist/'js').glob('model-catalog-*.js')]
assert len(assets)==1,assets
model_js=(dist/assets[0]).read_text()
assert 'Seedance' in model_js and '/contents/generations/tasks' in model_js and 'Chat API' in model_js
checks=[('/',200,'GET'),('/api/v2/health',200,'GET'),('/api/v2/models',401,'GET'),('/api/v2/models/test',401,'POST')]+[('/'+p,200,'GET') for p in assets]
statuses={}
for path,wanted,method in checks:
body=b'{"provider_id":"deployment-probe"}' if method=='POST' else None
request=urllib.request.Request('http://127.0.0.1:18766'+path,data=body,method=method,headers={'Content-Type':'application/json'})
try:
with urllib.request.urlopen(request,timeout=15) as response:
status=response.status;content=response.read()
if path=='/': assert content==(dist/'index.html').read_bytes()
if path.lstrip('/') in assets: assert content==(dist/path.lstrip('/')).read_bytes()
except urllib.error.HTTPError as error: status=error.code
assert status==wanted,(path,status,wanted)
statuses[method+' '+path]=status
wanted_image=subprocess.check_output(['docker','image','inspect','zyt/wecom-admin:ark-chat-config-20260917','--format','{{.Id}}'],text=True).strip()
states={}
for c in json.loads(subprocess.check_output(['docker','inspect','im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1'])):
assert c['State']['Running'] and c['RestartCount']==0 and c['Image']==wanted_image
health=c['State'].get('Health',{}).get('Status','not_configured')
assert health in ('healthy','not_configured')
name=c['Name'].lstrip('/')
states[name]={'running':True,'restarts':0,'health':health}
logs=subprocess.check_output(['docker','logs','--tail','100',name],stderr=subprocess.STDOUT,text=True)
assert 'Traceback (most recent call last)' not in logs and 'ERROR:' not in logs,name
before=json.loads((root/'before.json').read_text());after=json.loads((root/'after.json').read_text())
# Administrators can add models during a release; additions must survive deployment.
assert after['counts']['model_providers']>=before['counts']['model_providers'], 'Existing model count decreased'
print(json.dumps({'source_files_verified':len(manifest),'runtime_files_verified':len(expected),'http':statuses,'containers':states,'image':wanted_image,'model_config_snapshot_matches':after['providers_digest']==before['providers_digest'],'models_added_during_release':after['counts']['model_providers']-before['counts']['model_providers'],'video_config_blocked_without_network':after['video_config_blocked_without_network'],'counts':after['counts']}))
PY