adding sharp for image optimization #21
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: CI/CD | |
on: | |
push: | |
branches: | |
- 'main' | |
env: | |
API_ARTIFACT_REGISTRY_LOCATION: europe-west1 | |
API_ARTIFACT_REGISTRY: endpoints-api | |
API_ARTIFACT_REGISTRY_CONTAINER_NAME: endpoints-runtime-serverless | |
API_CLOUD_RUN_LOCATION: europe-west1 | |
API_CLOUD_RUN_SERVICE: public-api | |
API_ENDPOINTS_SERVICE: api.playlists.derlev.xyz | |
API_ESP_BASE_IMAGE: gcr.io/endpoints-release/endpoints-runtime-serverless:2 | |
FRONTEND_ARTIFACT_REGISTRY_LOCATION: europe-west1 | |
FRONTEND_ARTIFACT_REGISTRY: cr-webapp | |
FRONTEND_ARTIFACT_REGISTRY_CONTAINER_NAME: frontend | |
FRONTEND_CLOUD_RUN_LOCATION: europe-west1 | |
FRONTEND_CLOUD_RUN_SERVICE: frontend | |
jobs: | |
# Path Filtering | |
filter-paths: | |
name: Filter Paths | |
runs-on: ubuntu-latest | |
outputs: | |
functions: ${{ steps.filter.outputs.functions }} | |
api: ${{ steps.filter.outputs.api }} | |
frontend: ${{ steps.filter.outputs.frontend }} | |
permissions: | |
contents: read | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
- name: Filter Paths | |
id: filter | |
uses: dorny/paths-filter@v3.0.2 | |
with: | |
filters: | | |
functions: | |
- 'functions/**' | |
- '!(functions)/**/*.md' | |
api: | |
- 'public-api/**' | |
- '!(public-api)/**/*.md' | |
frontend: | |
- 'frontend/**' | |
- '!(frontend)/**/*.md' | |
# START: Cloud Functions | |
func-lint-typecheck: | |
name: 'Functions: Lint & Type Check' | |
runs-on: ubuntu-latest | |
needs: filter-paths | |
if: needs.filter-paths.outputs.functions == 'true' | |
permissions: | |
checks: write | |
pull-requests: read | |
contents: read | |
defaults: | |
run: | |
working-directory: functions | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
- name: Setup Nodejs Environment | |
uses: actions/setup-node@v4 | |
with: | |
node-version: 20 | |
cache: yarn | |
cache-dependency-path: 'functions/yarn.lock' | |
- name: Install Dependencies | |
run: yarn --frozen-lockfile | |
- name: Lint | |
run: yarn lint --output-file eslint_report.json --format json | |
continue-on-error: true | |
- name: Type Check | |
run: yarn tsc --noEmit > typescript.log | |
continue-on-error: true | |
- name: Annotate Code | |
uses: DerLev/eslint-annotations@v2 | |
with: | |
eslint-report: functions/eslint_report.json | |
typescript-log: functions/typescript.log | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
error-on-warn: true | |
status-check-name: 'Functions: Annotations' | |
fail-in-pr: false | |
add-notice-with-url: false | |
# END: Cloud Functions | |
# START: Public API - Cloud Endpoints | |
api-delete-outdated-ar: | |
name: "API: Delete outdated Container Images from Artifact Registry" | |
runs-on: ubuntu-latest | |
needs: filter-paths | |
if: needs.filter-paths.outputs.api == 'true' | |
permissions: | |
id-token: write | |
steps: | |
- name: Prevent Auth from printing warning | |
run: echo "Shut up!" > dont_warn.txt | |
- name: Authenticate to Google Cloud | |
id: auth | |
uses: google-github-actions/auth@v2 | |
with: | |
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }} | |
service_account: ${{ secrets.GCP_SA_EMAIL }} | |
- name: Cleanup Artifact Registry | |
uses: docker://europe-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli | |
with: | |
args: >- | |
-repo=${{ env.API_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.API_ARTIFACT_REGISTRY }}/${{ env.API_ARTIFACT_REGISTRY_CONTAINER_NAME }} | |
-keep=3 | |
-tag-filter-any=.* | |
api-delete-outdated-cr: | |
name: "API: Delete outdated Cloud Run Revisions" | |
runs-on: ubuntu-latest | |
needs: filter-paths | |
if: needs.filter-paths.outputs.api == 'true' | |
permissions: | |
id-token: write | |
steps: | |
- name: Prevent Auth from printing warning | |
run: echo "Shut up!" > dont_warn.txt | |
- name: Authenticate to Google Cloud | |
id: auth | |
uses: google-github-actions/auth@v2 | |
with: | |
token_format: "access_token" | |
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }} | |
service_account: ${{ secrets.GCP_SA_EMAIL }} | |
access_token_lifetime: 60s | |
create_credentials_file: false | |
- name: Only leave the most recent Cloud Run Revisions | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const response = await fetch("https://run.googleapis.com/v2/projects/${{ vars.GCP_PROJECT_ID }}/locations/${{ env.API_CLOUD_RUN_LOCATION }}/services/${{ env.API_CLOUD_RUN_SERVICE }}/revisions", { headers: { 'Authorization': "Bearer ${{ steps.auth.outputs.access_token }}" } }) | |
.then(res => res.json()); | |
const toBeDeleted = response.revisions.sort((a, b) => (new Date(b.createTime) - new Date(a.createTime))).slice(1); | |
const deletionPromises = []; | |
for(const revision of toBeDeleted) { | |
deletionPromises.push(fetch(`https://run.googleapis.com/v2/${revision.name}`, { method: 'DELETE', headers: { 'Authorization': "Bearer ${{ steps.auth.outputs.access_token }}" } })); | |
} | |
await Promise.all(deletionPromises); | |
api-deploy: | |
name: 'API: Deploy new OpenAPI version' | |
runs-on: ubuntu-latest | |
needs: [api-delete-outdated-ar, api-delete-outdated-cr] | |
permissions: | |
contents: read | |
id-token: write | |
defaults: | |
run: | |
working-directory: public-api | |
environment: "API: production" | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
- name: Setup Docker Buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Authenticate to Google Cloud | |
id: auth | |
uses: google-github-actions/auth@v2 | |
with: | |
token_format: access_token | |
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }} | |
service_account: ${{ secrets.GCP_SA_EMAIL }} | |
access_token_lifetime: 600s | |
- name: Login to GCP Artifact Registry | |
uses: docker/login-action@v3 | |
with: | |
registry: ${{ env.API_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev | |
username: oauth2accesstoken | |
password: ${{ steps.auth.outputs.access_token }} | |
- name: Setup Cloud SDK | |
uses: google-github-actions/setup-gcloud@v2 | |
- name: Deploy new Cloud Endpoints Revision | |
run: 'gcloud endpoints services deploy openapi-run.yaml' | |
- name: Get latest Revision Config ID | |
id: current-config-id | |
run: echo "config-id=$(gcloud endpoints configs list --service=${{ env.API_ENDPOINTS_SERVICE }} | awk 'NR==2 {print $1}')" >> $GITHUB_OUTPUT | |
- name: Download ESPv2 Config | |
id: current-esp-config | |
run: 'curl -o "service.json" -H "Authorization: Bearer ${{ steps.auth.outputs.access_token }}" "https://servicemanagement.googleapis.com/v1/services/${{ env.API_ENDPOINTS_SERVICE }}/configs/${{ steps.current-config-id.outputs.config-id }}?view=FULL"' | |
- name: Create Dockerfile for ESPv2 Container | |
run: | | |
cat <<EOF > Dockerfile | |
FROM ${{ env.API_ESP_BASE_IMAGE }} | |
USER root | |
ENV ENDPOINTS_SERVICE_PATH /etc/endpoints/service.json | |
COPY service.json \${ENDPOINTS_SERVICE_PATH} | |
RUN chown -R envoy:envoy \${ENDPOINTS_SERVICE_PATH} && chmod -R 755 \${ENDPOINTS_SERVICE_PATH} | |
USER envoy | |
ENTRYPOINT ["/env_start_proxy.py"] | |
EOF | |
- name: Extract Metadata for Docker | |
id: meta | |
uses: docker/metadata-action@v5 | |
with: | |
images: ${{ env.API_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.API_ARTIFACT_REGISTRY }}/${{ env.API_ARTIFACT_REGISTRY_CONTAINER_NAME }} | |
tags: | | |
type=raw,value=latest | |
type=sha,prefix=,format=long | |
type=raw,value=${{ env.API_ENDPOINTS_SERVICE }}-${{ steps.current-config-id.outputs.config-id }} | |
- name: Build and Push Docker Image | |
uses: docker/build-push-action@v5 | |
with: | |
context: public-api/ | |
push: true | |
tags: ${{ steps.meta.outputs.tags }} | |
labels: ${{ steps.meta.outputs.labels }} | |
- name: Create new Cloud Run Revision | |
env: | |
CLOUDSDK_CORE_DISABLE_PROMPTS: 1 | |
run: gcloud run deploy ${{ env.API_CLOUD_RUN_SERVICE }} --image ${{ env.API_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.API_ARTIFACT_REGISTRY }}/${{ env.API_ARTIFACT_REGISTRY_CONTAINER_NAME }}:${{ github.sha }} --region ${{ env.API_CLOUD_RUN_LOCATION }} | |
# END: Public API - Cloud Endpoints | |
# START: Frontend | |
frontend-lint-typecheck: | |
name: 'Frontend: Lint & Type Check' | |
runs-on: ubuntu-latest | |
needs: filter-paths | |
if: needs.filter-paths.outputs.frontend == 'true' | |
permissions: | |
checks: write | |
pull-requests: read | |
contents: read | |
defaults: | |
run: | |
working-directory: frontend | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
- name: Setup Nodejs Environment | |
uses: actions/setup-node@v4 | |
with: | |
node-version: 20 | |
cache: yarn | |
cache-dependency-path: 'frontend/yarn.lock' | |
- name: Install Dependencies | |
run: yarn --frozen-lockfile | |
- name: Lint | |
run: yarn lint:nofix --output-file eslint_report.json --format json | |
continue-on-error: true | |
- name: Type Check | |
run: yarn tsc --noEmit > typescript.log | |
continue-on-error: true | |
- name: Annotate Code | |
uses: DerLev/eslint-annotations@v2 | |
with: | |
eslint-report: frontend/eslint_report.json | |
typescript-log: frontend/typescript.log | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
error-on-warn: true | |
status-check-name: 'Frontend: Annotations' | |
fail-in-pr: false | |
add-notice-with-url: false | |
frontend-delete-outdated-ar: | |
name: "Frontend: Delete outdated Container Images from Artifact Registry" | |
runs-on: ubuntu-latest | |
needs: frontend-lint-typecheck | |
permissions: | |
id-token: write | |
steps: | |
- name: Prevent Auth from printing warning | |
run: echo "Shut up!" > dont_warn.txt | |
- name: Authenticate to Google Cloud | |
id: auth | |
uses: google-github-actions/auth@v2 | |
with: | |
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }} | |
service_account: ${{ secrets.GCP_SA_EMAIL }} | |
- name: Cleanup Artifact Registry | |
uses: docker://europe-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli | |
with: | |
args: >- | |
-repo=${{ env.FRONTEND_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.FRONTEND_ARTIFACT_REGISTRY }}/${{ env.FRONTEND_ARTIFACT_REGISTRY_CONTAINER_NAME }} | |
-keep=6 | |
-tag-filter-any=.* | |
frontend-delete-outdated-cr: | |
name: "Frontend: Delete outdated Cloud Run Revisions" | |
runs-on: ubuntu-latest | |
needs: frontend-lint-typecheck | |
permissions: | |
id-token: write | |
steps: | |
- name: Prevent Auth from printing warning | |
run: echo "Shut up!" > dont_warn.txt | |
- name: Authenticate to Google Cloud | |
id: auth | |
uses: google-github-actions/auth@v2 | |
with: | |
token_format: "access_token" | |
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }} | |
service_account: ${{ secrets.GCP_SA_EMAIL }} | |
access_token_lifetime: 60s | |
create_credentials_file: false | |
- name: Only leave the 2 most recent Cloud Run Revisions | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const response = await fetch("https://run.googleapis.com/v2/projects/${{ vars.GCP_PROJECT_ID }}/locations/${{ env.FRONTEND_CLOUD_RUN_LOCATION }}/services/${{ env.FRONTEND_CLOUD_RUN_SERVICE }}/revisions", { headers: { 'Authorization': "Bearer ${{ steps.auth.outputs.access_token }}" } }) | |
.then(res => res.json()); | |
const toBeDeleted = response.revisions.sort((a, b) => (new Date(b.createTime) - new Date(a.createTime))).slice(2); | |
const deletionPromises = []; | |
for(const revision of toBeDeleted) { | |
deletionPromises.push(fetch(`https://run.googleapis.com/v2/${revision.name}`, { method: 'DELETE', headers: { 'Authorization': "Bearer ${{ steps.auth.outputs.access_token }}" } })); | |
} | |
await Promise.all(deletionPromises); | |
frontend-build-deploy: | |
name: "Frontend: Build Container & Deploy on Cloud Run" | |
runs-on: ubuntu-latest | |
needs: [frontend-delete-outdated-ar, frontend-delete-outdated-cr] | |
permissions: | |
contents: read | |
id-token: write | |
environment: "Webapp: production" | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
- name: Setup Docker Buildx | |
uses: docker/setup-buildx-action@v3 | |
- name: Authenticate to Google Cloud | |
id: auth | |
uses: google-github-actions/auth@v2 | |
with: | |
token_format: access_token | |
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }} | |
service_account: ${{ secrets.GCP_SA_EMAIL }} | |
access_token_lifetime: 600s | |
- name: Login to GCP Artifact Registry | |
uses: docker/login-action@v3 | |
with: | |
registry: ${{ env.FRONTEND_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev | |
username: oauth2accesstoken | |
password: ${{ steps.auth.outputs.access_token }} | |
- name: Extract Metadata for Docker | |
id: meta | |
uses: docker/metadata-action@v5 | |
with: | |
images: ${{ env.FRONTEND_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.FRONTEND_ARTIFACT_REGISTRY }}/${{ env.FRONTEND_ARTIFACT_REGISTRY_CONTAINER_NAME }} | |
tags: | | |
type=raw,value=latest | |
type=sha,prefix=,format=long | |
- name: Build and Push Docker Image | |
uses: docker/build-push-action@v5 | |
with: | |
context: frontend/ | |
push: true | |
tags: ${{ steps.meta.outputs.tags }} | |
labels: ${{ steps.meta.outputs.labels }} | |
cache-from: type=gha | |
cache-to: type=gha,mode=max | |
- name: Set up Cloud SDK | |
uses: google-github-actions/setup-gcloud@v2 | |
- name: Get short commit SHA | |
id: commit_sha | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const sha = "${{ github.sha }}" | |
const shortSha = sha.substring(0, 7) | |
core.setOutput('shortSha', shortSha) | |
- name: Create new Cloud Run Revision | |
env: | |
CLOUDSDK_CORE_DISABLE_PROMPTS: 1 | |
run: gcloud run deploy ${{ env.FRONTEND_CLOUD_RUN_SERVICE }} --image ${{ env.FRONTEND_ARTIFACT_REGISTRY_LOCATION }}-docker.pkg.dev/${{ vars.GCP_PROJECT_ID }}/${{ env.FRONTEND_ARTIFACT_REGISTRY }}/${{ env.FRONTEND_ARTIFACT_REGISTRY_CONTAINER_NAME }}:${{ github.sha }} --region ${{ env.FRONTEND_CLOUD_RUN_LOCATION }} --tag sha-${{ steps.commit_sha.outputs.shortSha }} | |
- name: Get GCP Credentials File | |
id: creds | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const fs = require('fs'); | |
const sa = fs.readFileSync('${{ steps.auth.outputs.credentials_file_path }}', { encoding: 'utf-8' }); | |
core.setOutput('cerdsJson', sa); | |
- name: Update Firebase Hosting | |
uses: FirebaseExtended/action-hosting-deploy@v0 | |
with: | |
repoToken: ${{ secrets.GITHUB_TOKEN }} | |
firebaseServiceAccount: ${{ steps.creds.outputs.cerdsJson }} | |
channelId: live | |
projectId: ${{ vars.GCP_PROJECT_ID }} | |
# END: Frontend |