Skip to content
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

devops: update CI/CD pipeline for prisma migrations #106

Merged
merged 15 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
200 changes: 114 additions & 86 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,92 +1,129 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/https://github.com/imperial/cpp-connect/tree/mainbuilding-and-testing-nodejs
name: CI pipeline

name: Node.js CI

# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
IMPAAS_APP_NAME: cpp-connect
IMPAAS_DEPLOY_TOKEN: ${{ secrets.IMPAAS_DEPLOY_TOKEN }}

on:
push:
branches: ["main"]
branches:
- main
- 'feat-*'
pull_request:
branches: ["main"]
branches:
- main

jobs:
lint:
changes:
runs-on: ubuntu-latest
outputs:
migrations: ${{ steps.filter.outputs.migrations }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
migrations:
- 'prisma/migrations/**'

install-dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Cache node_modules
uses: actions/cache@v4
id: cache-primes
with:
path: node_modules
key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
run: npm ci

type-check:
runs-on: ubuntu-latest
needs: install-dependencies
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
cache: "npm"
- uses: actions/cache@v4 # From https://nextjs.org/docs/pages/building-your-application/deploying/ci-build-caching#github-actions
node-version-file: '.nvmrc'
cache: 'npm'
- name: Load cached node_modules
uses: actions/cache@v4
id: cache-primes
with:
# See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node
path: |
${{ github.workspace }}/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: npm ci
- run: npm run lint
path: node_modules
key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}
- name: Prisma generate
run: npx prisma generate
- name: Check types
run: npm run type-check

format-check:
style-check:
runs-on: ubuntu-latest
needs: install-dependencies
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Load cached node_modules
uses: actions/cache@v4
id: cache-primes
with:
path: node_modules
key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}
- name: Run Prettier
run: npm run style-check

lint:
runs-on: ubuntu-latest
needs: install-dependencies
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
cache: "npm"
- uses: actions/cache@v4 # From https://nextjs.org/docs/pages/building-your-application/deploying/ci-build-caching#github-actions
node-version-file: '.nvmrc'
cache: 'npm'
- name: Load cached node_modules
uses: actions/cache@v4
id: cache-primes
with:
# See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node
path: |
${{ github.workspace }}/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: npm ci
- run: npm run format -- --check
path: node_modules
key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}
- name: Run linter
run: npm run lint

# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
build-and-push-image:
needs:
- type-check
- style-check
- lint
- format-check
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
Expand All @@ -98,63 +135,54 @@ jobs:
type=ref,event=pr
type=sha
type=raw,value=latest,enable={{is_default_branch}}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
# pushes regardless; only main tagged with latest
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha, mode=max
no-cache: true

# Deploy!
deploy-impaas:
needs:
- build-and-push-image
needs: build-and-push-image
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
env:
TSURU_TOKEN: ${{ secrets.IMPAAS_DEPLOY_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install ImPaaS CLI
run: |
curl -fsSL "https://tsuru.io/get" | bash
- name: Setup ImPaaS CLI
run: |
tsuru target add impaas https://impaas.uk -s
- name: Deploy to ImPaaS
run: |
tsuru app deploy -i ghcr.io/imperial/cpp-connect:latest -a cpp-connect
- name: Install tsuru CLI
run: curl -fsSL "https://tsuru.io/get" | bash
- name: Add impaas target
run: tsuru target add impaas https://impaas.uk -s
- name: Deploy app
run: tsuru app deploy -i ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -a ${{ env.IMPAAS_APP_NAME }}
- name: Create upload directories
run: |
tsuru app run "mkdir -p \$UPLOAD_DIR && cd \$UPLOAD_DIR && mkdir -p banners cvs avatars logos attachments" -a cpp-connect
- name: Set database URL
run: |
tsuru app run "DATABASE_URL=postgres://\$PGUSER:\$PGPASSWORD@\$PGHOST:\$PGPORT/\$PGDATABASE && echo \$DATABASE_URL" -a cpp-connect \
| grep "^postgres://" \
| xargs -I {} tsuru env set -a cpp-connect DATABASE_URL={}
- name: Run migrations
run: |
tsuru app run "npm exec prisma migrate deploy" -a cpp-connect
- name: Set environment variables
run: |
tsuru env set -a cpp-connect \
NEXTAUTH_URL=${{ vars.NEXTAUTH_URL }} \
EMAIL_SERVER_USER=${{ secrets.EMAIL_SERVER_USER }} \
EMAIL_SERVER_PASSWORD=${{ secrets.EMAIL_SERVER_PASSWORD }} \
EMAIL_SERVER_HOST=${{ secrets.EMAIL_SERVER_HOST }} \
EMAIL_SERVER_PORT=${{ secrets.EMAIL_SERVER_PORT }} \
EMAIL_FROM=${{ secrets.EMAIL_FROM }} \
AUTH_SECRET=${{ secrets.AUTH_SECRET }} \
AUTH_TRUST_HOST=true \
MS_ENTRA_CLIENT_ID=${{ secrets.MS_ENTRA_CLIENT_ID }} \
MS_ENTRA_CLIENT_SECRET=${{ secrets.MS_ENTRA_CLIENT_SECRET }} \
MS_ENTRA_TENANT_ID=${{ secrets.MS_ENTRA_TENANT_ID }}

apply-db-migrations:
runs-on: ubuntu-latest
needs:
- changes
- deploy-impaas
if: github.ref == 'refs/heads/main' && needs.changes.outputs.migrations == 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Load cached node_modules
uses: actions/cache@v4
id: cache-primes
with:
path: node_modules
key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}
- name: Apply all pending migrations to the database
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.13.0
63 changes: 21 additions & 42 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,68 +1,47 @@
FROM node:18-alpine AS base
# Stage 1: Build
FROM node:18-alpine AS builder

FROM base AS app_base

# Make UPLOAD_DIRs
ENV UPLOAD_DIR=/uploads
RUN mkdir $UPLOAD_DIR
RUN mkdir $UPLOAD_DIR/banners $UPLOAD_DIR/cvs $UPLOAD_DIR/avatars $UPLOAD_DIR/logos $UPLOAD_DIR/attachments
ENV NEXT_TELEMETRY_DISABLED=1
WORKDIR /app

# Prisma base image only for installing prisma
FROM base AS prisma_base
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install prisma --omit=dev

FROM prisma_base AS deps
WORKDIR /app

# Won't need to re-install prisma, since it's already installed in the prisma_base image
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate
RUN npm run build

# Disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1

RUN npm run build
# Stage 2: Production
FROM node:18-alpine AS runner

# Production image, copy all the files and run next
FROM app_base AS runner
ENV NEXT_TELEMETRY_DISABLED=1
WORKDIR /app

# Disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1

COPY --from=builder /app/public ./public
# Make UPLOAD_DIRs
ENV UPLOAD_DIR=/uploads
RUN mkdir $UPLOAD_DIR
RUN mkdir $UPLOAD_DIR/banners $UPLOAD_DIR/cvs $UPLOAD_DIR/avatars $UPLOAD_DIR/logos $UPLOAD_DIR/attachments
RUN chown node:node /app
RUN chown -R node:node $UPLOAD_DIR

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown node:node .next

# Copy prisma schema
COPY --from=builder --chown=node:node /app/prisma ./prisma

# Copy over the prisma client
COPY --from=prisma_base /app/node_modules ./node_modules

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/public ./public
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static

# Take ownership of the app folder and uploads
RUN chown node:node /app
RUN chown -R node:node $UPLOAD_DIR
COPY --from=builder --chown=node:node /app/prisma ./prisma

USER node

EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
ENV NODE_ENV=production

CMD HOSTNAME="0.0.0.0" node server.js
CMD ["npm", "run", "start"]
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --write '{app,components,lib}/**/*.{ts,tsx,scss}'",
"type-check": "tsc --noEmit",
"format": "prettier --write '{app,components,lib}/**/*.{js,jsx,ts,tsx,json,css,scss,md}'",
procaconsul marked this conversation as resolved.
Show resolved Hide resolved
"style-check": "prettier --check '{app,components,lib}/**/*.{ts,tsx,js,jsx,json,css,scss,md}'",
"prepare": "husky",
"db:pull": "dotenv -e .env.local prisma db pull",
"db:push": "dotenv -e .env.local prisma db push",
Expand Down Expand Up @@ -110,4 +112,4 @@
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}
}
}