From f4f22e0796d8475f2208abd9fa7bdcf25c8746b9 Mon Sep 17 00:00:00 2001 From: Serhii Date: Sun, 18 Aug 2024 16:13:41 +0300 Subject: [PATCH] chore:ci/cd, docker init --- .github/workflows/deploy.yml | 60 ++++++++++++++++++++++ .github/workflows/test.yml | 97 ++++++++++++++---------------------- docker-compose.yml | 42 ++++++++++++++++ frontend/.dockerignore | 12 +++-- frontend/Dockerfile | 29 +++++++++++ frontend/package-lock.json | 44 ++++++++++------ frontend/package.json | 5 +- frontend/src/pages/Home.tsx | 2 +- linguaphoto/Dockerfile | 23 +++++++++ scripts/docker-compose.yml | 20 -------- 10 files changed, 231 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 docker-compose.yml create mode 100644 frontend/Dockerfile create mode 100644 linguaphoto/Dockerfile delete mode 100644 scripts/docker-compose.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..b3bd3e5 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,60 @@ +name: Deploy to AWS App Runner + +# Trigger this workflow on any push to the 'main' branch +on: + push: + branches: + - master + +jobs: + # Job for deploying the backend service + deploy-backend: + name: Deploy Backend to AWS App Runner + runs-on: ubuntu-latest # Run on the latest version of Ubuntu + + steps: + # Step 1: Checkout the code from the repository + - name: Checkout code + uses: actions/checkout@v3 # GitHub Action to checkout the code + + # Step 2: Set up AWS CLI with necessary credentials + - name: Set up AWS CLI + uses: aws-actions/configure-aws-credentials@v2 # GitHub Action to configure AWS credentials + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} # AWS Access Key ID stored as a GitHub secret + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # AWS Secret Access Key stored as a GitHub secret + aws-region: us-east-1 # AWS Region stored as a GitHub secret + + # Step 3: Deploy the backend service to AWS App Runner + - name: Deploy Backend + run: | + # AWS CLI command to update the backend service in AWS App Runner + aws apprunner update-service \ + --service-arn $(aws apprunner list-services --query "ServiceSummaryList[?ServiceName=='${{ secrets.APP_RUNNER_SERVICE_NAME_BACKEND }}'].ServiceArn | [0]" --output text) \ + --source-configuration SourceCodeRepository={"RepositoryUrl": "${{ secrets.REPOSITORY_URL_BACKEND }}", "SourceCodeVersion": {"Type": "BRANCH", "Value": "master"}} + + # Job for deploying the frontend service + deploy-frontend: + name: Deploy Frontend to AWS App Runner + runs-on: ubuntu-latest # Run on the latest version of Ubuntu + + steps: + # Step 1: Checkout the code from the repository + - name: Checkout code + uses: actions/checkout@v3 # GitHub Action to checkout the code + + # Step 2: Set up AWS CLI with necessary credentials + - name: Set up AWS CLI + uses: aws-actions/configure-aws-credentials@v2 # GitHub Action to configure AWS credentials + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} # AWS Access Key ID stored as a GitHub secret + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # AWS Secret Access Key stored as a GitHub secret + aws-region: ${{ secrets.AWS_REGION }} # AWS Region stored as a GitHub secret + + # Step 3: Deploy the frontend service to AWS App Runner + - name: Deploy Frontend + run: | + # AWS CLI command to update the frontend service in AWS App Runner + aws apprunner update-service \ + --service-arn $(aws apprunner list-services --query "ServiceSummaryList[?ServiceName=='${{ secrets.APP_RUNNER_SERVICE_NAME_FRONTEND }}'].ServiceArn | [0]" --output text) \ + --source-configuration SourceCodeRepository={"RepositoryUrl": "${{ secrets.REPOSITORY_URL_FRONTEND }}", "SourceCodeVersion": {"Type": "BRANCH", "Value": "master"}} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 926c7f1..c5e6980 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,6 @@ -name: CI Checks (Linting and Tests) +# GitHub Actions Workflow to test a full-stack project (Node.js frontend and Python backend) +# Trigger workflow on push to master or pull requests targeting master on: push: branches: @@ -13,101 +14,79 @@ on: - synchronize - ready_for_review +# Handle concurrency to cancel in-progress runs if a new one starts concurrency: group: tests-${{ github.head_ref || github.run_id }} cancel-in-progress: true -env: - LINGUAPHOTO_ENVIRONMENT: local - JWT_SECRET: test - AWS_ACCESS_KEY_ID: test - AWS_SECRET_ACCESS_KEY: test - AWS_ENDPOINT_URL_DYNAMODB: http://localhost:8000 - AWS_REGION: us-east-1 - jobs: - backend-tests: + run-tests: + # Set job timeout to 10 minutes timeout-minutes: 10 runs-on: ubuntu-latest steps: + # Step 1: Check out the repository - name: Check out repository uses: actions/checkout@v3 + # Step 2: Set up Node.js environment + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "20.10.0" + + # Step 3: Restore cache (Node modules and mypy cache) - name: Restore cache id: restore-cache uses: actions/cache/restore@v3 with: path: | - ${{ env.pythonLocation }} .mypy_cache/ - key: backend-tests-${{ github.event.pull_request.base.sha || github.sha }} + frontend/node_modules/ + key: tests-${{ github.sha }}-${{ hashFiles('frontend/package-lock.json') }}-${{ hashFiles('linguaphoto/requirements.txt') }} restore-keys: | - backend-tests- + tests-${{ github.sha }}- + tests- + # Step 4: Install Node.js packages + - name: Install Node packages + working-directory: frontend + run: npm install + + # Step 5: Build the frontend + - name: Build frontend + working-directory: frontend + run: npm run build + + # Step 6: Set up Python environment - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.11" - - name: Install Python package + # Step 7: Install Python packages (including dev dependencies) + - name: Install Python dependencies run: | - pip install --upgrade --upgrade-strategy eager -e '.[dev]' + cd linguaphoto # Navigate to the linguaphoto directory + pip install -r requirements.txt # Install Python dependencies + # Step 8: Run static code checks (linters, type checkers, etc.) - name: Run static checks run: | mkdir -p .mypy_cache make static-checks + # Step 9: Run unit tests for the backend - name: Run unit tests - run: | - make test-backend + run: make test-backend + # Step 10: Save cache (only on the master branch) - name: Save cache uses: actions/cache/save@v3 if: github.ref == 'refs/heads/master' with: path: | - ${{ env.pythonLocation }} .mypy_cache/ - key: ${{ steps.restore-cache.outputs.cache-primary-key }} - - frontend-tests: - timeout-minutes: 10 - runs-on: ubuntu-latest - - steps: - - name: Check out repository - uses: actions/checkout@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: "20" - - - name: Restore cache - id: restore-cache - uses: actions/cache/restore@v3 - with: - path: | - node_modules/ - key: frontend-tests-${{ github.event.pull_request.base.sha || github.sha }} - restore-keys: | - frontend-tests- - - - name: Install Node package - working-directory: frontend - run: | - npm install - - - name: Run tests - run: | - make test-frontend - - - name: Save cache - uses: actions/cache/save@v3 - if: github.ref == 'refs/heads/master' - with: - path: | - node_modules/ - key: ${{ steps.restore-cache.outputs.cache-primary-key }} + frontend/node_modules/ + key: tests-${{ github.sha }}-${{ hashFiles('frontend/package-lock.json') }}-${{ hashFiles('linguaphoto/requirements.txt') }} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fe5e4d3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ + + +services: + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + ports: + - "80:80" + depends_on: + - backend + + backend: + build: + context: ./linguaphoto + dockerfile: Dockerfile + ports: + - "8000:8000" + environment: + DEBUG: "true" + AWS_REGION: " us-east-1" # Environment variables for DynamoDB + AWS_ACCESS_KEY_ID: "your-access-key-id" + AWS_SECRET_ACCESS_KEY: "your-secret-access-key" + DYNAMODB_ENDPOINT: "http://dynamodb:8002" # Local DynamoDB endpoint + REDIS_URL: "redis://redis:6379" # Redis connection URL + + dynamodb: + image: amazon/dynamodb-local + ports: + - "8002:8000" + + dynamodb-admin: + image: aaronshaf/dynamodb-admin + ports: + - "8001:8001" + environment: + - DYNAMO_ENDPOINT=http://dynamodb:8000 + + redis: + image: redis + ports: + - "6379:6379" \ No newline at end of file diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 832b79f..182069a 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,5 +1,7 @@ -# .dockerignore - -# Only add the build directory to Docker. -* -!build/ +node_modules +build +dist +.git +Dockerfile +README.md +.env \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..94a3855 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,29 @@ +# Use an official Node.js runtime as a parent image +FROM node:20.10.0-alpine AS build + +# Set the working directory in the container +WORKDIR /src + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Build the React app +RUN npm run build + +# Use a lightweight web server to serve the React app +FROM nginx:1.25-alpine + +# Copy the build output to Nginx's html directory +COPY --from=build /src/build /usr/share/nginx/html + +# Expose port 80 to the outside world +EXPOSE 80 + +# Start Nginx when the container launches +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 655e1bc..7fbbe81 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.6.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -31,8 +32,8 @@ "@eslint/compat": "*", "@eslint/js": "*", "@fortawesome/free-brands-svg-icons": "^6.5.2", - "@fortawesome/free-solid-svg-icons": "^6.5.2", - "@fortawesome/react-fontawesome": "github:fortawesome/react-fontawesome", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@fortawesome/react-fontawesome": "^0.2.2", "@react-oauth/google": "^0.12.1", "@types/jest": "^29.5.12", "axios": "^1.7.2", @@ -2570,20 +2571,24 @@ } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", - "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "peer": true, + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.2" + "@fortawesome/fontawesome-common-types": "6.6.0" }, "engines": { "node": ">=6" } }, + "node_modules/@fortawesome/fontawesome-svg-core/node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/free-brands-svg-icons": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz", @@ -2599,19 +2604,26 @@ } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", - "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", "dev": true, - "hasInstallScript": true, - "license": "(CC-BY-4.0 AND MIT)", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.2" + "@fortawesome/fontawesome-common-types": "6.6.0" }, "engines": { "node": ">=6" } }, + "node_modules/@fortawesome/free-solid-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/react-fontawesome": { "version": "0.2.2", "resolved": "git+ssh://git@github.com/fortawesome/react-fontawesome.git#432b921d69d382c54ad9495fa9cbdcea539de05f", diff --git a/frontend/package.json b/frontend/package.json index 46283c6..2c19b98 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.6.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -48,8 +49,8 @@ "@eslint/compat": "*", "@eslint/js": "*", "@fortawesome/free-brands-svg-icons": "^6.5.2", - "@fortawesome/free-solid-svg-icons": "^6.5.2", - "@fortawesome/react-fontawesome": "github:fortawesome/react-fontawesome", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@fortawesome/react-fontawesome": "^0.2.2", "@react-oauth/google": "^0.12.1", "@types/jest": "^29.5.12", "axios": "^1.7.2", diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index c129e49..a65ce1c 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -14,7 +14,7 @@ const Home = () => {

LinguaPhoto

Visual language learning for everyone!

- + {/* */} diff --git a/linguaphoto/Dockerfile b/linguaphoto/Dockerfile new file mode 100644 index 0000000..fbbe2af --- /dev/null +++ b/linguaphoto/Dockerfile @@ -0,0 +1,23 @@ +# Use an official Python runtime as a parent image +FROM python:3.11-slim + +# Set the working directory in the container +WORKDIR /app + +# Copy the requirements file into the container +COPY requirements.txt . + +# Install any dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the current directory contents into the container at /app +COPY . . + +# Make port 8000 available to the world outside this container +EXPOSE 8000 + +# Define environment variable for FastAPI +ENV PYTHONUNBUFFERED=1 + +# Command to run the FastAPI server +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/scripts/docker-compose.yml b/scripts/docker-compose.yml deleted file mode 100644 index a8b6cf0..0000000 --- a/scripts/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Composes DynamoDB Local, the DynamoDB Admin UI, and Redis -version: '3.7' - -services: - dynamodb: - image: amazon/dynamodb-local - ports: - - "8000:8000" - - dynamodb-admin: - image: aaronshaf/dynamodb-admin - ports: - - "8001:8001" - environment: - - DYNAMO_ENDPOINT=http://dynamodb:8000 - - redis: - image: redis - ports: - - "6379:6379"