From 457836142b83b112ffbddb0b679b2187194b5991 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Wed, 30 Aug 2023 17:09:46 +1000 Subject: [PATCH] Add migrations function --- .github/workflows/pipeline.yml | 10 ++++++---- backend/app/alembic/env.py | 10 +++------- backend/app/db/session.py | 15 ++++++++++----- backend/app/migrate_handler.py | 8 ++++++++ backend/template.yaml | 30 +++++++++++++++++++++++++++++- 5 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 backend/app/migrate_handler.py diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index e57e7dc..eebc3ff 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: {} push: branches: - - 'terraform' + - "terraform" concurrency: group: ${{ github.ref }} @@ -61,15 +61,14 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - cache: 'poetry' + cache: "poetry" - run: poetry install - name: Poetry export run: poetry export -f requirements.txt --without-hashes > requirements.txt - - run: > - sam build --use-container + - run: sam build --use-container - name: sam deploy # Prevent prompts and failure when the stack is unchanged @@ -79,3 +78,6 @@ jobs: --no-fail-on-empty-changeset --stack-name fastapi-backend-lambda --parameter-overrides "LambdaSecurityGroupId=${{ vars.LAMBDA_SG_ID }} LambdaSubnet1Id=${{ vars.LAMBDA_SUBNET_1_ID }} LambdaSubnet2Id=${{ vars.LAMBDA_SUBNET_2_ID }} SecretKeyArn=${{ vars.SECRET_ARN }} DBSecretArn=${{ vars.DB_SECRET_ARN }} DBEndpoint=${{ vars.DB_ENDPOINT }}" + + - name: Run migrations + run: sam remote invoke FastApiMigrationsFunction --stack-name fastapi-backend-lambda diff --git a/backend/app/alembic/env.py b/backend/app/alembic/env.py index 7d70c32..6f7f6a1 100644 --- a/backend/app/alembic/env.py +++ b/backend/app/alembic/env.py @@ -6,7 +6,7 @@ from sqlalchemy import pool from app.core import config -from app.db.session import Base +from app.db.session import Base, get_db_url # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -28,10 +28,6 @@ # ... etc. -def get_url(): - return config.DATABASE_URL - - def run_migrations_offline(): """Run migrations in 'offline' mode. This configures the context with just a URL @@ -42,7 +38,7 @@ def run_migrations_offline(): script output. """ # url = config.get_main_option("sqlalchemy.url") - url = get_url() + url = get_db_url() context.configure( url=url, target_metadata=target_metadata, @@ -61,7 +57,7 @@ def run_migrations_online(): """ configuration = alembic_config.get_section( alembic_config.config_ini_section) - configuration["sqlalchemy.url"] = get_url() + configuration["sqlalchemy.url"] = get_db_url() connectable = engine_from_config( configuration, prefix="sqlalchemy.", diff --git a/backend/app/db/session.py b/backend/app/db/session.py index 5d920bd..091ee94 100644 --- a/backend/app/db/session.py +++ b/backend/app/db/session.py @@ -8,17 +8,22 @@ _session_local: None | Session = None +def get_db_url(): + db_secret_name = os.environ.get("DB_SECRET_ARN") + db_secret = load_secret(db_secret_name) + db_endpoint = os.environ.get("DB_ENDPOINT") + db_name = os.environ.get("DB_NAME") + db_url = f"postgresql://{db_secret['username']}:{db_secret['password']}@{db_endpoint}/{db_name}" + return db_url + + def get_session_local(): global _session_local if _session_local is not None: return _session_local() - db_secret_name = os.environ.get("DB_SECRET_ARN") - db_secret = load_secret(db_secret_name) - db_endpoint = os.environ.get("DB_ENDPOINT") - db_name = os.environ.get("DB_NAME") - db_url = f"postgresql://{db_secret['username']}:{db_secret['password']}@{db_endpoint}/{db_name}" + db_url = get_db_url() engine = create_engine(db_url) diff --git a/backend/app/migrate_handler.py b/backend/app/migrate_handler.py new file mode 100644 index 0000000..56fe067 --- /dev/null +++ b/backend/app/migrate_handler.py @@ -0,0 +1,8 @@ +from alembic.config import Config +from alembic import command + +alembic_cfg = Config("./alembic.ini") + + +def handler(event, context): + command.upgrade(alembic_cfg, "head") diff --git a/backend/template.yaml b/backend/template.yaml index f12eec6..280eb6f 100644 --- a/backend/template.yaml +++ b/backend/template.yaml @@ -52,7 +52,7 @@ Resources: PackageType: Zip Handler: app.lambda_handler.handler Runtime: python3.10 - Timeout: 300 + Timeout: 300 # 5 minutes Environment: Variables: CORS_ORIGINS: "" @@ -79,6 +79,34 @@ Resources: Name: !Sub "${AWS::StackName}-Function" Stack: !Sub "${AWS::StackName}" + FastApiMigrationsFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + FunctionName: FastApiMigrations + Description: > + Lambda function to run alembic migrations + PackageType: Zip + Handler: app.migrate_handler.handler + Runtime: python3.10 + Timeout: 900 # 15 minutes + Environment: + Variables: + DB_SECRET_ARN: !Ref DBSecretArn + DB_ENDPOINT: !Ref DBEndpoint + DB_NAME: !Ref DBName + Role: !GetAtt FastApiBackendRole.Arn + VpcConfig: + SecurityGroupIds: + - !Ref LambdaSecurityGroupId + SubnetIds: + - !Ref LambdaSubnet1Id + - !Ref LambdaSubnet2Id + Architectures: + - x86_64 # GitHub Actions requires this: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources + Tags: + Name: !Sub "${AWS::StackName}MigrationsFunction" + Stack: !Sub "${AWS::StackName}" + # API Gateway endpoint HttpApi: Type: AWS::Serverless::HttpApi