Skip to content
Closed
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
99 changes: 20 additions & 79 deletions .github/workflows/deploy-functions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ concurrency:
jobs:
deploy:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

Expand All @@ -41,87 +40,29 @@ jobs:
exit 1
fi

- name: Set up Node.js for Firebase CLI
uses: actions/setup-node@v3
with:
node-version: '20'

- name: Install Firebase CLI
run: |
npm install -g firebase-tools
firebase --version

- name: Install uv package manager
uses: astral-sh/setup-uv@v5
with:
version: "0.6.4"

- name: Install Python 3.11 with uv
run: uv python install 3.11

- name: Use uv to install dependencies in /functions/venv
working-directory: functions
run: |
uv venv venv --python 3.11
source venv/bin/activate
uv pip install --upgrade pip
uv sync --active
uv pip freeze > requirements.txt
deactivate

# Store key in the runner's environment variable to persist across steps
- name: Authenticate with GCP service account key
env:
SERVICE_ACCOUNT_JSON: ${{ github.event.inputs.environment == 'prod' && secrets.FIREBASE_PROD_SERVICE_ACCOUNT || secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }}
run: |
echo "$SERVICE_ACCOUNT_JSON" > service-account.json
echo "GOOGLE_APPLICATION_CREDENTIALS=service-account.json" >> $GITHUB_ENV

- name: Authenticate GitHub Actions service account
run: |
gcloud auth activate-service-account --key-file=service-account.json

- name: Verify service account ADC (Debug)
- name: Prepare service account key
id: prepare
run: |
gcloud config get-value account
gcloud auth list

- name: Verify Enabled APIs (Debug)
run: |
gcloud services list --enabled --project=${{ github.event.inputs.environment == 'prod' && 'hello-wisdom-prod' || 'hello-wisdom-staging' }}

- name: List Firebase Projects (Debug Mode)
run: firebase projects:list --debug
if [[ "${{ github.event.inputs.environment }}" == "prod" ]]; then
service_account_b64="${{ secrets.FIREBASE_PROD_SERVICE_ACCOUNT_B64 }}"
else
service_account_b64="${{ secrets.FIREBASE_STAGING_SERVICE_ACCOUNT_B64 }}"
fi

- name: Verify Service Account IAM Policy (Debug)
run: |
PROJECT_ID=${{ github.event.inputs.environment == 'prod' && 'hello-wisdom-prod' || 'hello-wisdom-staging' }}
echo "Verifying IAM policy for project: $PROJECT_ID"
gcloud projects get-iam-policy $PROJECT_ID --format=json
if [[ -z "$service_account_b64" ]]; then
echo "::error::Missing service account secret"
exit 1
fi

- name: Select Firebase Project
id: select_project
run: |
if [ "${{ github.event.inputs.environment }}" = "prod" ]; then
firebase use prod --non-interactive
else
firebase use staging --non-interactive
if ! echo "$service_account_b64" | base64 --decode > /dev/null 2>&1; then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to add validation for base64 encoding here. Let me know what you think of this approach.

echo "::error::Service account secret is not valid base64. Please ensure it is base64-encoded before storing as a secret."
exit 1
fi

- name: Deploy Firebase Functions (Debug Mode)
run: firebase deploy --only functions --debug
echo "service_account_b64=$service_account_b64" >> $GITHUB_OUTPUT

- name: Post-deployment summary
env:
ENVIRONMENT: ${{ github.event.inputs.environment }}
TRIGGER: ${{ github.event_name }}
REF_NAME: ${{ github.ref_name }}
run: |
echo "### Deployment Summary 🚀" >> $GITHUB_STEP_SUMMARY
echo "* **Environment**: $ENVIRONMENT" >> $GITHUB_STEP_SUMMARY
echo "* **Trigger**: $TRIGGER" >> $GITHUB_STEP_SUMMARY
echo "* **Branch/Ref**: $REF_NAME" >> $GITHUB_STEP_SUMMARY

- name: Cleanup credentials
if: always()
run: rm -f service-account.json
- name: Deploy Firebase Functions
uses: ./
with:
project_id: ${{ github.event.inputs.environment == 'prod' && 'hello-wisdom-prod' || 'hello-wisdom-staging' }}
service_account_json_b64: ${{ steps.prepare.outputs.service_account_b64 }}
22 changes: 11 additions & 11 deletions .github/workflows/lint-pr-title.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
name: Lint PR Title
name: "Lint PR"

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read
pull_request_target:
types:
- opened
- edited
- synchronize
- reopened

jobs:
lint-pr-title:
name: Validate PR Title
main:
name: Validate PR title
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
Expand All @@ -33,6 +36,3 @@ jobs:
✅ feat: Add new deployment option
✅ fix(functions): Resolve authentication issue
❌ feat: add new deployment option
wip: true
validateSingleCommit: false
validateSingleCommitMatchesPrTitle: false
105 changes: 15 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,111 +4,36 @@ A GitHub Action for deploying Python-based Firebase Cloud Functions. This reposi

## Features

- 🐍 Automatic Python version detection from pyproject.toml
- 📦 Dependency management using UV package manager
- 🔄 Environment-based deployments (staging/prod)
- 🐍 Python dependency management from pyproject.toml
- 🔐 Secure handling of service account credentials
- 📝 Detailed deployment summaries
- 🧪 Self-testing repository structure

## Repository Structure

This repository is structured to serve two purposes:

1. Provide the composite action implementation
2. Serve as a live example and test environment

```
.
├── action.yml # The composite action definition
├── .github/workflows/ # Contains workflow using the action
│ └── deploy-functions.yml
├── functions/ # Example Firebase Functions
│ ├── main.py
│ └── pyproject.toml
└── README.md # Documentation
```
- Detailed deployment summaries

## Usage

The workflow in this repository (.github/workflows/deploy-functions.yml) demonstrates the recommended usage:

```yaml
name: Test & Deploy Python Functions

on:
push:
branches: [staging, prod]
workflow_dispatch:
inputs:
environment:
type: choice
options: [staging, prod]
description: 'Environment to deploy to'
required: true

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: ./ # Uses local action for testing
with:
environment: ${{ github.event_name == 'push' && github.ref_name || inputs.environment }}
service_account_json: ${{ secrets[format('FIREBASE_{0}_SERVICE_ACCOUNT', github.event_name == 'push' && github.ref_name || inputs.environment)] }}
project_id: ${{ vars[format('FIREBASE_{0}_PROJECT_ID', github.event_name == 'push' && github.ref_name || inputs.environment)] }}
```

When using in your own repository, reference a specific version:
Basic usage:

```yaml
- uses: digital-wisdom/deploy-firebase-python@v1
with:
functions_dir: 'src/functions' # Default is 'functions'
environment: staging
service_account_json: ${{ secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }}
project_id: my-project-staging
project_id: my-firebase-project
service_account_json_b64: ${{ inputs.service_account_b64 }} # Must be base64 encoded
```

The action requires the service account JSON to be base64 encoded. How you provide this encoded value is up to your workflow design.

## Inputs

| Input | Description | Required | Default |
| ---------------------- | ------------------------------------------------- | -------- | ----------- |
| `functions_dir` | Directory containing functions and pyproject.toml | No | `functions` |
| `environment` | Environment to deploy to (staging/prod) | Yes | N/A |
| `service_account_json` | Firebase service account JSON | Yes | N/A |
| `project_id` | Firebase project ID | Yes | N/A |
| Input | Description | Required | Default |
| -------------------------- | ------------------------------------------------- | -------- | ----------- |
| `functions_dir` | Directory containing functions and pyproject.toml | No | `functions` |
| `to_deploy` | Firebase resource to deploy (e.g. functions) | No | `functions` |
| `project_id` | Firebase project ID | Yes | N/A |
| `service_account_json_b64` | Base64-encoded Firebase service account JSON | Yes | N/A |

## Prerequisites

1. **Firebase Project Setup**

- Create Firebase projects for your environments
- Generate service account keys
- Store service account JSON in GitHub Secrets
- Store project IDs in GitHub Variables

2. **Python Project Structure**
- Valid pyproject.toml in your functions directory
- Python version specified in requires-python
- Dependencies listed in project dependencies

## Project Structure

Your project should look something like this:

```
.
├── .github
│ └── workflows
│ └── deploy.yml
├── functions
│ ├── main.py
│ ├── pyproject.toml
│ └── other_files.py
└── firebase.json
```
- Firebase service account key
- Python project with pyproject.toml in functions directory

## Environment Variables

Expand Down
21 changes: 5 additions & 16 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,12 @@ inputs:
description: 'Firebase resource to deploy (e.g. functions, hosting)'
required: false
default: 'functions'
environment:
description: 'Environment to deploy to'
required: true
type: choice
options:
- staging
- prod
service_account_json:
description: 'Firebase service account JSON'
required: true
project_id:
description: 'Firebase project ID'
required: true
service_account_json_b64:
description: 'Firebase service account JSON (base64 encoded)'
required: true

runs:
using: 'composite'
Expand Down Expand Up @@ -57,13 +50,10 @@ runs:
- name: Authenticate with GCP service account key
shell: bash
run: |
echo "${{ inputs.service_account_json }}" > service-account.json
echo '${{ inputs.service_account_json_b64 }}' | base64 -d | jq '.' > service-account.json
echo "GOOGLE_APPLICATION_CREDENTIALS=service-account.json" >> $GITHUB_ENV

- name: Authenticate GitHub Actions service account
shell: bash
run: |
gcloud auth activate-service-account --key-file=service-account.json
rm -f service-account.json

- name: Verify service account ADC (Debug)
shell: bash
Expand Down Expand Up @@ -100,7 +90,6 @@ runs:
shell: bash
run: |
echo "### Deployment Summary 🚀" >> $GITHUB_STEP_SUMMARY
echo "* **Environment**: ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "* **Project**: ${{ inputs.project_id }}" >> $GITHUB_STEP_SUMMARY
echo "* **Resources**: ${{ inputs.to_deploy }}" >> $GITHUB_STEP_SUMMARY
echo "* **Functions Directory**: ${{ inputs.functions_dir }}" >> $GITHUB_STEP_SUMMARY
Expand Down
Loading