Skip to content

Commit 632c5b4

Browse files
Merge pull request #1 from digital-wisdom-ai/add-deploy-workflow
Update deployment workflow
2 parents 2fe7948 + b9fa8e2 commit 632c5b4

File tree

9 files changed

+1291
-1
lines changed

9 files changed

+1291
-1
lines changed

.firebaserc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"projects": {
3+
"default": "hello-wisdom-staging",
4+
"staging": "hello-wisdom-staging",
5+
"prod": "hello-wisdom-prod"
6+
},
7+
"targets": {},
8+
"etags": {}
9+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
name: Deploy Python Firebase Functions
2+
3+
on:
4+
push:
5+
branches:
6+
- add-deploy-workflow
7+
workflow_dispatch:
8+
inputs:
9+
environment:
10+
description: 'Environment to deploy to'
11+
required: true
12+
default: 'staging'
13+
type: choice
14+
options:
15+
- staging
16+
- prod
17+
confirm_prod:
18+
description: 'Type "yes" to confirm production deployment'
19+
required: false
20+
type: string
21+
22+
permissions:
23+
contents: read
24+
25+
concurrency:
26+
group: deploy-${{ github.ref }}
27+
cancel-in-progress: false
28+
29+
jobs:
30+
deploy:
31+
runs-on: ubuntu-latest
32+
33+
steps:
34+
- uses: actions/checkout@v4
35+
36+
- name: Validate production deployment
37+
if: ${{ github.event.inputs.environment == 'prod' }}
38+
run: |
39+
if [[ "${{ github.event.inputs.confirm_prod }}" != "yes" ]]; then
40+
echo "::error::Production deployment requires explicit confirmation. Please type 'yes' in the confirm_prod field."
41+
exit 1
42+
fi
43+
44+
- name: Set up Node.js for Firebase CLI
45+
uses: actions/setup-node@v3
46+
with:
47+
node-version: '20'
48+
49+
- name: Install Firebase CLI
50+
run: |
51+
npm install -g firebase-tools
52+
firebase --version
53+
54+
- name: Install uv package manager
55+
uses: astral-sh/setup-uv@v5
56+
with:
57+
version: "0.6.4"
58+
59+
- name: Install Python 3.11 with uv
60+
run: uv python install 3.11
61+
62+
- name: Use uv to install dependencies in /functions/venv
63+
working-directory: functions
64+
run: |
65+
uv venv venv --python 3.11
66+
source venv/bin/activate
67+
uv pip install --upgrade pip
68+
uv sync --active
69+
uv pip freeze > requirements.txt
70+
deactivate
71+
72+
# Store key in the runner's environment variable to persist across steps
73+
- name: Authenticate with GCP service account key
74+
env:
75+
SERVICE_ACCOUNT_JSON: ${{ github.event.inputs.environment == 'prod' && secrets.FIREBASE_PROD_SERVICE_ACCOUNT || secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }}
76+
run: |
77+
echo "$SERVICE_ACCOUNT_JSON" > service-account.json
78+
echo "GOOGLE_APPLICATION_CREDENTIALS=service-account.json" >> $GITHUB_ENV
79+
80+
- name: Authenticate GitHub Actions service account
81+
run: |
82+
gcloud auth activate-service-account --key-file=service-account.json
83+
84+
- name: Verify service account ADC (Debug)
85+
run: |
86+
gcloud config get-value account
87+
gcloud auth list
88+
89+
- name: Verify Enabled APIs (Debug)
90+
run: |
91+
gcloud services list --enabled --project=${{ github.event.inputs.environment == 'prod' && 'hello-wisdom-prod' || 'hello-wisdom-staging' }}
92+
93+
- name: List Firebase Projects (Debug Mode)
94+
run: firebase projects:list --debug
95+
96+
- name: Verify Service Account IAM Policy (Debug)
97+
run: |
98+
PROJECT_ID=${{ github.event.inputs.environment == 'prod' && 'hello-wisdom-prod' || 'hello-wisdom-staging' }}
99+
echo "Verifying IAM policy for project: $PROJECT_ID"
100+
gcloud projects get-iam-policy $PROJECT_ID --format=json
101+
102+
- name: Select Firebase Project
103+
id: select_project
104+
run: |
105+
if [ "${{ github.event.inputs.environment }}" = "prod" ]; then
106+
firebase use prod --non-interactive
107+
else
108+
firebase use staging --non-interactive
109+
fi
110+
111+
- name: Deploy Firebase Functions (Debug Mode)
112+
run: firebase deploy --only functions --debug
113+
114+
- name: Post-deployment summary
115+
env:
116+
ENVIRONMENT: ${{ github.event.inputs.environment }}
117+
TRIGGER: ${{ github.event_name }}
118+
REF_NAME: ${{ github.ref_name }}
119+
run: |
120+
echo "### Deployment Summary 🚀" >> $GITHUB_STEP_SUMMARY
121+
echo "* **Environment**: $ENVIRONMENT" >> $GITHUB_STEP_SUMMARY
122+
echo "* **Trigger**: $TRIGGER" >> $GITHUB_STEP_SUMMARY
123+
echo "* **Branch/Ref**: $REF_NAME" >> $GITHUB_STEP_SUMMARY
124+
125+
- name: Cleanup credentials
126+
if: always()
127+
run: rm -f service-account.json

.gitignore

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
.DS_Store
2+
3+
# Logs
4+
logs
5+
*.log
6+
npm-debug.log*
7+
yarn-debug.log*
8+
yarn-error.log*
9+
firebase-debug.log*
10+
firebase-debug.*.log*
11+
12+
# Firebase cache
13+
.firebase/
14+
15+
# Firebase config
16+
17+
# Uncomment this if you'd like others to create their own Firebase project.
18+
# For a team working on the same Firebase project(s), it is recommended to leave
19+
# it commented so all members can deploy to the same project(s) in .firebaserc.
20+
# .firebaserc
21+
22+
# Runtime data
23+
pids
24+
*.pid
25+
*.seed
26+
*.pid.lock
27+
28+
# Directory for instrumented libs generated by jscoverage/JSCover
29+
lib-cov
30+
31+
# Coverage directory used by tools like istanbul
32+
coverage
33+
34+
# nyc test coverage
35+
.nyc_output
36+
37+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
38+
.grunt
39+
40+
# Bower dependency directory (https://bower.io/)
41+
bower_components
42+
43+
# node-waf configuration
44+
.lock-wscript
45+
46+
# Compiled binary addons (http://nodejs.org/api/addons.html)
47+
build/Release
48+
49+
# Dependency directories
50+
node_modules/
51+
52+
# Optional npm cache directory
53+
.npm
54+
55+
# Optional eslint cache
56+
.eslintcache
57+
58+
# Optional REPL history
59+
.node_repl_history
60+
61+
# Output of 'npm pack'
62+
*.tgz
63+
64+
# Yarn Integrity file
65+
.yarn-integrity
66+
67+
# dotenv environment variables file
68+
.env
69+
70+
# dataconnect generated files
71+
.dataconnect

README.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,65 @@
1-
# firebase_deploy_python
1+
# Deploy Python Firebase Functions with GitHub Actions
2+
This project contains a sample workflow for automating the deployment of a Python Firebase Function with GitHub Actions. The Firebase files and Python code deploy a simple hello world function with Flask as a Firebase serverless Function. Note that Python 3.11 is the latest version supported by Python Firebase Functions v2 as of April 2025.
3+
4+
# GitHub Actions Trigger
5+
The action is triggered from any push to the feature branch `add-deploy-workflow` or a manual trigger. The default for the feature branch is `staging`. The manual trigger allows the user to select a GitHub branch and Firebase alias `staging`/`prod`.
6+
7+
# Python package manager
8+
The project uses `uv`, a modern package manager, which creates a virtual environment named `.venv`. Firebase expects a virtual environment named `venv` with `pip` installed. We can use `uv` commands to do so. Note that we are running `uv sync` with the `--active` flag because we need to maintain two virtual environments: `venv` for Firebase and the default `.venv` for uv.
9+
10+
# GCP Service Account
11+
Deployment depends upon a Google Cloud service account for authentication. Set this up in the GCP console by creating a dedicated service account for GitHub Actions. You'll need to do this for each Firebase project.
12+
13+
GCP console > IAM & Admin > Service Accounts > Create Service Account
14+
Name: github-actions-deploy-staging
15+
Description: Deploy to Firebase with GitHub Actions
16+
Roles:
17+
- Firebase Admin SDK Admin
18+
- Cloud Functions Admin
19+
- Service Account User
20+
Skip granting users access and click Done.
21+
22+
Alternatively, use `gcloud` CLI for each project:
23+
24+
`gcloud config set project PROJECT_ID`
25+
26+
`gcloud iam service-accounts create github-actions-deploy \`
27+
` --description="Deploy to Firebase project with GitHub Actions" \`
28+
29+
`gcloud projects add-iam-policy-binding $(gcloud config get-value project) \`
30+
` --member="serviceAccount:github-actions-deploy@$(gcloud config get-value project).iam.gserviceaccount.com" \`
31+
` --role="roles/firebase.admin"`
32+
33+
`gcloud projects add-iam-policy-binding $(gcloud config get-value project) \`
34+
` --member="serviceAccount:github-actions-deploy@$(gcloud config get-value project).iam.gserviceaccount.com" \`
35+
` --role="roles/cloudfunctions.admin"`
36+
37+
`gcloud projects add-iam-policy-binding $(gcloud config get-value project) \`
38+
` --member="serviceAccount:github-actions-deploy@$(gcloud config get-value project).iam.gserviceaccount.com" \`
39+
` --role="roles/iam.serviceAccountUser"`
40+
41+
`gcloud projects add-iam-policy-binding $(gcloud config get-value project) \`
42+
` --member="serviceAccount:github-actions-deploy@$(gcloud config get-value project).iam.gserviceaccount.com" \`
43+
` --role="roles/serviceusage.serviceUsageConsumer"`
44+
45+
`gcloud projects add-iam-policy-binding $(gcloud config get-value project) \`
46+
` --member="serviceAccount:github-actions-deploy@$(gcloud config get-value project).iam.gserviceaccount.com" \`
47+
` --role="roles/cloudruntimeconfig.admin"`
48+
49+
# GCP Authentication Keys
50+
Each Firebase project requires a service account key. Download keys from Google and remember to exclude them from version control.
51+
52+
Store the keys as secrets in your repo:
53+
GitHub repo → Settings → Secrets and variables → Actions
54+
FIREBASE_PROD_SERVICE_ACCOUNT
55+
FIREBASE_STAGING_SERVICE_ACCOUNT
56+
57+
Alternatively, use GitHub CLI:
58+
`gh secret set FIREBASE_STAGING_SERVICE_ACCOUNT --repo user/repo-name < key-staging.json`
59+
`gh secret set FIREBASE_PROD_SERVICE_ACCOUNT --repo user/repo-name < key-prod.json`
60+
61+
# Firebase CLI
62+
It’s important to use the latest version of `firebase-tools`, which will automatically enable the correct APIs for the Python Firebase Function in the corresponding GCP project.
63+
64+
# Debugging and Credential Verification
65+
The steps labeled (Debug) are for debugging, verifying credentials, and checking settings, so they can be omitted. The `firebase deploy` command uses a `--debug` flag that isn't strictly necessary. The GitHub Actions workflow would run faster without the debugging steps and flags.

firebase.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"functions": [
3+
{
4+
"source": "functions",
5+
"runtime": "python311",
6+
"codebase": "default"
7+
}
8+
],
9+
"emulators": {
10+
"functions": {
11+
"port": 5001
12+
},
13+
"auth": {
14+
"port": 9099
15+
},
16+
"ui": {
17+
"enabled": true
18+
},
19+
"singleProjectMode": true
20+
}
21+
}

functions/.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
*.local
2+
.python-version
3+
4+
# Python
5+
*.pyc
6+
*.pyo
7+
__pycache__/
8+
venv/
9+
10+
# pyproject.toml is the source of truth
11+
requirements.txt
12+
13+
# uv
14+
.uv/
15+
.venv/
16+
17+
# Firebase
18+
firebase-debug.log
19+
firebase-debug.*.log
20+
.runtimeconfig.json
21+
.firebase/
22+
23+
*.log
24+
.DS_Store
25+
Thumbs.db
26+
.vscode/
27+
.idea/
28+
dist/
29+
build/
30+
node_modules/
31+
32+
# As a failsafe
33+
.env
34+
service-account.json

functions/main.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Welcome to Cloud Functions for Firebase for Python!
2+
# Deploy with `firebase deploy`
3+
4+
from firebase_functions import https_fn #version 0.1.2
5+
from flask import Flask #version 3.1.0
6+
7+
app = Flask(__name__)
8+
9+
@app.route('/')
10+
def hello_dw():
11+
return "Hello, Digital Wisdom!"
12+
13+
@https_fn.on_request(
14+
memory=512,
15+
timeout_sec=60,
16+
max_instances=1
17+
)
18+
def simple_function(req: https_fn.Request) -> https_fn.Response:
19+
with app.request_context(req.environ):
20+
return app.full_dispatch_request()

functions/pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[project]
2+
name = "functions"
3+
version = "0.1.0"
4+
requires-python = ">=3.10,<3.12" # Firebase Functions support 3.10 and 3.11
5+
dependencies = [
6+
"firebase-admin>=6.7.0",
7+
"firebase-functions>=0.1.2",
8+
"flask>=3.1.0",
9+
]

0 commit comments

Comments
 (0)