diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0328da9..11d69ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,47 +1,75 @@ -name: Run Tests +name: "Run Tests" -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: + types: + - opened + - synchronize + - reopened + - closed jobs: test: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.11 - - - name: Install Poetry - run: | - python -m pip install --upgrade pip - python -m pip install poetry - - - name: Install dependencies - working-directory: ./space2stats_api/src - run: | - poetry install --with test - - - name: install lib postgres - uses: nyurik/action-setup-postgis@v2 - - - name: Run pre-commit - working-directory: ./space2stats_api/src - run: | - poetry run pre-commit run --all-files - - - name: Run tests - working-directory: ./space2stats_api/src - run: | - poetry run python -m pytest --benchmark-skip tests - env: - PGHOST: localhost - PGPORT: 5432 - PGDATABASE: mydatabase - PGUSER: myuser - PGPASSWORD: mypassword - PGTABLENAME: space2stats - S3_BUCKET_NAME: test-bucket \ No newline at end of file + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Install Poetry + run: | + python -m pip install --upgrade pip + python -m pip install poetry + + - name: Install dependencies + working-directory: ./space2stats_api/src + run: | + poetry install --with test + + - name: install lib postgres + uses: nyurik/action-setup-postgis@v2 + + - name: Run pre-commit + working-directory: ./space2stats_api/src + run: | + poetry run pre-commit run --all-files + + - name: Run tests + working-directory: ./space2stats_api/src + run: | + poetry run python -m pytest --benchmark-skip tests + env: + PGHOST: localhost + PGPORT: 5432 + PGDATABASE: mydatabase + PGUSER: myuser + PGPASSWORD: mypassword + PGTABLENAME: space2stats + S3_BUCKET_NAME: test-bucket + + deploy-to-dev: + if: ${{ github.event_name == 'pull_request' }} + needs: test + uses: "./.github/workflows/reusable-deploy.yml" + with: + environment: Space2Stats API Dev + stage: pr-${{ github.event.pull_request.number }} + pr-number: ${{ github.event.pull_request.number }} + secrets: inherit + + deploy-to-production: + if: ${{ github.event_name == 'push' && github.ref_name == 'main' }} + needs: test + uses: "./.github/workflows/reusable-deploy.yml" + with: + environment: Space2Stats API Prod + stage: prod + secrets: inherit diff --git a/.github/workflows/destroy.yml b/.github/workflows/destroy.yml new file mode 100644 index 0000000..fae8ffa --- /dev/null +++ b/.github/workflows/destroy.yml @@ -0,0 +1,70 @@ +name: Destroy Preview Environment + +on: + pull_request: + types: + - closed + +permissions: + id-token: write + contents: read + pull-requests: write + +jobs: + destroy: + concurrency: Space2Stats API Dev + environment: Space2Stats API Dev + runs-on: ubuntu-latest + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - name: Install AWS CDK + run: npm install -g aws-cdk + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::017820688988:role/Space2Stats-Deploy-Role + aws-region: ${{ vars.CDK_DEFAULT_REGION }} + + - name: Install CDK dependencies + working-directory: ./space2stats_api/cdk + run: | + pip install -r requirements-cdk.txt + + - name: Tear down CDK stack + working-directory: ./space2stats_api/cdk + env: + STAGE: pr-${{ github.event.pull_request.number }} + PGHOST: ${{ secrets.PGHOST }} + PGPORT: ${{ secrets.PGPORT }} + PGDATABASE: ${{ secrets.PGDATABASE }} + PGUSER: ${{ secrets.PGUSER }} + PGPASSWORD: ${{ secrets.PGPASSWORD }} + PGTABLENAME: ${{ secrets.PGTABLENAME }} + CDK_CERTIFICATE_ARN: ${{ vars.CDK_CERTIFICATE_ARN }} + CDK_DEFAULT_ACCOUNT: ${{ vars.CDK_DEFAULT_ACCOUNT }} + CDK_DEFAULT_REGION: ${{ vars.CDK_DEFAULT_REGION }} + CDK_DOMAIN_NAME: ${{ vars.CDK_DOMAIN_NAME }} + run: cdk destroy --require-approval never + + - name: Find Comment + uses: peter-evans/find-comment@v3 + id: find-comment + if: ${{ github.event.pull_request.number }} + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "PR Deployment Details:" + + - name: Create or update comment with removal confirmation + uses: peter-evans/create-or-update-comment@v4 + if: ${{ github.event.pull_request.number }} + with: + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + body: | + Removed PR Preview Environment. + edit-mode: append diff --git a/.github/workflows/reusable-deploy.yml b/.github/workflows/reusable-deploy.yml new file mode 100644 index 0000000..f25b3e3 --- /dev/null +++ b/.github/workflows/reusable-deploy.yml @@ -0,0 +1,85 @@ +name: Deploy + +on: + workflow_call: + inputs: + environment: + type: string + required: true + stage: + type: string + required: true + pr-number: + type: number + required: false + +permissions: + id-token: write + contents: read + pull-requests: write + +jobs: + deploy: + concurrency: ${{ inputs.stage }} + environment: ${{ inputs.environment }} + runs-on: ubuntu-latest + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - name: Install AWS CDK + run: npm install -g aws-cdk + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::017820688988:role/Space2Stats-Deploy-Role + aws-region: ${{ vars.CDK_DEFAULT_REGION }} + + - name: Install CDK dependencies + working-directory: ./space2stats_api/cdk + run: | + pip install -r requirements-cdk.txt + + - name: Deploy CDK stack to staging + working-directory: ./space2stats_api/cdk + env: + STAGE: ${{ inputs.stage }} + PGHOST: ${{ secrets.PGHOST }} + PGPORT: ${{ secrets.PGPORT }} + PGDATABASE: ${{ secrets.PGDATABASE }} + PGUSER: ${{ secrets.PGUSER }} + PGPASSWORD: ${{ secrets.PGPASSWORD }} + PGTABLENAME: ${{ secrets.PGTABLENAME }} + CDK_CERTIFICATE_ARN: ${{ vars.CDK_CERTIFICATE_ARN }} + CDK_DEFAULT_ACCOUNT: ${{ vars.CDK_DEFAULT_ACCOUNT }} + CDK_DEFAULT_REGION: ${{ vars.CDK_DEFAULT_REGION }} + CDK_DOMAIN_NAME: ${{ vars.CDK_DOMAIN_NAME }} + run: cdk deploy --require-approval never --outputs-file outputs.json + + - name: Get API URL + id: get-api-url + working-directory: ./space2stats_api/cdk + run: | + echo "api-url=$(jq -r '."Space2Stats-${{ inputs.stage }}".ApiGatewayUrl' outputs.json)" >> $GITHUB_OUTPUT + + - name: Find Comment + uses: peter-evans/find-comment@v3 + id: find-comment + if: ${{ inputs.pr-number }} + with: + issue-number: ${{ inputs.pr-number }} + comment-author: "github-actions[bot]" + body-includes: "PR Deployment Details:" + + - name: Create or update comment with URL + uses: peter-evans/create-or-update-comment@v4 + if: ${{ inputs.pr-number }} + with: + issue-number: ${{ inputs.pr-number }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + body: | + PR Deployment Details: + 🚀 PR deployed to ${{ steps.get-api-url.outputs.api-url }} + edit-mode: replace diff --git a/space2stats_api/cdk/app.py b/space2stats_api/cdk/app.py index a531357..edc0eca 100644 --- a/space2stats_api/cdk/app.py +++ b/space2stats_api/cdk/app.py @@ -1,8 +1,12 @@ +import os + from aws_cdk import App, Environment from aws_stack import Space2StatsStack from settings import DeploymentSettings -settings = DeploymentSettings(_env_file="aws_deployment.env") +settings = DeploymentSettings( + _env_file=f"aws_deployment_{os.environ.get('STAGE', 'dev')}.env" +) env = Environment( account=settings.CDK_DEFAULT_ACCOUNT, region=settings.CDK_DEFAULT_REGION @@ -10,6 +14,8 @@ app = App() -Space2StatsStack(app, "Space2StatsStack", env=env) +Space2StatsStack( + app, f"Space2Stats-{settings.STAGE}", env=env, deployment_settings=settings +) app.synth() diff --git a/space2stats_api/cdk/aws_stack.py b/space2stats_api/cdk/aws_stack.py index 251edb2..66aaf2f 100644 --- a/space2stats_api/cdk/aws_stack.py +++ b/space2stats_api/cdk/aws_stack.py @@ -1,4 +1,4 @@ -from aws_cdk import Duration, Stack +from aws_cdk import CfnOutput, Duration, Stack from aws_cdk import aws_apigatewayv2 as apigatewayv2 from aws_cdk import aws_apigatewayv2_integrations as integrations from aws_cdk import aws_certificatemanager as acm @@ -10,11 +10,16 @@ class Space2StatsStack(Stack): - def __init__(self, scope: Construct, id: str, **kwargs) -> None: + def __init__( + self, + scope: Construct, + id: str, + deployment_settings: DeploymentSettings, + **kwargs, + ) -> None: super().__init__(scope, id, **kwargs) app_settings = AppSettings(_env_file="./aws_app.env") - deployment_settings = DeploymentSettings(_env_file="./aws_deployment.env") bucket = s3.Bucket( self, @@ -43,13 +48,6 @@ def __init__(self, scope: Construct, id: str, **kwargs) -> None: self, "Certificate", deployment_settings.CDK_CERTIFICATE_ARN ) - domain_name = apigatewayv2.DomainName( - self, - "DomainName", - domain_name=deployment_settings.CDK_DOMAIN_NAME, - certificate=certificate, - ) - http_api = apigatewayv2.HttpApi( self, "Space2StatsHttpApi", @@ -58,10 +56,24 @@ def __init__(self, scope: Construct, id: str, **kwargs) -> None: ), ) - apigatewayv2.ApiMapping( + CfnOutput( self, - "ApiMapping", - api=http_api, - domain_name=domain_name, - stage=http_api.default_stage, + "ApiGatewayUrl", + key="ApiGatewayUrl", + value=http_api.url, ) + + if deployment_settings.CDK_DOMAIN_NAME: + domain_name = apigatewayv2.DomainName( + self, + "DomainName", + domain_name=deployment_settings.CDK_DOMAIN_NAME, + certificate=certificate, + ) + apigatewayv2.ApiMapping( + self, + "ApiMapping", + api=http_api, + domain_name=domain_name, + stage=http_api.default_stage, + ) diff --git a/space2stats_api/cdk/requirements-cdk.txt b/space2stats_api/cdk/requirements-cdk.txt index 1221d73..2fb220d 100644 --- a/space2stats_api/cdk/requirements-cdk.txt +++ b/space2stats_api/cdk/requirements-cdk.txt @@ -1,4 +1,5 @@ aws-cdk-lib==2.130.0 +aws-cdk.aws-lambda-python-alpha==2.130.0-alpha.0 constructs==10.3.0 pydantic_settings>=2.0 \ No newline at end of file diff --git a/space2stats_api/cdk/settings.py b/space2stats_api/cdk/settings.py index 5934527..83044aa 100644 --- a/space2stats_api/cdk/settings.py +++ b/space2stats_api/cdk/settings.py @@ -1,3 +1,5 @@ +from typing import Optional + from pydantic_settings import BaseSettings @@ -14,4 +16,5 @@ class DeploymentSettings(BaseSettings): CDK_DEFAULT_ACCOUNT: str CDK_DEFAULT_REGION: str CDK_CERTIFICATE_ARN: str - CDK_DOMAIN_NAME: str + CDK_DOMAIN_NAME: Optional[str] + STAGE: str = "dev"