This is an automated email from the ASF dual-hosted git repository. skrawcz pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/hamilton.git
commit 98ff08037a9411aacf820a657e0a476e20067c1e Author: Stefan Krawczyk <[email protected]> AuthorDate: Tue Feb 24 17:32:10 2026 -0800 Add PostgreSQL 12→16 migration scripts for Hamilton UI Hamilton UI now requires PostgreSQL 16 (upgraded from 12) to support Django 6.0.2+, which requires PostgreSQL 14+. This commit adds three migration strategies: 1. **migrate_postgres_simple.sh** (RECOMMENDED) - Uses Django's dumpdata/loaddata for safe migration - Handles Hamilton-specific Django apps correctly - Validates data before and after migration - Better error checking and recovery instructions 2. **backup_hamilton_data.sh** & **restore_hamilton_data.sh** - Standalone backup/restore using Django tools - Can be used independently for backups - Exports to portable JSON format 3. **migrate_postgres.sh** (Alternative approach) - Uses pg_dump/pg_restore for raw SQL migration - Provided for users who prefer PostgreSQL native tools Django approach benefits: - Only exports Hamilton application data (not system tables) - Handles foreign keys and permissions correctly - Portable JSON format works across PostgreSQL versions - Built-in validation via Django ORM Also adds UPGRADE.md documenting: - When migration is needed - Fresh start vs. data migration options - Troubleshooting common issues - Version compatibility matrix Closes backward compatibility concerns for PostgreSQL upgrade. --- ui/UPGRADE.md | 275 ++++++++++++++++++++++++++++++++++++++++++ ui/backup_hamilton_data.sh | 125 +++++++++++++++++++ ui/migrate_postgres.sh | 145 ++++++++++++++++++++++ ui/migrate_postgres_simple.sh | 246 +++++++++++++++++++++++++++++++++++++ ui/restore_hamilton_data.sh | 195 ++++++++++++++++++++++++++++++ 5 files changed, 986 insertions(+) diff --git a/ui/UPGRADE.md b/ui/UPGRADE.md new file mode 100644 index 00000000..e177c988 --- /dev/null +++ b/ui/UPGRADE.md @@ -0,0 +1,275 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +# Hamilton UI Upgrade Guide + +This guide covers backward compatibility concerns and migration steps when upgrading Hamilton UI. + +## Upgrading from PostgreSQL 12 to PostgreSQL 16 + +**Affected versions:** Upgrading to versions after commit `0da07178` (February 2026) + +### What Changed + +Hamilton UI Docker setup now uses **PostgreSQL 16** instead of PostgreSQL 12 to support Django 6.0.2+, which requires PostgreSQL 14 or later. + +### Do I Need to Migrate? + +**You need to migrate if ALL of the following are true:** +1. You have been using Hamilton UI with Docker (`./run.sh` or `./dev.sh`) +2. You have existing project data, runs, or artifacts you want to keep +3. You are upgrading from a version that used PostgreSQL 12 + +**You do NOT need to migrate if:** +- This is a fresh installation +- You don't care about preserving existing data +- You're using the PyPI package (`hamilton ui`) which uses SQLite by default + +### Migration Options + +#### Option 1: Fresh Start (Recommended for Development) + +If your data is not critical (development/testing), the simplest approach is to start fresh: + +```bash +cd hamilton/ui + +# Stop containers +./stop.sh + +# Remove old PostgreSQL 12 data +docker volume rm ui_postgres_data + +# Start with PostgreSQL 16 +./run.sh --build +``` + +**⚠️ Warning:** This will delete all existing projects, runs, and artifacts. + +#### Option 2: Migrate Your Data + +If you need to preserve your data, follow these steps: + +##### Step 1: Export data from PostgreSQL 12 + +While still using the old version: + +```bash +cd hamilton/ui + +# Ensure containers are running +docker compose up -d + +# Export data +docker compose exec db pg_dump -U hamilton hamilton > hamilton_backup.sql + +# Stop containers +docker compose down +``` + +##### Step 2: Upgrade to PostgreSQL 16 + +```bash +# Remove old PostgreSQL 12 volume +docker volume rm ui_postgres_data + +# Pull latest code with PostgreSQL 16 +git pull # or checkout the latest version + +# Start new containers +./run.sh --build +``` + +##### Step 3: Import data into PostgreSQL 16 + +```bash +# Wait for database to be ready +sleep 10 + +# Import data +docker compose exec -T db psql -U hamilton hamilton < hamilton_backup.sql + +# Verify data +docker compose exec db psql -U hamilton hamilton -c "\dt" +``` + +##### Step 4: Verify Migration + +Navigate to http://localhost:8242 and verify: +- Your projects exist +- Run history is preserved +- Artifacts are accessible + +##### Troubleshooting + +**Issue:** Import fails with "relation already exists" + +The Hamilton UI automatically runs migrations on startup, which creates empty tables. Your import will skip those errors - this is normal. + +**Issue:** Permission errors on import + +```bash +# Grant permissions to hamilton user +docker compose exec db psql -U hamilton hamilton -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO hamilton;" +docker compose exec db psql -U hamilton hamilton -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO hamilton;" +``` + +**Issue:** Blob/artifact files missing + +If you stored artifacts locally (not S3), you also need to copy the blob storage: + +```bash +# Identify blob storage location (check docker-compose.yml) +docker compose exec backend ls /code/blobs + +# If using local storage, copy blobs directory +cp -r old_blobs_directory new_blobs_directory +``` + +#### Option 3: Use pg_upgrade (Advanced) + +For minimal downtime migrations, you can use PostgreSQL's `pg_upgrade` tool. This is more complex and generally not recommended for Hamilton UI due to its development-focused nature. + +See [PostgreSQL pg_upgrade documentation](https://www.postgresql.org/docs/current/pgupgrade.html) for details. + +## Other Breaking Changes + +### Python Version (Internal) + +The Docker images now use **Python 3.12** instead of Python 3.8. This is transparent to users - no action needed. + +**Why:** Python 3.8 reached end-of-life in October 2024. Python 3.12 provides better performance and security. + +### Dependency Management (Internal) + +The Docker backend now uses **uv** instead of pip for dependency management. This is transparent to users - no action needed. + +**Why:** uv provides faster installs and reproducible builds via lock files. + +### Django 6.0+ Requirements + +Django 6.0.2+ requires PostgreSQL 14 or later, which is why we upgraded to PostgreSQL 16. + +## Migration Script + +For automated migration, you can use this script: + +```bash +#!/bin/bash +# migrate_postgres.sh - Migrate Hamilton UI from PostgreSQL 12 to 16 + +set -e + +BACKUP_FILE="hamilton_backup_$(date +%Y%m%d_%H%M%S).sql" + +echo "Hamilton UI PostgreSQL 12 → 16 Migration" +echo "========================================" +echo "" + +# Check if old containers are running +if ! docker compose ps | grep -q "ui-db"; then + echo "Error: Hamilton UI containers not running. Start them first with ./run.sh" + exit 1 +fi + +# Backup +echo "Step 1: Backing up PostgreSQL 12 data..." +docker compose exec -T db pg_dump -U hamilton hamilton > "$BACKUP_FILE" +echo "✓ Backup saved to: $BACKUP_FILE" +echo "" + +# Stop and remove +echo "Step 2: Stopping containers and removing old data..." +docker compose down +docker volume rm ui_postgres_data +echo "✓ Old data removed" +echo "" + +# Start new +echo "Step 3: Starting PostgreSQL 16..." +./run.sh --build +sleep 15 # Wait for initialization +echo "✓ PostgreSQL 16 ready" +echo "" + +# Restore +echo "Step 4: Restoring data..." +docker compose exec -T db psql -U hamilton hamilton < "$BACKUP_FILE" 2>&1 | grep -v "ERROR:.*already exists" || true +echo "✓ Data restored" +echo "" + +# Fix permissions +echo "Step 5: Fixing permissions..." +docker compose exec -T db psql -U hamilton hamilton -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO hamilton;" > /dev/null +docker compose exec -T db psql -U hamilton hamilton -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO hamilton;" > /dev/null +echo "✓ Permissions fixed" +echo "" + +echo "Migration complete!" +echo "Navigate to http://localhost:8242 to verify your data." +``` + +Save as `migrate_postgres.sh`, make executable with `chmod +x migrate_postgres.sh`, and run with `./migrate_postgres.sh`. + +## Getting Help + +If you encounter issues during migration: + +1. Check the [Hamilton UI documentation](https://hamilton.apache.org/concepts/ui) +2. Search or create an issue on [GitHub](https://github.com/apache/hamilton/issues) +3. Join our [Slack community](https://join.slack.com/t/hamilton-opensource/shared_invite/zt-2niepkra8-DGKGf_tTYhXuJWBTXtIs4g) + +## Version Compatibility Matrix + +| Hamilton UI Version | PostgreSQL Version | Python Version | Django Version | +|--------------------|--------------------|----------------|----------------| +| < 0.0.17 (2026-02) | 12 | 3.8 | 4.2 | +| ≥ 0.0.17 (2026-02) | 16 | 3.12 | 6.0.2 | + +## FAQ + +**Q: Can I continue using PostgreSQL 12?** + +No. Django 6.0.2+ explicitly requires PostgreSQL 14 or later. You must upgrade to at least PostgreSQL 14 (we recommend 16). + +**Q: Will my PyPI installation be affected?** + +No. The PyPI package (`apache-hamilton-ui`) uses SQLite by default for local development. This upgrade only affects Docker deployments. + +**Q: Can I downgrade if something goes wrong?** + +Yes, as long as you keep your backup file: + +```bash +# Checkout old version +git checkout <old_commit_before_upgrade> + +# Remove new volume +docker volume rm ui_postgres_data + +# Start old version +./run.sh --build + +# Restore from backup (if needed) +docker compose exec -T db psql -U hamilton hamilton < hamilton_backup.sql +``` + +**Q: Do I need to update my Hamilton SDK client code?** + +No. The Hamilton SDK (`apache-hamilton-sdk`) is compatible with both old and new versions of the UI backend. No client code changes required. diff --git a/ui/backup_hamilton_data.sh b/ui/backup_hamilton_data.sh new file mode 100755 index 00000000..87e601e3 --- /dev/null +++ b/ui/backup_hamilton_data.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# backup_hamilton_data.sh - Export Hamilton UI data using Django's dumpdata + +set -euo pipefail + +BACKUP_FILE="${1:-hamilton_data_$(date +%Y%m%d_%H%M%S).json}" + +echo "Hamilton UI Data Backup" +echo "=======================" +echo "" + +# Check if docker compose or docker-compose is available +if command -v docker-compose &> /dev/null; then + DOCKER_COMPOSE="docker-compose" +elif docker compose version &> /dev/null 2>&1; then + DOCKER_COMPOSE="docker compose" +else + echo "Error: Neither 'docker compose' nor 'docker-compose' found" + exit 1 +fi + +# Check if containers are running +if ! $DOCKER_COMPOSE ps --services --filter "status=running" | grep -q "backend"; then + echo "Error: Hamilton UI backend container is not running" + echo "Start containers first: ./run.sh or ./dev.sh" + exit 1 +fi + +# Check if database is ready +echo "Checking database connection..." +if ! $DOCKER_COMPOSE exec -T backend python manage.py migrate --check > /dev/null 2>&1; then + echo "Error: Database is not ready or migrations are pending" + echo "Run: $DOCKER_COMPOSE exec backend python manage.py migrate" + exit 1 +fi +echo "✓ Database connected" +echo "" + +# List Hamilton UI Django apps (excluding Django system apps) +HAMILTON_APPS=( + "trackingserver_base" + "trackingserver_auth" + "trackingserver_projects" + "trackingserver_run_tracking" + "trackingserver_template" +) + +echo "Backing up Hamilton UI data..." +echo "Apps: ${HAMILTON_APPS[*]}" +echo "" + +# Dump data using Django's dumpdata +# This is better than pg_dump because: +# - Handles foreign keys correctly +# - Portable JSON format (works across PostgreSQL versions) +# - Only dumps application data (not system tables) +# - Django handles restore ordering automatically +if ! $DOCKER_COMPOSE exec -T backend python manage.py dumpdata \ + --natural-foreign \ + --natural-primary \ + --indent 2 \ + "${HAMILTON_APPS[@]}" > "$BACKUP_FILE"; then + echo "Error: Backup failed" + rm -f "$BACKUP_FILE" + exit 1 +fi + +# Validate backup file +if [ ! -s "$BACKUP_FILE" ]; then + echo "Error: Backup file is empty" + rm -f "$BACKUP_FILE" + exit 1 +fi + +# Check if it's valid JSON +if ! python3 -m json.tool "$BACKUP_FILE" > /dev/null 2>&1; then + echo "Error: Backup file contains invalid JSON" + exit 1 +fi + +# Count records +RECORD_COUNT=$(python3 -c "import json; print(len(json.load(open('$BACKUP_FILE'))))") +FILE_SIZE=$(du -h "$BACKUP_FILE" | cut -f1) + +echo "✓ Backup complete" +echo "" +echo "File: $BACKUP_FILE" +echo "Size: $FILE_SIZE" +echo "Records: $RECORD_COUNT" +echo "" + +# Show breakdown by model +echo "Records by model:" +python3 -c " +import json +from collections import Counter + +data = json.load(open('$BACKUP_FILE')) +models = Counter(item['model'] for item in data) + +for model, count in sorted(models.items()): + print(f' {model}: {count}') +" + +echo "" +echo "To restore this backup on a new system:" +echo " 1. Start Hamilton UI: ./run.sh" +echo " 2. Run: ./restore_hamilton_data.sh $BACKUP_FILE" diff --git a/ui/migrate_postgres.sh b/ui/migrate_postgres.sh new file mode 100755 index 00000000..7bfa734f --- /dev/null +++ b/ui/migrate_postgres.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# migrate_postgres.sh - Migrate Hamilton UI from PostgreSQL 12 to 16 + +set -e + +BACKUP_FILE="hamilton_backup_$(date +%Y%m%d_%H%M%S).sql" + +echo "Hamilton UI PostgreSQL 12 → 16 Migration" +echo "========================================" +echo "" +echo "This script will:" +echo " 1. Backup your PostgreSQL 12 data" +echo " 2. Stop containers and remove old data volume" +echo " 3. Start PostgreSQL 16 containers" +echo " 4. Restore your data to PostgreSQL 16" +echo "" +read -p "Continue? (yes/no): " CONFIRM + +if [ "$CONFIRM" != "yes" ]; then + echo "Aborted." + exit 0 +fi + +# Check if docker compose or docker-compose is available +if command -v docker-compose &> /dev/null; then + DOCKER_COMPOSE="docker-compose" +elif docker compose version &> /dev/null; then + DOCKER_COMPOSE="docker compose" +else + echo "Error: Neither 'docker compose' nor 'docker-compose' command found" + exit 1 +fi + +# Check if old containers are running +if ! $DOCKER_COMPOSE ps | grep -q "db"; then + echo "Error: Hamilton UI containers not running. Start them first with ./run.sh" + exit 1 +fi + +# Check PostgreSQL version +PG_VERSION=$($DOCKER_COMPOSE exec -T db psql -U hamilton -d hamilton -c "SHOW server_version;" -t | tr -d ' ') +if [[ $PG_VERSION == 16* ]]; then + echo "You are already running PostgreSQL 16. No migration needed." + exit 0 +fi + +echo "" +echo "Detected PostgreSQL version: $PG_VERSION" +echo "" + +# Backup +echo "Step 1: Backing up PostgreSQL data..." +$DOCKER_COMPOSE exec -T db pg_dump -U hamilton hamilton > "$BACKUP_FILE" +echo "✓ Backup saved to: $BACKUP_FILE ($(du -h "$BACKUP_FILE" | cut -f1))" +echo "" + +# Stop and remove +echo "Step 2: Stopping containers and removing old data..." +$DOCKER_COMPOSE down +docker volume rm ui_postgres_data || true +echo "✓ Old data removed" +echo "" + +# Start new +echo "Step 3: Starting PostgreSQL 16 containers..." +echo " This may take a few minutes on first run..." +./run.sh --build > /tmp/hamilton_build.log 2>&1 & +BUILD_PID=$! + +# Wait for build to complete +wait $BUILD_PID || true + +# Wait for database to be ready +echo " Waiting for database to initialize..." +sleep 20 + +MAX_RETRIES=30 +RETRY_COUNT=0 +until $DOCKER_COMPOSE exec -T db pg_isready -U hamilton > /dev/null 2>&1; do + RETRY_COUNT=$((RETRY_COUNT + 1)) + if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then + echo "Error: Database failed to start after $MAX_RETRIES attempts" + echo "Check logs: $DOCKER_COMPOSE logs db" + exit 1 + fi + echo " Still waiting... ($RETRY_COUNT/$MAX_RETRIES)" + sleep 2 +done + +echo "✓ PostgreSQL 16 ready" +echo "" + +# Restore +echo "Step 4: Restoring data to PostgreSQL 16..." +echo " (Ignoring 'already exists' errors - this is normal)" +$DOCKER_COMPOSE exec -T db psql -U hamilton hamilton < "$BACKUP_FILE" 2>&1 | \ + grep -v "ERROR:.*already exists" | \ + grep -v "^$" || true +echo "✓ Data restored" +echo "" + +# Fix permissions +echo "Step 5: Fixing permissions..." +$DOCKER_COMPOSE exec -T db psql -U hamilton hamilton -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO hamilton;" > /dev/null +$DOCKER_COMPOSE exec -T db psql -U hamilton hamilton -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO hamilton;" > /dev/null +echo "✓ Permissions fixed" +echo "" + +# Verify +echo "Step 6: Verifying migration..." +TABLE_COUNT=$($DOCKER_COMPOSE exec -T db psql -U hamilton hamilton -c "\dt" | grep -c "public" || echo "0") +echo "✓ Found $TABLE_COUNT tables" +echo "" + +echo "========================================" +echo "Migration complete!" +echo "========================================" +echo "" +echo "Next steps:" +echo " 1. Navigate to http://localhost:8242" +echo " 2. Verify your projects and runs are present" +echo " 3. If everything looks good, you can delete the backup:" +echo " rm $BACKUP_FILE" +echo "" +echo "If you encounter issues:" +echo " - Check logs: $DOCKER_COMPOSE logs" +echo " - Restore from backup: See UPGRADE.md" +echo " - Get help: https://github.com/apache/hamilton/issues" diff --git a/ui/migrate_postgres_simple.sh b/ui/migrate_postgres_simple.sh new file mode 100755 index 00000000..4acd60c5 --- /dev/null +++ b/ui/migrate_postgres_simple.sh @@ -0,0 +1,246 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# migrate_postgres_simple.sh - Migrate Hamilton UI from PostgreSQL 12 to 16 +# Uses Django's dumpdata/loaddata for safe, validated migration + +set -euo pipefail + +BACKUP_FILE="hamilton_migration_$(date +%Y%m%d_%H%M%S).json" + +echo "Hamilton UI PostgreSQL 12 → 16 Migration" +echo "=========================================" +echo "" +echo "This script will:" +echo " 1. Export your Hamilton data using Django" +echo " 2. Stop containers and remove PostgreSQL 12 volume" +echo " 3. Start PostgreSQL 16 containers" +echo " 4. Restore your Hamilton data" +echo "" + +# Check if docker compose or docker-compose is available +if command -v docker-compose &> /dev/null; then + DOCKER_COMPOSE="docker-compose" +elif docker compose version &> /dev/null 2>&1; then + DOCKER_COMPOSE="docker compose" +else + echo "Error: Neither 'docker compose' nor 'docker-compose' found" + exit 1 +fi + +# Check if containers are running +if ! $DOCKER_COMPOSE ps --services --filter "status=running" | grep -q "backend"; then + echo "Error: Hamilton UI containers not running" + echo "Start them first: ./run.sh" + exit 1 +fi + +# Check PostgreSQL version +echo "Checking current PostgreSQL version..." +PG_VERSION=$($DOCKER_COMPOSE exec -T db psql -U hamilton -d hamilton -At -c "SHOW server_version;" | cut -d. -f1) + +if [ "$PG_VERSION" -ge 16 ]; then + echo "Already on PostgreSQL $PG_VERSION. No migration needed." + exit 0 +fi + +if [ "$PG_VERSION" -lt 12 ]; then + echo "Error: PostgreSQL $PG_VERSION detected" + echo "This script handles PostgreSQL 12+ → 16" + echo "Manual upgrade required for older versions" + exit 1 +fi + +echo "Current version: PostgreSQL $PG_VERSION" +echo "" + +read -p "Continue with migration? (yes/no): " CONFIRM +if [ "$CONFIRM" != "yes" ]; then + echo "Aborted." + exit 0 +fi + +echo "" +echo "================================================================" +echo "Step 1: Exporting Hamilton data" +echo "================================================================" +echo "" + +# Use the backup script +if ! ./backup_hamilton_data.sh "$BACKUP_FILE"; then + echo "Error: Data export failed" + exit 1 +fi + +# Verify backup was created and has content +if [ ! -s "$BACKUP_FILE" ]; then + echo "Error: Backup file is empty or missing" + exit 1 +fi + +RECORD_COUNT=$(python3 -c "import json; print(len(json.load(open('$BACKUP_FILE'))))") +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Warning: Backup contains 0 records" + echo "This is OK if you have no data yet, but unexpected if you've been using Hamilton UI" + read -p "Continue anyway? (yes/no): " CONTINUE + if [ "$CONTINUE" != "yes" ]; then + echo "Aborted. Backup saved at: $BACKUP_FILE" + exit 0 + fi +fi + +echo "" +echo "================================================================" +echo "Step 2: Removing PostgreSQL 12 data" +echo "================================================================" +echo "" +echo "⚠️ WARNING: About to delete PostgreSQL 12 data volume" +echo " Backup: $BACKUP_FILE ($RECORD_COUNT records)" +echo "" +read -p "Type 'DELETE' to confirm: " CONFIRM_DELETE + +if [ "$CONFIRM_DELETE" != "DELETE" ]; then + echo "Aborted. Your backup is saved at: $BACKUP_FILE" + exit 0 +fi + +echo "" +echo "Stopping containers..." +$DOCKER_COMPOSE down + +echo "Removing PostgreSQL 12 volume..." +if docker volume rm ui_postgres_data; then + echo "✓ Volume removed" +else + echo "Error: Could not remove volume" + echo "It may not exist or be in use by another container" + docker volume ls | grep postgres + exit 1 +fi + +echo "" +echo "================================================================" +echo "Step 3: Starting PostgreSQL 16" +echo "================================================================" +echo "" + +if [ ! -f "./run.sh" ]; then + echo "Error: ./run.sh not found" + echo "Run this script from the ui/ directory" + exit 1 +fi + +echo "Building and starting containers..." +echo "(This may take a few minutes on first build)" +echo "" + +if ! ./run.sh --build; then + echo "" + echo "Error: Failed to start containers" + echo "Your backup is safe at: $BACKUP_FILE" + echo "" + echo "To retry manually:" + echo " 1. Fix any Docker issues" + echo " 2. Run: ./run.sh --build" + echo " 3. Run: ./restore_hamilton_data.sh $BACKUP_FILE" + exit 1 +fi + +# Wait for backend to be ready +echo "" +echo "Waiting for backend to be ready..." +MAX_RETRIES=30 +RETRY_COUNT=0 + +until $DOCKER_COMPOSE exec -T backend python manage.py migrate --check > /dev/null 2>&1; do + RETRY_COUNT=$((RETRY_COUNT + 1)) + if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then + echo "Error: Backend not ready after $MAX_RETRIES attempts" + echo "Your backup is safe at: $BACKUP_FILE" + echo "" + echo "To restore manually:" + echo " 1. Wait for containers to be healthy: $DOCKER_COMPOSE ps" + echo " 2. Run: ./restore_hamilton_data.sh $BACKUP_FILE" + exit 1 + fi + echo " Waiting... (attempt $RETRY_COUNT/$MAX_RETRIES)" + sleep 2 +done + +echo "✓ Backend ready" + +# Check new PostgreSQL version +NEW_PG_VERSION=$($DOCKER_COMPOSE exec -T db psql -U hamilton -d hamilton -At -c "SHOW server_version;" | cut -d. -f1) +echo "✓ PostgreSQL $NEW_PG_VERSION running" + +echo "" +echo "================================================================" +echo "Step 4: Restoring Hamilton data" +echo "================================================================" +echo "" + +# Copy backup into backend container so loaddata can access it +echo "Copying backup into container..." +$DOCKER_COMPOSE cp "$BACKUP_FILE" backend:/tmp/restore.json + +# Use the restore script +if ! $DOCKER_COMPOSE exec -T backend bash -c "cd /code && python manage.py loaddata /tmp/restore.json"; then + echo "" + echo "Error: Data restore failed" + echo "Your backup is safe at: $BACKUP_FILE" + echo "" + echo "To retry manually:" + echo " ./restore_hamilton_data.sh $BACKUP_FILE" + exit 1 +fi + +echo "✓ Data restored" + +# Verify +echo "" +echo "Verifying migration..." +POST_COUNTS=$($DOCKER_COMPOSE exec -T backend python manage.py shell <<'PYTHON' +from trackingserver_base.models import Project +from trackingserver_run_tracking.models import DAGRun + +print(f"{Project.objects.count()},{DAGRun.objects.count()}") +PYTHON +) + +IFS=',' read -r PROJECTS RUNS <<< "$POST_COUNTS" + +echo " Projects: $PROJECTS" +echo " Runs: $RUNS" + +echo "" +echo "================================================================" +echo "✅ Migration Complete!" +echo "================================================================" +echo "" +echo "Your Hamilton UI has been upgraded from PostgreSQL $PG_VERSION to $NEW_PG_VERSION" +echo "" +echo "Next steps:" +echo " 1. Navigate to http://localhost:8242" +echo " 2. Verify your projects and runs appear" +echo " 3. If everything looks good, delete the backup:" +echo " rm $BACKUP_FILE" +echo "" +echo "If you encounter issues:" +echo " - Backup file: $BACKUP_FILE" +echo " - Check logs: $DOCKER_COMPOSE logs" +echo " - Get help: https://github.com/apache/hamilton/issues" diff --git a/ui/restore_hamilton_data.sh b/ui/restore_hamilton_data.sh new file mode 100755 index 00000000..dd045c4a --- /dev/null +++ b/ui/restore_hamilton_data.sh @@ -0,0 +1,195 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# restore_hamilton_data.sh - Import Hamilton UI data using Django's loaddata + +set -euo pipefail + +BACKUP_FILE="${1:-}" + +if [ -z "$BACKUP_FILE" ]; then + echo "Usage: $0 <backup_file.json>" + echo "" + echo "Available backups:" + ls -lh hamilton_data_*.json 2>/dev/null || echo " (none found)" + exit 1 +fi + +if [ ! -f "$BACKUP_FILE" ]; then + echo "Error: Backup file not found: $BACKUP_FILE" + exit 1 +fi + +echo "Hamilton UI Data Restore" +echo "========================" +echo "" +echo "Backup file: $BACKUP_FILE" +echo "Size: $(du -h "$BACKUP_FILE" | cut -f1)" +echo "" + +# Validate JSON +echo "Validating backup file..." +if ! python3 -m json.tool "$BACKUP_FILE" > /dev/null 2>&1; then + echo "Error: Backup file contains invalid JSON" + exit 1 +fi + +RECORD_COUNT=$(python3 -c "import json; print(len(json.load(open('$BACKUP_FILE'))))") +echo "✓ Found $RECORD_COUNT records" +echo "" + +# Check if docker compose or docker-compose is available +if command -v docker-compose &> /dev/null; then + DOCKER_COMPOSE="docker-compose" +elif docker compose version &> /dev/null 2>&1; then + DOCKER_COMPOSE="docker compose" +else + echo "Error: Neither 'docker compose' nor 'docker-compose' found" + exit 1 +fi + +# Check if containers are running +if ! $DOCKER_COMPOSE ps --services --filter "status=running" | grep -q "backend"; then + echo "Error: Hamilton UI backend container is not running" + echo "Start containers first: ./run.sh" + exit 1 +fi + +# Check if database is ready +echo "Checking database connection..." +if ! $DOCKER_COMPOSE exec -T backend python manage.py migrate --check > /dev/null 2>&1; then + echo "Error: Database is not ready or migrations need to run" + echo "This is normal for a fresh installation." + echo "" + echo "Running migrations..." + if ! $DOCKER_COMPOSE exec -T backend python manage.py migrate; then + echo "Error: Migrations failed" + exit 1 + fi + echo "✓ Migrations complete" +fi +echo "✓ Database ready" +echo "" + +# Check if there's existing data +EXISTING_PROJECTS=$($DOCKER_COMPOSE exec -T backend python manage.py shell -c "from trackingserver_base.models import *; print(Project.objects.count())" 2>/dev/null || echo "0") + +if [ "$EXISTING_PROJECTS" != "0" ]; then + echo "⚠️ WARNING: Database already contains $EXISTING_PROJECTS projects" + echo "" + echo "Restoring will ADD to existing data (not replace it)." + echo "This may cause conflicts if the same data exists." + echo "" + read -p "Continue with restore? (yes/no): " CONFIRM + if [ "$CONFIRM" != "yes" ]; then + echo "Aborted." + exit 0 + fi + echo "" +fi + +# Create a pre-restore snapshot of record counts +echo "Creating pre-restore snapshot..." +PRE_RESTORE_COUNTS=$($DOCKER_COMPOSE exec -T backend python manage.py shell <<'PYTHON' +from trackingserver_base.models import Project +from trackingserver_projects.models import ProjectVersion +from trackingserver_run_tracking.models import DAGRun, DAGTemplate + +print(f"{Project.objects.count()},{ProjectVersion.objects.count()},{DAGRun.objects.count()},{DAGTemplate.objects.count()}") +PYTHON +) + +IFS=',' read -r PRE_PROJECTS PRE_VERSIONS PRE_RUNS PRE_TEMPLATES <<< "$PRE_RESTORE_COUNTS" + +echo " Projects: $PRE_PROJECTS" +echo " Versions: $PRE_VERSIONS" +echo " Runs: $PRE_RUNS" +echo " Templates: $PRE_TEMPLATES" +echo "" + +# Restore data using Django's loaddata +echo "Restoring data..." +RESTORE_LOG="/tmp/hamilton_restore_$(date +%Y%m%d_%H%M%S).log" + +if ! $DOCKER_COMPOSE exec -T backend python manage.py loaddata "$BACKUP_FILE" > "$RESTORE_LOG" 2>&1; then + echo "Error: Restore failed" + echo "" + echo "Log file: $RESTORE_LOG" + echo "" + echo "Last 20 lines of error:" + tail -20 "$RESTORE_LOG" + exit 1 +fi + +echo "✓ Data loaded" +echo "" + +# Verify restore by comparing counts +echo "Verifying restore..." +POST_RESTORE_COUNTS=$($DOCKER_COMPOSE exec -T backend python manage.py shell <<'PYTHON' +from trackingserver_base.models import Project +from trackingserver_projects.models import ProjectVersion +from trackingserver_run_tracking.models import DAGRun, DAGTemplate + +print(f"{Project.objects.count()},{ProjectVersion.objects.count()},{DAGRun.objects.count()},{DAGTemplate.objects.count()}") +PYTHON +) + +IFS=',' read -r POST_PROJECTS POST_VERSIONS POST_RUNS POST_TEMPLATES <<< "$POST_RESTORE_COUNTS" + +ADDED_PROJECTS=$((POST_PROJECTS - PRE_PROJECTS)) +ADDED_VERSIONS=$((POST_VERSIONS - PRE_VERSIONS)) +ADDED_RUNS=$((POST_RUNS - PRE_RUNS)) +ADDED_TEMPLATES=$((POST_TEMPLATES - PRE_TEMPLATES)) + +echo "Records added:" +echo " Projects: $ADDED_PROJECTS (total: $POST_PROJECTS)" +echo " Versions: $ADDED_VERSIONS (total: $POST_VERSIONS)" +echo " Runs: $ADDED_RUNS (total: $POST_RUNS)" +echo " Templates: $ADDED_TEMPLATES (total: $POST_TEMPLATES)" +echo "" + +# Sanity check - if we expected to add data but nothing changed +EXPECTED_RECORDS=$(python3 -c " +import json +data = json.load(open('$BACKUP_FILE')) +models = [item['model'] for item in data] +print(len([m for m in models if 'project' in m or 'dagrun' in m or 'dagtemplate' in m])) +") + +if [ "$EXPECTED_RECORDS" -gt 0 ] && [ "$ADDED_PROJECTS" -eq 0 ] && [ "$ADDED_RUNS" -eq 0 ]; then + echo "⚠️ Warning: Backup contained records but none were added" + echo " This could mean:" + echo " - Records already exist (duplicates skipped)" + echo " - Backup is from a different schema version" + echo " - There was an error during restore" + echo "" + echo " Check the restore log: $RESTORE_LOG" + exit 1 +fi + +echo "✓ Restore complete!" +echo "" +echo "Next steps:" +echo " 1. Navigate to http://localhost:8242" +echo " 2. Verify your projects and runs appear" +echo " 3. Check that data looks correct" +echo "" +echo "If something looks wrong:" +echo " - Check logs: $DOCKER_COMPOSE logs backend" +echo " - Restore log: $RESTORE_LOG"
