Infrastructure as Code for Google Cloud Firestore
Project description
FireSync
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
- Quick Start
- Commands Overview
- Workspace Configuration
- Usage Examples
- Schema Files
- CI/CD Integration
- Troubleshooting
- Contributing
- License
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.listdatastore.indexes.createdatastore.indexes.updatefirebase.projects.get
Installation
Install from PyPI:
pip install firestore-schema-migration
This installs the firesync command globally.
Alternative: Install from source
git clone https://github.com/PavelRavvich/firesync.git
cd firesync
pip 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.yamlfiresync env add <name>- Add environment to workspacefiresync env remove <name>- Remove environment from workspacefiresync env list- List all environmentsfiresync env show <name>- Show environment detailsfiresync pull- Export Firestore schema to local filesfiresync plan- Compare local vs remote schemasfiresync apply- Deploy local schema to Firestore
Quick Setup
- Initialize workspace:
firesync init
- Add your environments:
# Using direct path to key file
firesync env add dev --key-path=./secrets/gcp-key-dev.json --description="Development"
# Using environment variable (auto-detects JSON content or file path)
firesync env add prod --key-env=GCP_PROD_KEY --description="Production"
- Pull schemas from all environments:
firesync pull --all
Commands Overview
firesync init
Initialize a new FireSync workspace. Creates config.yaml to manage multiple environments.
# Initialize in current directory
firesync init
# Initialize in custom directory
firesync init --path ./my-project
Options:
--path PATH- Target directory for workspace (default: current directory)
Creates:
config.yaml- Workspace configurationfirestore_schema/- Default schema directory
firesync env
Manage environments in your workspace.
Authentication Options:
FireSync supports two ways to provide GCP service account credentials:
| Option | Description | Use Case |
|---|---|---|
--key-path |
Direct path to key file | Local development |
--key-env |
Environment variable name | CI/CD and shared environments |
The --key-env option is smart: it auto-detects whether the environment variable contains:
- JSON content (the key itself) - creates a temp file for gcloud
- File path (path to key file) - reads the key from that path
Add environment:
# Option 1: Direct path to key file
firesync env add dev --key-path=./secrets/gcp-key-dev.json
# Option 2: Environment variable (auto-detects JSON content or file path)
firesync env add prod --key-env=GCP_PROD_KEY --description="Production environment"
Example environment variable usage:
# Store JSON content in env var (for CI/CD secrets)
export GCP_KEY='{"type":"service_account","project_id":"my-project",...}'
# Or store path to key file in env var
export GCP_KEY='/path/to/service-account.json'
# Both work with the same command:
firesync env add prod --key-env=GCP_KEY
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 indexesfield-indexes.json- Single-field indexesttl-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 # Direct path to key file
description: "Development environment"
prod:
key_env: GCP_PROD_KEY # Env var (JSON content OR file path)
description: "Production environment"
Fields:
version- Config format version (always 1)schema_base_dir- Base directory for all schema filesenvironments- Map of environment configurations (choose ONE per environment):key_path- Direct path to GCP service account key file (relative to config.yaml)key_env- Environment variable name (auto-detects JSON content or file path)description- Optional description of the environment
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
Your workspace (after firesync init):
your-project/
โโโ config.yaml # Workspace configuration
โโโ firestore_schema/ # Schema directories (per environment)
โ โโโ dev/
โ โ โโโ composite-indexes.json
โ โ โโโ field-indexes.json
โ โ โโโ ttl-policies.json
โ โโโ staging/
โ โ โโโ ...
โ โโโ prod/
โ โโโ ...
โโโ secrets/ # GCP service account keys (gitignored)
โโโ gcp-key-dev.json
โโโ gcp-key-staging.json
โโโ gcp-key-prod.json
Source code structure (for contributors):
firesync/
โโโ src/firesync/ # Main Python package
โ โโโ __init__.py
โ โโโ __main__.py # python -m firesync support
โ โโโ main.py # CLI entry point
โ โโโ cli.py # CLI argument parsing
โ โโโ 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
โ โโโ commands/ # Command implementations
โ โโโ __init__.py
โ โโโ init.py
โ โโโ env.py
โ โโโ pull.py
โ โโโ plan.py
โ โโโ apply.py
โโโ tests/ # Unit tests
โ โโโ test_cli.py
โ โโโ test_config.py
โ โโโ test_gcloud.py
โ โโโ test_normalizers.py
โ โโโ test_operations.py
โ โโโ test_schema.py
โ โโโ test_workspace.py
โโโ pyproject.toml # Package configuration
โโโ 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.11'
- name: Install firesync
run: pip install firestore-schema-migration
- 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: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install firestore-schema-migration
- 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: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install firestore-schema-migration
- 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: actions/setup-python@v4
with:
python-version: '3.11'
- run: pip install firestore-schema-migration
- 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.indexAdminorroles/datastore.owner
Schema Files Not Found
# Pull schema first if firestore_schema/ is empty
firesync pull --env=dev
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file firestore_schema_migration-0.1.2.tar.gz.
File metadata
- Download URL: firestore_schema_migration-0.1.2.tar.gz
- Upload date:
- Size: 33.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f0de98a04a2031beae04f5fdde029eaf4614688763efd72d740f468656d5281
|
|
| MD5 |
a1720ee8e042d70d51a7e2f0de4857dc
|
|
| BLAKE2b-256 |
c2688c8729672db013fca5e2f0038ce18d26186aef0eae5cf4ac1a7d2837060c
|
File details
Details for the file firestore_schema_migration-0.1.2-py3-none-any.whl.
File metadata
- Download URL: firestore_schema_migration-0.1.2-py3-none-any.whl
- Upload date:
- Size: 31.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bcabd20b8863cb00ffa84b2c398b2a2df5fa85a155cf9cfc143ccb60b11acae
|
|
| MD5 |
b2bd28a81f82ec62c058d1c348209670
|
|
| BLAKE2b-256 |
85efa5edd59404c3442935adda8fb30e61a420c00673f41a0ef11140ff1740e1
|