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 Mode
- Usage Examples
- Configuration
- 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
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.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:
./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
Commands Overview
firesync init
Initialize a new FireSync workspace. Creates config.yaml to manage multiple environments.
./firesync init
Creates:
config.yaml- Workspace configurationfirestore_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 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
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 filesenvironments- Map of environment configurationskey_path- Path to GCP service account key (relative to config.yaml)key_env- Name of environment variable containing key JSONschema_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.indexAdminorroles/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
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.0.tar.gz.
File metadata
- Download URL: firestore_schema_migration-0.1.0.tar.gz
- Upload date:
- Size: 32.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9a9067570b44dd9dcc1fb7f9cbf52349b72cf52728d6f6da12149267a9cdccf
|
|
| MD5 |
8a89c294ae71adf7bdaf4d57163d11f9
|
|
| BLAKE2b-256 |
249426bf0bf3daa99e8df45e998135fc9baa6e4d949c1c0b37b107543f348173
|
File details
Details for the file firestore_schema_migration-0.1.0-py3-none-any.whl.
File metadata
- Download URL: firestore_schema_migration-0.1.0-py3-none-any.whl
- Upload date:
- Size: 30.5 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 |
edfd5b844f93d9f3f1bdcb559847ee162cbca149cf6af814d944ead80296119c
|
|
| MD5 |
5a95245c65138b6f83b5d1f8e832ac4f
|
|
| BLAKE2b-256 |
7f9e8708db64ca61a9fe6d2eed080fb1cae63a19b3104069d0748a1216f66426
|