Skip to content

updating readme

updating readme #17

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