Skip to main content

Infrastructure as Code for Google Cloud Firestore

Project description

FireSync

Tests Coverage Python 3.8-3.14 License: MIT Platform Code style: black CodeQL TruffleHog

Infrastructure as Code for Google Cloud Firestore

FireSync is a lightweight Python tool that brings version control and deployment automation to your Firestore database schema. Manage composite indexes, single-field indexes, and TTL policies using a familiar pull-plan-apply workflow inspired by Terraform.

Table of Contents

Features

  • ๐Ÿ”„ Version Control - Track Firestore schema changes in git
  • ๐Ÿ” Plan Before Apply - Preview changes before deploying
  • ๐Ÿ›ก๏ธ Safety First - Idempotent operations, no accidental deletions
  • ๐Ÿ”‘ Flexible Auth - Use any GCP service account key
  • ๐Ÿ Zero Dependencies - Pure Python stdlib, just needs gcloud CLI
  • ๐ŸชŸ Cross-Platform - Works on Linux, macOS, and Windows

Quick Start

Prerequisites

  • Python 3.8+
  • Google Cloud SDK (gcloud CLI)
  • GCP service account with Firestore permissions:
    • datastore.indexes.list
    • datastore.indexes.create
    • datastore.indexes.update
    • firebase.projects.get

Installation

git clone https://github.com/yourusername/firesync.git
cd firesync
chmod +x firesync

Or install as a Python package:

pip3 install -e .

All Commands

Get help on any command:

./firesync --help
./firesync pull --help
./firesync plan --help
./firesync apply --help
./firesync env --help

Available Commands:

  • firesync init - Initialize workspace with config.yaml
  • firesync env add <name> - Add environment to workspace
  • firesync env remove <name> - Remove environment from workspace
  • firesync env list - List all environments
  • firesync env show <name> - Show environment details
  • firesync pull - Export Firestore schema to local files
  • firesync plan - Compare local vs remote schemas
  • firesync apply - Deploy local schema to Firestore

Quick Setup

  1. Initialize workspace:
./firesync init
  1. Add your environments:
./firesync env add dev --key-path=./secrets/gcp-key-dev.json --description="Development environment"
./firesync env add staging --key-path=./secrets/gcp-key-staging.json --description="Staging environment"
./firesync env add prod --key-env=GCP_PROD_KEY --description="Production environment"
  1. Pull schemas from all environments:
./firesync pull --all

Commands Overview

firesync init

Initialize a new FireSync workspace. Creates config.yaml to manage multiple environments.

./firesync init

Creates:

  • config.yaml - Workspace configuration
  • firestore_schema/ - Default schema directory

firesync env

Manage environments in your workspace.

Add environment:

# Using key file path
./firesync env add dev --key-path=./secrets/gcp-key-dev.json

# Using key file path with description
./firesync env add staging --key-path=./secrets/gcp-key-staging.json --description="Staging environment"

# Using environment variable
./firesync env add prod --key-env=GCP_PROD_KEY --description="Production environment"

List environments:

./firesync env list
# Shows environment names with key paths (including absolute paths) or key env variables
# Example output:
#   โ€ข dev
#     key_path: secrets/gcp-key-dev.json - Development environment (/absolute/path/to/secrets/gcp-key-dev.json)
#   โ€ข prod
#     key_env: GCP_PROD_KEY - Production environment

Show environment details:

./firesync env show dev

Remove environment:

./firesync env remove dev

firesync pull

Export Firestore schema from GCP to local JSON files.

Pull all environments:

./firesync pull --all

Pull single environment:

./firesync pull --env=dev

Creates three JSON files in schema directory:

  • composite-indexes.json - Composite indexes
  • field-indexes.json - Single-field indexes
  • ttl-policies.json - TTL policies

firesync plan

Compare local schema against remote Firestore and preview changes.

Compare local vs remote:

./firesync plan --env=dev

Migration mode - compare two environments (local vs local):

./firesync plan --env-from=dev --env-to=staging

With custom schema directory:

./firesync plan --env=dev --schema-dir=custom_schemas

Output shows:

  • [+] WILL CREATE - Resource exists locally but not remotely
  • [-] WILL DELETE - Resource exists remotely but not locally
  • [~] WILL UPDATE - Resource differs between local and remote
  • [~] No changes - Schemas are in sync

firesync apply

Deploy local schema to Firestore.

Apply to environment:

./firesync apply --env=dev

Migration mode - apply source schema to target environment:

./firesync apply --env-from=dev --env-to=staging

With custom schema directory:

./firesync apply --env=prod --schema-dir=custom_schemas

Note: Apply operations are idempotent and skip existing resources. Delete operations are not implemented for safety.

Workspace Configuration

FireSync uses config.yaml to manage multiple environments with separate schemas.

Workspace Structure

your-project/
โ”œโ”€โ”€ config.yaml                    # Workspace configuration
โ”œโ”€โ”€ firestore_schema/              # Schema directories per environment
โ”‚   โ”œโ”€โ”€ dev/
โ”‚   โ”‚   โ”œโ”€โ”€ composite-indexes.json
โ”‚   โ”‚   โ”œโ”€โ”€ field-indexes.json
โ”‚   โ”‚   โ””โ”€โ”€ ttl-policies.json
โ”‚   โ”œโ”€โ”€ staging/
โ”‚   โ”‚   โ”œโ”€โ”€ composite-indexes.json
โ”‚   โ”‚   โ”œโ”€โ”€ field-indexes.json
โ”‚   โ”‚   โ””โ”€โ”€ ttl-policies.json
โ”‚   โ””โ”€โ”€ prod/
โ”‚       โ”œโ”€โ”€ composite-indexes.json
โ”‚       โ”œโ”€โ”€ field-indexes.json
โ”‚       โ””โ”€โ”€ ttl-policies.json
โ””โ”€โ”€ secrets/                       # GCP service account keys (gitignored)
    โ”œโ”€โ”€ gcp-key-dev.json
    โ”œโ”€โ”€ gcp-key-staging.json
    โ””โ”€โ”€ gcp-key-prod.json

config.yaml Format

version: 1
schema_base_dir: firestore_schema

environments:
  dev:
    key_path: secrets/gcp-key-dev.json
    schema_dir: dev
  staging:
    key_path: secrets/gcp-key-staging.json
    schema_dir: staging
  prod:
    key_env: GCP_PROD_KEY
    schema_dir: prod

Fields:

  • version - Config format version (always 1)
  • schema_base_dir - Base directory for all schema files
  • environments - Map of environment configurations
    • key_path - Path to GCP service account key (relative to config.yaml)
    • key_env - Name of environment variable containing key JSON
    • schema_dir - Schema directory name (relative to schema_base_dir)

Usage Examples

Basic Workflow

# 1. Initialize workspace
./firesync init

# 2. Add environment
./firesync env add dev --key-path=./secrets/gcp-key-dev.json --description="Development environment"

# 3. Pull current schema
./firesync pull --env=dev

# 4. Edit schema files (add/modify indexes)
vim firestore_schema/dev/composite-indexes.json

# 5. Preview changes
./firesync plan --env=dev

# 6. Apply changes
./firesync apply --env=dev

# 7. Commit to git
git add config.yaml firestore_schema/
git commit -m "Add user query index"

Multi-Environment Management

Manage dev, staging, and production environments separately:

# Set up all environments
./firesync init
./firesync env add dev --key-path=./secrets/gcp-key-dev.json --description="Development environment"
./firesync env add staging --key-path=./secrets/gcp-key-staging.json --description="Staging environment"
./firesync env add prod --key-env=GCP_PROD_KEY --description="Production environment"

# Pull schemas from all environments
./firesync pull --all

# Work on dev environment
vim firestore_schema/dev/composite-indexes.json
./firesync plan --env=dev
./firesync apply --env=dev

# Promote dev schema to staging
# --env-from: source environment (reads LOCAL schema files from firestore_schema/dev/)
# --env-to: target environment (applies to REMOTE Firestore in staging project)
./firesync plan --env-from=dev --env-to=staging
./firesync apply --env-from=dev --env-to=staging

# IMPORTANT: Local files for staging are NOT updated automatically!
# Pull staging schema to update local files after migration:
./firesync pull --env=staging

# After testing, promote to production
./firesync plan --env-from=dev --env-to=prod
./firesync apply --env-from=dev --env-to=prod

# Update production local files
./firesync pull --env=prod

Environment Migration

Compare and migrate schemas between environments:

# Compare dev and staging schemas (local vs local)
./firesync plan --env-from=dev --env-to=staging

# Apply dev schema to staging environment
./firesync apply --env-from=dev --env-to=staging

# Verify changes
./firesync pull --env=staging
git diff firestore_schema/staging/

Configuration

Custom Schema Directory

Override the schema directory for any command:

./firesync plan --env=dev --schema-dir=custom_schemas
./firesync apply --env=staging --schema-dir=backups/schemas_2024

Schema Files

FireSync manages three types of Firestore configurations:

composite-indexes.json

Composite indexes for complex queries spanning multiple fields. Each index includes metadata like name, state, and density from Firestore.

Example:

[
  {
    "density": "SPARSE_ALL",
    "fields": [
      {
        "fieldPath": "userId",
        "order": "ASCENDING"
      },
      {
        "fieldPath": "status",
        "order": "ASCENDING"
      },
      {
        "fieldPath": "createdAt",
        "order": "DESCENDING"
      },
      {
        "fieldPath": "__name__",
        "order": "DESCENDING"
      }
    ],
    "name": "projects/your-project-id/databases/(default)/collectionGroups/orders/indexes/CICAgICAgICA",
    "queryScope": "COLLECTION",
    "state": "READY"
  }
]

Note: When creating new indexes manually, you only need to specify fields and queryScope. The name, state, and density fields are populated by Firestore.

field-indexes.json

Single-field index configurations including default index settings and exemptions.

Example:

[
  {
    "indexConfig": {
      "indexes": [
        {
          "fields": [
            {
              "fieldPath": "*",
              "order": "ASCENDING"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        },
        {
          "fields": [
            {
              "fieldPath": "*",
              "order": "DESCENDING"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        },
        {
          "fields": [
            {
              "arrayConfig": "CONTAINS",
              "fieldPath": "*"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        }
      ]
    },
    "name": "projects/your-project-id/databases/(default)/collectionGroups/__default__/fields/*"
  }
]

Note: The __default__/fields/* entry controls default indexing behavior for all collections.

ttl-policies.json

Time-to-live policies for automatic document deletion. Documents are deleted when the TTL field value expires.

Example:

[
  {
    "indexConfig": {
      "ancestorField": "projects/your-project-id/databases/(default)/collectionGroups/__default__/fields/*",
      "indexes": [
        {
          "fields": [
            {
              "fieldPath": "expiresAt",
              "order": "ASCENDING"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        },
        {
          "fields": [
            {
              "fieldPath": "expiresAt",
              "order": "DESCENDING"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        },
        {
          "fields": [
            {
              "arrayConfig": "CONTAINS",
              "fieldPath": "expiresAt"
            }
          ],
          "queryScope": "COLLECTION",
          "state": "READY"
        }
      ],
      "usesAncestorConfig": true
    },
    "name": "projects/your-project-id/databases/(default)/collectionGroups/sessions/fields/expiresAt",
    "ttlConfig": {
      "state": "ACTIVE"
    }
  }
]

Note: TTL policies automatically create necessary indexes for the TTL field (ASCENDING, DESCENDING, and CONTAINS). The ttlConfig.state can be ACTIVE or INACTIVE.

Project Structure

firesync/
โ”œโ”€โ”€ firesync                   # Unified CLI entry point
โ”œโ”€โ”€ firestore_init.py          # Initialize workspace
โ”œโ”€โ”€ firestore_env.py           # Environment management
โ”œโ”€โ”€ firestore_pull.py          # Export schema from Firestore
โ”œโ”€โ”€ firestore_plan.py          # Compare local vs remote
โ”œโ”€โ”€ firestore_apply.py         # Apply schema to Firestore
โ”œโ”€โ”€ core/                      # Core Python package
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ cli.py                 # CLI argument parsing utilities
โ”‚   โ”œโ”€โ”€ config.py              # Configuration management
โ”‚   โ”œโ”€โ”€ gcloud.py              # GCloud CLI wrapper
โ”‚   โ”œโ”€โ”€ normalizers.py         # Data normalization
โ”‚   โ”œโ”€โ”€ operations.py          # Resource operations
โ”‚   โ”œโ”€โ”€ schema.py              # Schema file handling
โ”‚   โ””โ”€โ”€ workspace.py           # Workspace configuration
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ firesync_cli.py        # CLI implementation
โ”œโ”€โ”€ config.yaml                # Workspace configuration
โ”œโ”€โ”€ firestore_schema/          # Schema directories (per environment)
โ”‚   โ”œโ”€โ”€ dev/
โ”‚   โ”‚   โ”œโ”€โ”€ composite-indexes.json
โ”‚   โ”‚   โ”œโ”€โ”€ field-indexes.json
โ”‚   โ”‚   โ””โ”€โ”€ ttl-policies.json
โ”‚   โ”œโ”€โ”€ staging/
โ”‚   โ”‚   โ”œโ”€โ”€ composite-indexes.json
โ”‚   โ”‚   โ”œโ”€โ”€ field-indexes.json
โ”‚   โ”‚   โ””โ”€โ”€ ttl-policies.json
โ”‚   โ””โ”€โ”€ prod/
โ”‚       โ”œโ”€โ”€ composite-indexes.json
โ”‚       โ”œโ”€โ”€ field-indexes.json
โ”‚       โ””โ”€โ”€ ttl-policies.json
โ”œโ”€โ”€ secrets/                   # GCP service account keys (gitignored)
โ”‚   โ”œโ”€โ”€ gcp-key-dev.json
โ”‚   โ”œโ”€โ”€ gcp-key-staging.json
โ”‚   โ””โ”€โ”€ gcp-key-prod.json
โ”œโ”€โ”€ pyproject.toml             # Package configuration
โ”œโ”€โ”€ setup.py                   # Backward compatibility
โ”œโ”€โ”€ .gitignore
โ”œโ”€โ”€ LICENSE
โ””โ”€โ”€ README.md

CI/CD Integration

GitHub Actions - Single Environment

name: Deploy Firestore Schema

on:
  push:
    branches: [main]

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

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install gcloud CLI
        uses: google-github-actions/setup-gcloud@v1

      - name: Create config.yaml
        run: |
          echo "version: 1" > config.yaml
          echo "schema_base_dir: firestore_schema" >> config.yaml
          echo "environments:" >> config.yaml
          echo "  prod:" >> config.yaml
          echo "    key_env: GCP_KEY" >> config.yaml
          echo "    schema_dir: prod" >> config.yaml

      - name: Plan schema changes
        env:
          GCP_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
        run: ./firesync plan --env=prod

      - name: Apply schema changes
        env:
          GCP_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
        run: ./firesync apply --env=prod

GitHub Actions - Multi-Environment Pipeline

name: Deploy to All Environments

on:
  push:
    branches: [main]

jobs:
  deploy-dev:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: google-github-actions/setup-gcloud@v1

      - name: Deploy to dev
        env:
          GCP_DEV_KEY: ${{ secrets.GCP_DEV_KEY }}
        run: |
          ./firesync plan --env=dev
          ./firesync apply --env=dev

  deploy-staging:
    needs: deploy-dev
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: google-github-actions/setup-gcloud@v1

      - name: Deploy to staging
        env:
          GCP_STAGING_KEY: ${{ secrets.GCP_STAGING_KEY }}
        run: |
          ./firesync plan --env=staging
          ./firesync apply --env=staging

  deploy-prod:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v3
      - uses: google-github-actions/setup-gcloud@v1

      - name: Deploy to production
        env:
          GCP_PROD_KEY: ${{ secrets.GCP_PROD_KEY }}
        run: |
          ./firesync plan --env=prod
          ./firesync apply --env=prod

Limitations

  • Delete operations: Plan shows deletions but Apply doesn't implement them (safety feature)
  • Manual schema editing: Schema files must be edited manually or pulled from existing Firestore
  • Single region: Assumes single Firestore region per project

Troubleshooting

Authentication Issues

# Verify gcloud is installed
gcloud --version

# Check active account
gcloud auth list

# Re-authenticate if needed
gcloud auth login

Permission Denied

Ensure your service account has these IAM roles:

  • roles/datastore.indexAdmin or
  • roles/datastore.owner

Schema Files Not Found

# Pull schema first if firestore_schema/ is empty
./firesync pull --key-path=./gcp-key.json

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

MIT License - see LICENSE for details.

Author

Pavel Ravvich


Note: Always test schema changes in development/staging environments before applying to production.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

firestore_schema_migration-0.1.0.tar.gz (32.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

firestore_schema_migration-0.1.0-py3-none-any.whl (30.5 kB view details)

Uploaded Python 3

File details

Details for the file firestore_schema_migration-0.1.0.tar.gz.

File metadata

File hashes

Hashes for firestore_schema_migration-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a9a9067570b44dd9dcc1fb7f9cbf52349b72cf52728d6f6da12149267a9cdccf
MD5 8a89c294ae71adf7bdaf4d57163d11f9
BLAKE2b-256 249426bf0bf3daa99e8df45e998135fc9baa6e4d949c1c0b37b107543f348173

See more details on using hashes here.

File details

Details for the file firestore_schema_migration-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for firestore_schema_migration-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 edfd5b844f93d9f3f1bdcb559847ee162cbca149cf6af814d944ead80296119c
MD5 5a95245c65138b6f83b5d1f8e832ac4f
BLAKE2b-256 7f9e8708db64ca61a9fe6d2eed080fb1cae63a19b3104069d0748a1216f66426

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page