Skip to content

Commit 0d4f35f

Browse files
Adds database deployment script and documentation (#78)
Introduces a script to streamline database deployments to test environments, including building projects and publishing DACPACs. Also, updates the documentation with instructions on how to use the new deployment script and seed the test databases.
1 parent 1ca3c9d commit 0d4f35f

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,46 @@ Services communicate asynchronously via RabbitMQ message queues. See the Message
4747
}
4848
```
4949

50+
## Database Deployment (Test Environments)
51+
52+
The `publish_db.py` script is provided for deploying database projects to test environments.
53+
54+
### Prerequisites
55+
- Python 3
56+
- .NET SDK with sqlpackage tool installed
57+
58+
### Usage
59+
1. **Set required environment variables** before running:
60+
```bash
61+
export SERVER="your-test-sql-server-address"
62+
export DB_USER="your-database-username"
63+
export DB_PASS="your-database-password"
64+
```
65+
66+
2. **Run the script from the project root directory**:
67+
```bash
68+
# Preview changes without deploying
69+
python3 publish_db.py --dry-run
70+
71+
# Deploy to test environment (Release build - recommended)
72+
python3 publish_db.py
73+
74+
# Deploy using Debug build
75+
python3 publish_db.py --config Debug
76+
```
77+
78+
The script will build and publish all database projects (AuditDb, OfflocStagingDb, DeliusStagingDb, OfflocRunningPictureDb, DeliusRunningPictureDb, MatchingDb, ClusterDb) to the specified test server. You will be prompted to confirm before deployment begins.
79+
80+
#### Seeding Test Data
81+
82+
If you want to seed the test data, you can run the Fake Data Seeder project.
83+
84+
```bash
85+
export ConnectionStrings__ClusterDb="Server=$SERVER;Database=ClusterDb;User Id=$DB_USER;Password=$DB_PASS;TrustServerCertificate=True;"
86+
87+
dotnet run --project ./src/FakeDataSeeder/FakeDataSeeder.csproj;
88+
```
89+
5090
## Running the apps
5191
The recommended way to run and debug these apps is using .NET Aspire.
5292
- **Using Visual Studio Code**: open the project and press `F5`, selecting the *Default Configuration*.

publish_db.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
2+
#!/usr/bin/env python3
3+
import os
4+
import sys
5+
import subprocess
6+
import argparse
7+
from datetime import datetime
8+
from pathlib import Path
9+
10+
def log(message):
11+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
12+
print(f"[{timestamp}] {message}")
13+
14+
def validate_environment():
15+
SERVER = os.environ.get("SERVER")
16+
DB_USER = os.environ.get("DB_USER")
17+
DB_PASS = os.environ.get("DB_PASS")
18+
19+
if not SERVER or not DB_USER or not DB_PASS:
20+
log("ERROR: Missing required environment variables.")
21+
print("Required: SERVER, DB_USER, DB_PASS")
22+
sys.exit(1)
23+
24+
return SERVER, DB_USER, DB_PASS
25+
26+
def validate_project_files(databases, build_config):
27+
log("Validating project files...")
28+
missing_files = []
29+
for db in databases:
30+
project_file = Path(f"./src/database/{db}/{db}.sqlproj")
31+
if not project_file.exists():
32+
missing_files.append(str(project_file))
33+
34+
if missing_files:
35+
log("ERROR: Missing project files:")
36+
for f in missing_files:
37+
print(f" - {f}")
38+
sys.exit(1)
39+
40+
log("All project files found.")
41+
42+
def build_all_databases(databases, build_config, dry_run):
43+
log(f"Building all database projects ({build_config} configuration)...")
44+
45+
if dry_run:
46+
log("[DRY RUN] Would build all projects")
47+
return True
48+
49+
try:
50+
for db in databases:
51+
log(f"Building {db}...")
52+
subprocess.check_call(
53+
["dotnet", "build", f"./src/database/{db}/{db}.sqlproj",
54+
"-c", build_config, "--nologo", "-v", "minimal"],
55+
stdout=subprocess.DEVNULL
56+
)
57+
log("All projects built successfully.")
58+
return True
59+
except subprocess.CalledProcessError as e:
60+
log(f"ERROR: Build failed for {db}")
61+
return False
62+
63+
def publish_database(db, server, user, password, build_config, dry_run):
64+
conn = (
65+
f"Server={server};Database={db};"
66+
f"User Id={user};Password={password};"
67+
f"TrustServerCertificate=True;"
68+
)
69+
70+
dacpac_path = f"./src/database/{db}/bin/{build_config}/{db}.dacpac"
71+
72+
if dry_run:
73+
log(f"[DRY RUN] Would publish {dacpac_path} to {server}/{db}")
74+
return True
75+
76+
log(f"Publishing {db} to {server}...")
77+
78+
try:
79+
subprocess.check_call([
80+
"dotnet", "sqlpackage",
81+
"/action:Publish",
82+
f"/SourceFile:{dacpac_path}",
83+
f"/TargetConnectionString:{conn}",
84+
"/v:OfflocRunningPictureDb=OfflocRunningPictureDb",
85+
"/v:DeliusRunningPictureDb=DeliusRunningPictureDb",
86+
"/v:MatchingDb=MatchingDb",
87+
"/Quiet:True"
88+
])
89+
log(f"Successfully published {db}")
90+
return True
91+
except subprocess.CalledProcessError as e:
92+
log(f"ERROR: Failed to publish {db}")
93+
return False
94+
95+
def main():
96+
parser = argparse.ArgumentParser(description="Deploy database projects to test environments")
97+
parser.add_argument("--dry-run", action="store_true", help="Show what would be done without making changes")
98+
parser.add_argument("--config", choices=["Debug", "Release"], default="Release", help="Build configuration (default: Release)")
99+
args = parser.parse_args()
100+
101+
DATABASES = [
102+
"AuditDb",
103+
"OfflocStagingDb",
104+
"DeliusStagingDb",
105+
"OfflocRunningPictureDb",
106+
"DeliusRunningPictureDb",
107+
"MatchingDb",
108+
"ClusterDb",
109+
]
110+
111+
log("=== Database Deployment Script ===")
112+
113+
SERVER, DB_USER, DB_PASS = validate_environment()
114+
115+
log(f"Target Server: {SERVER}")
116+
log(f"User: {DB_USER}")
117+
log(f"Build Config: {args.config}")
118+
log(f"Databases: {', '.join(DATABASES)}")
119+
120+
if args.dry_run:
121+
log("DRY RUN MODE - No changes will be made")
122+
else:
123+
response = input("\nProceed with deployment? (yes/no): ")
124+
if response.lower() not in ["yes", "y"]:
125+
log("Deployment cancelled by user")
126+
sys.exit(0)
127+
128+
validate_project_files(DATABASES, args.config)
129+
130+
if not build_all_databases(DATABASES, args.config, args.dry_run):
131+
log("Deployment aborted due to build failures")
132+
sys.exit(1)
133+
134+
failed_databases = []
135+
for db in DATABASES:
136+
if not publish_database(db, SERVER, DB_USER, DB_PASS, args.config, args.dry_run):
137+
failed_databases.append(db)
138+
139+
if failed_databases:
140+
log(f"ERROR: Deployment completed with failures: {', '.join(failed_databases)}")
141+
sys.exit(1)
142+
143+
log("=== All databases deployed successfully ===")
144+
145+
if __name__ == "__main__":
146+
main()

0 commit comments

Comments
 (0)