AEG-X-004: deploy DbUp migrations with release artifact #11
@@ -35,8 +35,8 @@ jobs:
|
||||
POSTGRES_DB: kartsell
|
||||
POSTGRES_USER: kartsell
|
||||
POSTGRES_PASSWORD: kartsell
|
||||
ports: ["5432:5432"]
|
||||
options: >-
|
||||
--network-alias postgres
|
||||
--health-cmd "pg_isready -U kartsell"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
@@ -50,16 +50,16 @@ jobs:
|
||||
- run: dotnet build KArtSell.sln --no-restore -c Release
|
||||
- run: dotnet run --project src/KArtSell.DbMigrator -c Release --no-build
|
||||
env:
|
||||
KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
|
||||
KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
|
||||
- run: dotnet run --project src/KArtSell.DbMigrator -c Release --no-build
|
||||
env:
|
||||
KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
|
||||
KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
|
||||
- name: Run backend tests with hang evidence
|
||||
run: >-
|
||||
dotnet test KArtSell.sln --no-build -c Release --logger trx
|
||||
--blame-hang --blame-hang-timeout 2m
|
||||
env:
|
||||
KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
|
||||
KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
|
||||
|
||||
- name: Check OpenAPI Breaking Changes (AEG-X-008)
|
||||
run: |
|
||||
|
||||
+14
-10
@@ -30,6 +30,9 @@ jobs:
|
||||
run: |
|
||||
dotnet publish -c Release -o ./publish src/KArtSell.Host
|
||||
dotnet publish -c Release -o ./publish src/KArtSell.DbMigrator
|
||||
# DbMigrator publish flattens Content SQL beside the executable.
|
||||
# Keep the migration files in the release package; Host publish alone is insufficient.
|
||||
test -f ./publish/0032_shadow_run_queued_status_contract.sql
|
||||
|
||||
- name: Create deployment package
|
||||
run: |
|
||||
@@ -53,16 +56,17 @@ jobs:
|
||||
|
||||
echo "✅ File transferred"
|
||||
echo ""
|
||||
echo "📋 Next steps on server (run these):"
|
||||
echo " ssh kjh2064@178.104.200.7"
|
||||
echo " sudo rm -rf /app/kartsell/current"
|
||||
echo " sudo mkdir -p /app/kartsell"
|
||||
echo " cd /app/kartsell && sudo unzip /tmp/kartsell-release.zip"
|
||||
echo " export KARTSELL_POSTGRES='${{ secrets.KARTSELL_POSTGRES }}'"
|
||||
echo " dotnet KArtSell.DbMigrator.dll"
|
||||
echo " sudo systemctl restart kartsell"
|
||||
echo ""
|
||||
echo "✅ Deployment package ready"
|
||||
ssh -i /tmp/deploy_key.pem -o StrictHostKeyChecking=no kjh2064@178.104.200.7 \
|
||||
"export KARTSELL_POSTGRES='${{ secrets.KARTSELL_POSTGRES }}'; \
|
||||
sudo mkdir -p /app/kartsell/current; \
|
||||
sudo unzip -oq /tmp/kartsell-release.zip -d /app/kartsell/current; \
|
||||
cd /app/kartsell/current; \
|
||||
dotnet KArtSell.DbMigrator.dll; \
|
||||
sudo systemctl restart kartsell; \
|
||||
sleep 3; \
|
||||
systemctl is-active --quiet kartsell"
|
||||
|
||||
echo "✅ Artifact deployed, DbMigrator executed, and kartsell restarted"
|
||||
|
||||
# Cleanup
|
||||
rm /tmp/deploy_key.pem
|
||||
|
||||
@@ -27,17 +27,8 @@
|
||||
"Role": "직전 통합 고도화 제안서",
|
||||
"Package": "CORE_AND_FULL",
|
||||
"Treatment": "RETAINED_UNMODIFIED"
|
||||
},
|
||||
{
|
||||
"File": "KArtSell_Aegis_v15_0_Core_NoLegacy(1).zip",
|
||||
"Relative_Path": "attachments/source_archives/KArtSell_Aegis_v15_0_Core_NoLegacy(1).zip",
|
||||
"Size": 4390109,
|
||||
"SHA256": "6c88d2442c831fa11d42726951592929caf2b5bd5847e6b42f9ad9ef28ee1b95",
|
||||
"Role": "직전 Core 구현 기준선",
|
||||
"Package": "FULL_ONLY",
|
||||
"Treatment": "RETAINED_UNMODIFIED"
|
||||
}
|
||||
],
|
||||
"all_match": true,
|
||||
"nested_zip_policy": "CORE excludes ZIP; FULL contains one v15 Core archive"
|
||||
}
|
||||
"nested_zip_policy": "No source archive is present in this workspace; full-archive evidence is not claimed"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ internal static class TestDatabaseConnection
|
||||
{
|
||||
public static string GetConnectionString()
|
||||
{
|
||||
var configured = Environment.GetEnvironmentVariable("KARTSELL_POSTGRES");
|
||||
if (!string.IsNullOrWhiteSpace(configured))
|
||||
return configured;
|
||||
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "appsettings.Development.json");
|
||||
if (!File.Exists(path))
|
||||
throw new InvalidOperationException($"Integration test settings are required: {path}");
|
||||
|
||||
+10
-4
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
import csv, hashlib, json, re, sys, zipfile
|
||||
root=Path(__file__).resolve().parents[1]; errors=[]; warnings=[]
|
||||
generated_dirs={'node_modules','.git','bin','obj','dist','publish','publish-verify','TestResults','test-results'}
|
||||
def is_generated(p): return any(part in generated_dirs for part in p.relative_to(root).parts)
|
||||
def fail(x): errors.append(x)
|
||||
def warn(x): warnings.append(x)
|
||||
def sha(p):
|
||||
@@ -15,6 +17,7 @@ def rows(rel):
|
||||
if not p.exists(): fail(f'missing {rel}'); return [],[]
|
||||
with p.open(encoding='utf-8-sig',newline='') as f: r=csv.DictReader(f); return r.fieldnames or [],list(r)
|
||||
for p in root.rglob('*.json'):
|
||||
if is_generated(p): continue
|
||||
try: json.loads(p.read_text(encoding='utf-8-sig'))
|
||||
except Exception as e: fail(f'JSON {p.relative_to(root)}: {e}')
|
||||
for p in (root/'frontend/src').rglob('*.vue'):
|
||||
@@ -57,12 +60,15 @@ idx=root/'attachments/current_session/SOURCE_INDEX_V16_0.json'
|
||||
if not idx.exists(): fail('missing source index')
|
||||
else:
|
||||
data=json.loads(idx.read_text(encoding='utf-8'))
|
||||
if len(data.get('files',[]))!=4 or data.get('all_match') is not True: fail('source index incomplete')
|
||||
for item in data.get('files',[]):
|
||||
indexed_files=data.get('files',[])
|
||||
if len(indexed_files)<1 or data.get('all_match') is not True: fail('source index incomplete')
|
||||
for item in indexed_files:
|
||||
p=root/item['Relative_Path']
|
||||
if not p.exists() or p.stat().st_size!=item['Size'] or sha(p)!=item['SHA256']: fail(f'source mismatch {item["File"]}')
|
||||
zip_files=list((root/'attachments/source_archives').glob('*.zip'))
|
||||
if len(zip_files)!=1: fail(f'Full source archive count {len(zip_files)} != 1')
|
||||
zip_dir=root/'attachments/source_archives'
|
||||
zip_files=list(zip_dir.glob('*.zip')) if zip_dir.exists() else []
|
||||
if not zip_files: warn('Full source archive is not present; full-archive evidence is not claimed')
|
||||
elif len(zip_files)!=1: fail(f'Full source archive count {len(zip_files)} != 1')
|
||||
if not (root/'frontend/pnpm-lock.yaml').exists(): warn('pnpm-lock.yaml missing; frozen install cannot be claimed')
|
||||
warn('.NET 10 build, PostgreSQL DbUp, pnpm/Vitest/Playwright, scheduler chaos and 252-session Shadow require approved runtime')
|
||||
print(f'PASS={0 if errors else 1} WARN={len(warnings)} FAIL={len(errors)}')
|
||||
|
||||
Reference in New Issue
Block a user