Skip to content

feat: auto-downgrading revision #4678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 121 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
408e7fc
first version
korolenkowork May 1, 2025
dce47b0
fix
korolenkowork May 1, 2025
6127d86
Main logic completed
korolenkowork May 1, 2025
8cb9b81
minor
korolenkowork May 1, 2025
9cc98a8
Update configuration.mdx
korolenkowork May 1, 2025
2758d9f
lint fix
korolenkowork May 1, 2025
e1ee04b
Add test
korolenkowork May 2, 2025
51787ba
fix path
korolenkowork May 2, 2025
db174f9
fix
korolenkowork May 2, 2025
9b2f67f
migrations-e2e-prepare-for-coding
korolenkowork May 2, 2025
3fb2996
migrations-e2e
korolenkowork May 2, 2025
08db877
Done!
korolenkowork May 2, 2025
4f635f3
fix pull old version e2e
korolenkowork May 2, 2025
f140de4
fix
korolenkowork May 3, 2025
995fd83
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
1d1e462
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
5dd3bfa
fix
korolenkowork May 3, 2025
cb46278
Update test-pr-e2e.yml
korolenkowork May 3, 2025
bf1b653
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
f469bab
Merge branch 'main' into feature/auto-downgrade-revision
korolenkowork May 3, 2025
3e3975c
Check revision between runs
korolenkowork May 3, 2025
4671d62
Merge branch 'feature/auto-downgrade-revision' of https://github.com/…
korolenkowork May 3, 2025
c55103a
fix execute container name
korolenkowork May 3, 2025
83b748d
test
korolenkowork May 3, 2025
f3ee8e3
test
korolenkowork May 3, 2025
9036754
test
korolenkowork May 3, 2025
e185781
test
korolenkowork May 3, 2025
33fbe21
test
korolenkowork May 3, 2025
3fcdfcb
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
b2beede
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
e4dd78a
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
8fd2137
revert
korolenkowork May 3, 2025
9545c5a
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
87d28d3
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
a2a1260
fix e2e env
korolenkowork May 3, 2025
cf0d105
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
1d34408
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
ff4b2e9
I hate pipelines
korolenkowork May 3, 2025
303e63c
Done!
korolenkowork May 3, 2025
394fd03
Add dummy migrations to the second step
korolenkowork May 3, 2025
a3a2735
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
fd45db1
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
4c96bcc
Fix docker cp path
korolenkowork May 3, 2025
b3a1190
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
197c98a
Update run-migrations-e2e-tests.yml
korolenkowork May 3, 2025
a38facc
Merge branch 'main' into feature/auto-downgrade-revision
shahargl May 4, 2025
8702a2b
Update run-migrations-e2e-tests.yml
korolenkowork May 4, 2025
0e208ef
Merge branch 'feature/auto-downgrade-revision' of https://github.com/…
korolenkowork May 4, 2025
32f2cbe
fix workflow
korolenkowork May 4, 2025
9d7ea1f
Remove test alembic config
korolenkowork May 4, 2025
b05a727
fix
korolenkowork May 4, 2025
099d9ea
fix
korolenkowork May 4, 2025
755f143
fix
korolenkowork May 4, 2025
8af5a68
fix
korolenkowork May 6, 2025
f83c3f0
Merge branch 'main' into feature/auto-downgrade-revision
korolenkowork May 6, 2025
c29ef1b
fix?
korolenkowork May 6, 2025
753f899
fix?
korolenkowork May 6, 2025
1cdc291
change SECRET_MANAGER_DIRECTORY
korolenkowork May 6, 2025
9d9791e
Delete docker-compose-e2e-postgres.yml~
korolenkowork May 6, 2025
beb812e
Revert "change SECRET_MANAGER_DIRECTORY"
korolenkowork May 6, 2025
5136a03
fix
korolenkowork May 6, 2025
2b3693a
Revert "fix"
korolenkowork May 6, 2025
775322f
fix
korolenkowork May 6, 2025
ef08091
fix
korolenkowork May 9, 2025
f1072e8
fix?
korolenkowork May 9, 2025
fa83f0f
revert
korolenkowork May 9, 2025
c24254c
fix
korolenkowork May 9, 2025
f40ed3c
test
korolenkowork May 9, 2025
6b9d5a0
test
korolenkowork May 9, 2025
0be6341
try
korolenkowork May 9, 2025
5224b8c
test
korolenkowork May 10, 2025
0870a1b
fix
korolenkowork May 10, 2025
a8d1acc
Revert "test"
korolenkowork May 10, 2025
e1f4a30
Update docker-compose-e2e-postgres.yml
korolenkowork May 10, 2025
8ced224
fix
korolenkowork May 10, 2025
42a362a
Update docker-compose-e2e-postgres.yml
korolenkowork May 10, 2025
1211b91
Mock final step migrations
korolenkowork May 10, 2025
c1e648f
Update poetry.lock
korolenkowork May 10, 2025
99be2fa
Update run-migrations-e2e-tests.yml
korolenkowork May 10, 2025
5e1e409
fix
korolenkowork May 10, 2025
7a405b8
Update run-migrations-e2e-tests.yml
korolenkowork May 10, 2025
fea2ead
Update run-migrations-e2e-tests.yml
korolenkowork May 10, 2025
ea6746e
Update run-migrations-e2e-tests.yml
korolenkowork May 10, 2025
4fdb76e
try
korolenkowork May 10, 2025
d458eac
test
korolenkowork May 10, 2025
4c79efe
Update docker-compose-e2e-postgres.yml
korolenkowork May 15, 2025
1d2716c
minor changes in copy migrations logic
korolenkowork May 15, 2025
4adea39
Merge branch 'feature/auto-downgrade-revision' of https://github.com/…
korolenkowork May 15, 2025
e645a5e
fix
korolenkowork May 15, 2025
a1ffa55
test
korolenkowork May 15, 2025
f4d8ea8
fix
korolenkowork May 15, 2025
84ce798
Update docker-compose-e2e-postgres.yml
korolenkowork May 15, 2025
36051a0
Update docker-compose-e2e-postgres.yml
korolenkowork May 15, 2025
d41c554
Update docker-compose-e2e-postgres.yml
korolenkowork May 15, 2025
68416f8
Update docker-compose-e2e-postgres.yml
korolenkowork May 15, 2025
4a6039e
stop instead of down
korolenkowork May 15, 2025
9eeaddd
Update docker-compose-e2e-postgres.yml
korolenkowork May 15, 2025
24200a7
final
korolenkowork May 15, 2025
e7ea523
final
korolenkowork May 15, 2025
70325a0
fix
korolenkowork May 15, 2025
1725ae2
fix test script path
korolenkowork May 15, 2025
166abba
fix
korolenkowork May 15, 2025
70808cc
fix
korolenkowork May 15, 2025
89868ba
Ahhhh
korolenkowork May 15, 2025
4faabfb
Merge branch 'main' into feature/auto-downgrade-revision
shahargl May 19, 2025
e1e1dad
Post review fixes
korolenkowork May 19, 2025
40a8340
Merge branch 'feature/auto-downgrade-revision' of https://github.com/…
korolenkowork May 19, 2025
ad148d8
fix
korolenkowork May 19, 2025
7afb934
Update docker-compose-e2e-postgres.yml
korolenkowork May 19, 2025
3f1f88a
Merge branch 'main' into feature/auto-downgrade-revision
shahargl May 19, 2025
49df674
fix
korolenkowork May 19, 2025
077ba69
Merge branch 'feature/auto-downgrade-revision' of https://github.com/…
korolenkowork May 19, 2025
ed91ff4
Add migration path to dockerfiles
korolenkowork May 19, 2025
f1328ef
Merge branch 'main' into feature/auto-downgrade-revision
korolenkowork May 22, 2025
bc5ebce
Merge branch 'main' into feature/auto-downgrade-revision
shahargl May 25, 2025
02ca8bf
Merge branch 'main' into feature/auto-downgrade-revision
shahargl May 26, 2025
db76ae7
Merge branch 'main' into feature/auto-downgrade-revision
korolenkowork May 29, 2025
c05e35e
Make separate docker compose for migrations e2e
korolenkowork May 29, 2025
aea9b81
Merge branch 'main' into feature/auto-downgrade-revision
korolenkowork Jun 9, 2025
356ce18
Merge branch 'main' into feature/auto-downgrade-revision
korolenkowork Jun 14, 2025
13fe761
Update poetry.lock
korolenkowork Jun 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
499 changes: 499 additions & 0 deletions .github/workflows/run-migrations-e2e-tests.yml

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions .github/workflows/test-pr-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ jobs:
is-fork: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
backend-image-name: ${{ needs.build-backend.outputs.image_name }}
frontend-image-name: ${{ needs.build-frontend.outputs.image_name }}

run-postgresql-without-redis:
needs: [build-frontend, build-backend, prepare-test-environment]
uses: ./.github/workflows/run-e2e-tests.yml
Expand All @@ -311,7 +311,7 @@ jobs:
is-fork: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
backend-image-name: ${{ needs.build-backend.outputs.image_name }}
frontend-image-name: ${{ needs.build-frontend.outputs.image_name }}

run-sqlite-without-redis:
needs: [build-frontend, build-backend, prepare-test-environment]
uses: ./.github/workflows/run-e2e-tests.yml
Expand All @@ -321,4 +321,15 @@ jobs:
python-version: 3.11
is-fork: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
backend-image-name: ${{ needs.build-backend.outputs.image_name }}
frontend-image-name: ${{ needs.build-frontend.outputs.image_name }}

run-migrations-tests:
needs: [build-frontend, build-backend, prepare-test-environment]
uses: ./.github/workflows/run-migrations-e2e-tests.yml
with:
db-type: postgres-migrations
redis_enabled: false
python-version: 3.11
is-fork: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
backend-image-name: ${{ needs.build-backend.outputs.image_name }}
frontend-image-name: ${{ needs.build-frontend.outputs.image_name }}
1 change: 1 addition & 0 deletions docker-compose-with-arq.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ services:
- REDIS_PORT=6379
volumes:
- ./state:/state
- ./tmp/keep/migrations:/tmp/keep/migrations
depends_on:
- keep-arq-redis

Expand Down
1 change: 1 addition & 0 deletions docker-compose-with-auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ services:
- KEEP_DEFAULT_PASSWORD=keep
volumes:
- ./state:/state
- ./tmp/keep/migrations:/tmp/keep/migrations

keep-websocket-server:
extends:
Expand Down
1 change: 1 addition & 0 deletions docker-compose-with-otel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ services:
volumes:
- .:/app
- ./state:/state
- ./tmp/keep/migrations:/tmp/keep/migrations

keep-websocket-server:
extends:
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ services:
- PORT=8080
- SECRET_MANAGER_TYPE=FILE
- SECRET_MANAGER_DIRECTORY=/state
- MIGRATIONS_PATH=/tmp/keep/migrations
- DATABASE_CONNECTION_STRING=sqlite:////state/db.sqlite3?check_same_thread=False
- OPENAI_API_KEY=$OPENAI_API_KEY
- ALLOW_DB_DOWNGRADE=false
- PUSHER_APP_ID=1
- PUSHER_APP_KEY=keepappkey
- PUSHER_APP_SECRET=keepappsecret
Expand Down
1 change: 1 addition & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ services:
volumes:
- .:/app
- ./state:/state
- ./tmp/keep/migrations:/tmp/keep/migrations

keep-websocket-server:
extends:
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ services:
- KEEP_METRICS=true
volumes:
- ./state:/state
- ./tmp/keep/migrations:/tmp/keep/migrations

keep-websocket-server:
extends:
Expand Down
2 changes: 2 additions & 0 deletions docs/deployment/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Keep is highly configurable through environment variables. This allows you to cu
| **KEEP_STORE_RAW_ALERTS** | Enables storing of raw alerts | No | "false" | "true" or "false" |
| **TENANT_CONFIGURATION_RELOAD_TIME** | Time in minutes to reload tenant configurations | No | 5 | Positive integer |
| **KEEP_LIVE_DEMO_MODE** | Keep will simulate incoming alerts and other activity | No | "false" | "true" or "false" |
| **MIGRATIONS_PATH** | Path to migrations directory | No | "/tmp/keep/migrations" | Valid os path |

### Logging and Environment

Expand Down Expand Up @@ -63,6 +64,7 @@ Keep is highly configurable through environment variables. This allows you to cu
| **DB_SERVICE_ACCOUNT** | Service account for database impersonation | No | None | Valid service account email |
| **DB_IP_TYPE** | Specifies the Cloud SQL IP type | No | "public" | "public", "private" or "psc" |
| **SKIP_DB_CREATION** | Skips database creation and migrations | No | "false" | "true" or "false" |
| **ALLOW_DB_DOWNGRADE** | Enables downgrading database schema | No | "false" | "true" or "false" |

### Resource Provisioning

Expand Down
112 changes: 107 additions & 5 deletions keep/api/core/db_on_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
import hashlib
import logging
import os
import shutil

import alembic.command
import alembic.config
from alembic.runtime.migration import MigrationContext
from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select

Expand Down Expand Up @@ -167,23 +169,123 @@ def try_create_single_tenant(tenant_id: str, create_default_user=True) -> None:
logger.exception("Failed to create single tenant")
pass

def get_current_revision():
"""Get current app revision"""
with engine.connect() as connection:
context = MigrationContext.configure(connection)
return context.get_current_revision()

def migrate_db():
def copy_migrations(app_migrations_path, local_migrations_path):
"""Copy migrations to a local backup folder for safe downgrade purposes."""

source_versions_path = os.path.join(app_migrations_path, "versions")

# Ensure destination exists
try:
os.makedirs(local_migrations_path, exist_ok=True)
except Exception as e:
logger.error(f"Failed to create local migrations folder with error: {e}")


# Clear previous versioned migrations to ensure only migrations relevant to the current version are present
for filename in os.listdir(local_migrations_path):
file_path = os.path.join(local_migrations_path, filename)
if os.path.isfile(file_path) or os.path.islink(file_path):
os.remove(file_path)

# Alembic needs the full migration history to safely perform a downgrade to earlier versions
# Copy new migrations
for item in os.listdir(source_versions_path):
src = os.path.join(source_versions_path, item)
dst = os.path.join(local_migrations_path, item)
if os.path.isdir(src):
shutil.copytree(src, dst, dirs_exist_ok=True)
else:
shutil.copy(src, dst)

def downgrade_db(config, expected_revision, local_migrations_path, app_migrations_path):
"""
Downgrade the DB to the previous revision, using local backup migrations temporarily.
Restores original migrations after downgrade.
"""
source_versions_path = os.path.join(app_migrations_path, "versions")
source_versions_path_copy = os.path.join(app_migrations_path, "versions_copy")

try:
logger.info("Backing up original migrations...")
if os.path.exists(source_versions_path_copy):
shutil.rmtree(source_versions_path_copy)
shutil.move(source_versions_path, source_versions_path_copy)
logger.info("Original migrations backed up.")

logger.info("Restoring migrations from local backup...")
shutil.copytree(local_migrations_path, source_versions_path)
logger.info("Migrations restored from local.")

logger.info("Downgrading the database...")
alembic.command.downgrade(config, expected_revision)
logger.info("Database successfully downgraded.")

except Exception as e:
logger.error(f"Error occurred during downgrade process: {e}")
finally:
logger.info("Restoring original migrations...")
try:
if os.path.exists(source_versions_path):
shutil.rmtree(source_versions_path)
if os.path.exists(source_versions_path_copy):
shutil.move(source_versions_path_copy, source_versions_path)
logger.info("Original migrations restored!")
else:
logger.warning("Backup not found!!! Original migrations not restored!!!")
except Exception as restore_error:
logger.error(f"Failed to restore original migrations: {restore_error}")

def migrate_db(config_path: str = None, app_migrations_path: str = None):
"""
Run migrations to make sure the DB is up-to-date.
"""
if os.environ.get("SKIP_DB_CREATION", "false") == "true":
logger.info("Skipping running migrations...")
return None

logger.info("Running migrations...")
config_path = os.path.dirname(os.path.abspath(__file__)) + "/../../" + "alembic.ini"
config_path = config_path or os.path.dirname(os.path.abspath(__file__)) + "/../../" + "alembic.ini"
config = alembic.config.Config(file_=config_path)
# Re-defined because alembic.ini uses relative paths which doesn't work
# when running the app as a pyhton pakage (could happen form any path)

# This path will be used to save migrations locally for safe downgrade purposes
local_migrations_path = os.environ.get("MIGRATIONS_PATH", "/tmp/keep/migrations")
app_migrations_path = app_migrations_path or os.path.dirname(os.path.abspath(__file__)) + "/../models/db/migrations"
config.set_main_option(
"script_location",
os.path.dirname(os.path.abspath(__file__)) + "/../models/db/migrations",
app_migrations_path,
)
alembic.command.upgrade(config, "head")
alembic_script = alembic.script.ScriptDirectory.from_config(config)

current_revision = get_current_revision()
expected_revision = alembic_script.get_current_head()

# If the current revision is the same as the expected revision, we don't need to run migrations
if current_revision and expected_revision and current_revision == expected_revision:
logger.info("Database schema is up-to-date!")
return None

logger.warning(f"Database schema ({current_revision}) doesn't match application version ({expected_revision})")
logger.info("Running migrations...")
try:
alembic.command.upgrade(config, "head")
except Exception as e:
logger.error(f"{e} it's seems like Keep was rolled back to a previous version")

if not os.getenv("ALLOW_DB_DOWNGRADE", "false") == "true":
logger.error(f"ALLOW_DB_DOWNGRADE is not set to true, but the database schema ({current_revision}) doesn't match application version ({expected_revision})")
raise RuntimeError("Database downgrade is not allowed")

logger.info("Downgrading database schema...")
downgrade_db(config, expected_revision, local_migrations_path, app_migrations_path)

# Copy migrations to local folder for safe downgrade purposes
copy_migrations(app_migrations_path, local_migrations_path)

logger.info("Finished migrations")
14 changes: 7 additions & 7 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ psycopg-binary = "^3.2.3"
psycopg = "^3.2.3"
prometheus-client = "^0.21.1"
psycopg2-binary = "^2.9.10"
black = "^24.3.0"

prometheus-fastapi-instrumentator = "^7.0.0"
slowapi = "^0.1.9"
Expand All @@ -100,7 +101,6 @@ awscli = "^1.40.8"
pre-commit = "^3.0.4"
pre-commit-hooks = "^4.4.0"
yamllint = "^1.29.0"
black = "^24.3.0"
isort = "^5.12.0"
autopep8 = "^2.0.1"
flake8 = "^6.0.0"
Expand Down
6 changes: 4 additions & 2 deletions tests/e2e_tests/docker-compose-e2e-mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,23 @@ services:
- API_URL=http://keep-backend:8080
- POSTHOG_DISABLED=true
- SENTRY_DISABLED=true
- ALLOW_DB_DOWNGRADE=true

# Backend Services
keep-backend:
# to be replaced in github actions
image: "%KEEPBACKEND_IMAGE%"
ports:
- "8080:8080"
environment:
- AUTH_TYPE=NO_AUTH
- DATABASE_CONNECTION_STRING=mysql+pymysql://root:keep@keep-database:3306/keep
- POSTHOG_DISABLED=true
- SECRET_MANAGER_DIRECTORY=/app
- MIGRATIONS_PATH=/tmp/migrations
- SQLALCHEMY_WARN_20=1
- REDIS=${REDIS:-false}
- REDIS_HOST=${REDIS_HOST:-localhost}
ports:
- "8080:8080"
depends_on:
keep-database:
condition: service_healthy
Expand Down
Loading
Loading