From 7751f001bd32860f8b9553c31bb8b1e1a88f7ae2 Mon Sep 17 00:00:00 2001 From: Kelly Johnson Date: Sat, 19 Apr 2025 23:44:40 -0700 Subject: [PATCH 1/9] feat: Use shared action for local deploy --- .github/workflows/deploy-functions.yml | 89 ++------------------------ 1 file changed, 5 insertions(+), 84 deletions(-) diff --git a/.github/workflows/deploy-functions.yml b/.github/workflows/deploy-functions.yml index 89d0e6d..f02ed27 100644 --- a/.github/workflows/deploy-functions.yml +++ b/.github/workflows/deploy-functions.yml @@ -29,7 +29,6 @@ concurrency: jobs: deploy: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 @@ -41,87 +40,9 @@ 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 + - name: Deploy Firebase Functions + uses: ./ 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) - 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 - - - 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 - - - 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 - fi - - - name: Deploy Firebase Functions (Debug Mode) - run: firebase deploy --only functions --debug - - - 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 \ No newline at end of file + environment: ${{ github.event.inputs.environment }} + service_account_json: ${{ github.event.inputs.environment == 'prod' && secrets.FIREBASE_PROD_SERVICE_ACCOUNT || secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }} + project_id: ${{ github.event.inputs.environment == 'prod' && 'hello-wisdom-prod' || 'hello-wisdom-staging' }} From 290d44811c528ac9bd99fcf9f0ae7c78685ec0d1 Mon Sep 17 00:00:00 2001 From: Kelly Johnson Date: Sat, 19 Apr 2025 23:49:41 -0700 Subject: [PATCH 2/9] Fix lint permissions --- .github/workflows/lint-pr-title.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 0d1bf14..0a9e6fd 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -6,6 +6,7 @@ on: permissions: pull-requests: read + contents: read jobs: lint-pr-title: From 84540346c0acd3ea74f09d65b92289462834ccf5 Mon Sep 17 00:00:00 2001 From: Kelly Johnson Date: Sat, 19 Apr 2025 23:58:58 -0700 Subject: [PATCH 3/9] Fix lint --- .github/workflows/lint-pr-title.yml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 0a9e6fd..272930e 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -1,17 +1,19 @@ -name: Lint PR Title +name: "Lint PR" on: - pull_request: - types: [opened, edited, synchronize, reopened] - -permissions: - pull-requests: read - contents: 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: @@ -34,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 From 9dc3c475402645006019d9538e8bb0b7c5e60087 Mon Sep 17 00:00:00 2001 From: Kelly Johnson Date: Sun, 20 Apr 2025 00:13:33 -0700 Subject: [PATCH 4/9] Fix deploy translation error --- .github/workflows/deploy-functions.yml | 1 - README.md | 18 ++++++++++-------- action.yml | 7 +++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/deploy-functions.yml b/.github/workflows/deploy-functions.yml index f02ed27..aebeeff 100644 --- a/.github/workflows/deploy-functions.yml +++ b/.github/workflows/deploy-functions.yml @@ -44,5 +44,4 @@ jobs: uses: ./ with: environment: ${{ github.event.inputs.environment }} - service_account_json: ${{ github.event.inputs.environment == 'prod' && secrets.FIREBASE_PROD_SERVICE_ACCOUNT || secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }} project_id: ${{ github.event.inputs.environment == 'prod' && 'hello-wisdom-prod' || 'hello-wisdom-staging' }} diff --git a/README.md b/README.md index f95eabc..94ed323 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ jobs: - 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)] }} ``` @@ -67,18 +66,21 @@ When using in your own repository, reference a specific version: with: functions_dir: 'src/functions' # Default is 'functions' environment: staging - service_account_json: ${{ secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }} project_id: my-project-staging ``` +Note: The action requires the following secrets to be defined in your repository: + +- `FIREBASE_STAGING_SERVICE_ACCOUNT`: Service account JSON for staging environment +- `FIREBASE_PROD_SERVICE_ACCOUNT`: Service account JSON for production environment + ## 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` | +| `environment` | Environment to deploy to (staging/prod) | Yes | N/A | +| `project_id` | Firebase project ID | Yes | N/A | ## Prerequisites diff --git a/action.yml b/action.yml index d50ed09..9bd720c 100644 --- a/action.yml +++ b/action.yml @@ -22,9 +22,6 @@ inputs: options: - staging - prod - service_account_json: - description: 'Firebase service account JSON' - required: true project_id: description: 'Firebase project ID' required: true @@ -56,8 +53,10 @@ runs: - name: Authenticate with GCP service account key shell: bash + env: + SERVICE_ACCOUNT_JSON: ${{ inputs.environment == 'prod' && secrets.FIREBASE_PROD_SERVICE_ACCOUNT || secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }} run: | - echo "${{ inputs.service_account_json }}" > service-account.json + echo "$SERVICE_ACCOUNT_JSON" > service-account.json echo "GOOGLE_APPLICATION_CREDENTIALS=service-account.json" >> $GITHUB_ENV - name: Authenticate GitHub Actions service account From cbcdd5a73c76a81fb0b383d5964bf42237a4ec9f Mon Sep 17 00:00:00 2001 From: Kelly Johnson Date: Sun, 20 Apr 2025 08:58:29 -0700 Subject: [PATCH 5/9] Improvements --- .github/workflows/deploy-functions.yml | 11 ++- README.md | 105 ++++--------------------- action.yml | 20 ++--- 3 files changed, 29 insertions(+), 107 deletions(-) diff --git a/.github/workflows/deploy-functions.yml b/.github/workflows/deploy-functions.yml index aebeeff..bec907e 100644 --- a/.github/workflows/deploy-functions.yml +++ b/.github/workflows/deploy-functions.yml @@ -40,8 +40,17 @@ jobs: exit 1 fi + - name: Prepare service account + id: prepare + run: | + if [[ "${{ github.event.inputs.environment }}" == "prod" ]]; then + echo "service_account_b64=$(echo '${{ secrets.FIREBASE_PROD_SERVICE_ACCOUNT }}' | base64)" >> $GITHUB_OUTPUT + else + echo "service_account_b64=$(echo '${{ secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }}' | base64)" >> $GITHUB_OUTPUT + fi + - name: Deploy Firebase Functions uses: ./ with: - environment: ${{ github.event.inputs.environment }} project_id: ${{ github.event.inputs.environment == 'prod' && 'hello-wisdom-prod' || 'hello-wisdom-staging' }} + service_account_json_b64: ${{ steps.prepare.outputs.service_account_b64 }} diff --git a/README.md b/README.md index 94ed323..33a35b7 100644 --- a/README.md +++ b/README.md @@ -4,113 +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 }} - 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 - project_id: my-project-staging + project_id: my-firebase-project + service_account_json_b64: ${{ inputs.service_account_b64 }} # Must be base64 encoded ``` -Note: The action requires the following secrets to be defined in your repository: - -- `FIREBASE_STAGING_SERVICE_ACCOUNT`: Service account JSON for staging environment -- `FIREBASE_PROD_SERVICE_ACCOUNT`: Service account JSON for production environment +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 | -| `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 diff --git a/action.yml b/action.yml index 9bd720c..700d9ca 100644 --- a/action.yml +++ b/action.yml @@ -15,16 +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 project_id: description: 'Firebase project ID' required: true + service_account_json_b64: + description: 'Firebase service account JSON (base64 encoded)' + required: true runs: using: 'composite' @@ -53,16 +49,11 @@ runs: - name: Authenticate with GCP service account key shell: bash - env: - SERVICE_ACCOUNT_JSON: ${{ inputs.environment == 'prod' && secrets.FIREBASE_PROD_SERVICE_ACCOUNT || secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }} run: | - echo "$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 @@ -99,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 From 68166075d8dafa7de7a0f51a2816b2131e8d3020 Mon Sep 17 00:00:00 2001 From: Kelly Johnson Date: Sun, 20 Apr 2025 09:16:10 -0700 Subject: [PATCH 6/9] Try to fix formatting --- .github/workflows/deploy-functions.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-functions.yml b/.github/workflows/deploy-functions.yml index bec907e..3a8dcd5 100644 --- a/.github/workflows/deploy-functions.yml +++ b/.github/workflows/deploy-functions.yml @@ -44,11 +44,19 @@ jobs: id: prepare run: | if [[ "${{ github.event.inputs.environment }}" == "prod" ]]; then - echo "service_account_b64=$(echo '${{ secrets.FIREBASE_PROD_SERVICE_ACCOUNT }}' | base64)" >> $GITHUB_OUTPUT + sa_json="${{ secrets.FIREBASE_PROD_SERVICE_ACCOUNT }}" else - echo "service_account_b64=$(echo '${{ secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }}' | base64)" >> $GITHUB_OUTPUT + sa_json="${{ secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }}" fi + if [[ -z "$sa_json" ]]; then + echo "::error::Missing service account secret" + exit 1 + fi + + service_account_b64=$(printf '%s' "$sa_json" | base64 | tr -d '\n') + echo "service_account_b64=$service_account_b64" >> $GITHUB_OUTPUT + - name: Deploy Firebase Functions uses: ./ with: From 9bbfc6facd0b70f3376232e9296078056f7af1ae Mon Sep 17 00:00:00 2001 From: Angelita Garcia <136642765+aquitzia@users.noreply.github.com> Date: Mon, 21 Apr 2025 16:45:19 -0700 Subject: [PATCH 7/9] fix: Validate and update b64 secrets --- .github/workflows/deploy-functions.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-functions.yml b/.github/workflows/deploy-functions.yml index 3a8dcd5..f65cc01 100644 --- a/.github/workflows/deploy-functions.yml +++ b/.github/workflows/deploy-functions.yml @@ -40,21 +40,25 @@ jobs: exit 1 fi - - name: Prepare service account + - name: Prepare service account key id: prepare run: | if [[ "${{ github.event.inputs.environment }}" == "prod" ]]; then - sa_json="${{ secrets.FIREBASE_PROD_SERVICE_ACCOUNT }}" + service_account_b64="${{ secrets.FIREBASE_PROD_SERVICE_ACCOUNT_B64 }}" else - sa_json="${{ secrets.FIREBASE_STAGING_SERVICE_ACCOUNT }}" + service_account_b64="${{ secrets.FIREBASE_STAGING_SERVICE_ACCOUNT_B64 }}" fi - if [[ -z "$sa_json" ]]; then + if [[ -z "$service_account_b64" ]]; then echo "::error::Missing service account secret" exit 1 fi - service_account_b64=$(printf '%s' "$sa_json" | base64 | tr -d '\n') + if ! echo "$service_account_b64" | base64 --decode > /dev/null 2>&1; then + echo "::error::Service account secret is not valid base64. Please ensure it is base64-encoded before storing as a secret." + exit 1 + fi + echo "service_account_b64=$service_account_b64" >> $GITHUB_OUTPUT - name: Deploy Firebase Functions From 606f06192f77d1410524d29eebb95eeb5b39bd51 Mon Sep 17 00:00:00 2001 From: Angelita Garcia <136642765+aquitzia@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:08:45 -0700 Subject: [PATCH 8/9] fix: Premature deletion of service-account.json --- action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/action.yml b/action.yml index 700d9ca..dfbacc4 100644 --- a/action.yml +++ b/action.yml @@ -53,7 +53,6 @@ runs: echo '${{ inputs.service_account_json_b64 }}' | base64 -d | jq '.' > service-account.json echo "GOOGLE_APPLICATION_CREDENTIALS=service-account.json" >> $GITHUB_ENV gcloud auth activate-service-account --key-file=service-account.json - rm -f service-account.json - name: Verify service account ADC (Debug) shell: bash From c3301c433f02ea787dac73006d6c53255a0a83ae Mon Sep 17 00:00:00 2001 From: Angelita Garcia <136642765+aquitzia@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:26:11 -0700 Subject: [PATCH 9/9] fix: clean up credentials after gcloud auth --- action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yml b/action.yml index dfbacc4..700d9ca 100644 --- a/action.yml +++ b/action.yml @@ -53,6 +53,7 @@ runs: echo '${{ inputs.service_account_json_b64 }}' | base64 -d | jq '.' > service-account.json echo "GOOGLE_APPLICATION_CREDENTIALS=service-account.json" >> $GITHUB_ENV gcloud auth activate-service-account --key-file=service-account.json + rm -f service-account.json - name: Verify service account ADC (Debug) shell: bash