Skip to main content

Snowcap: Snowflake infrastructure as code

Project description

snowcap - Snowflake infrastructure as code

PyPI Python 3.10+ License

formerly Titan Core

Table of Contents

Brought to you by Datacoves

Datacoves

Snowcap helps you provision, deploy, and secure resources in Snowflake. Datacoves takes it further: a managed DataOps platform for dbt and Airflow, deployable in your private cloud or available as SaaS.

  • Private cloud or SaaS - your data, your choice
  • Managed dbt + Airflow - production-ready from day one
  • In-browser VS Code - onboard developers in minutes
  • Bring your own tools - integrates with your existing stack, no lock-in
  • AI-assisted development - connect your organization's approved LLM (Anthropic, OpenAI, Azure, Gemini, and more)
  • Built-in governance - CI/CD, guardrails, and best practices included

Snowcap is the power tools. Datacoves is the workshop.

Explore the platform →


Why Snowcap?

Snowcap replaces tools like Terraform, Schemachange, or Permifrost with a single, purpose-built tool for Snowflake.

Feature Snowcap Terraform Permifrost
Snowflake-native Yes No (generic) Yes
State file No Yes No
YAML + Python Yes HCL only YAML only
Speed 50-90% faster Baseline Medium
All resource types Yes Most Roles/grants only
for_each templating Yes Yes No
Export existing resources Yes Import only No

Deploy any Snowflake resource, including users, roles, schemas, databases, integrations, pipes, stages, functions, stored procedures, and more. Convert adhoc, bug-prone SQL management scripts into simple, repeatable configuration.

Who is Snowcap for?

  • DevOps engineers looking to automate and manage Snowflake infrastructure
  • Analytics engineers working with dbt who want to manage Snowflake resources without macros
  • Data platform teams who need to reliably manage Snowflake with CI/CD
  • Organizations that prefer a git-based workflow for infrastructure management
  • Teams seeking to replace Terraform for Snowflake-related tasks
    ╔══════════╗                                           ╔═══════════╗
    ║  CONFIG  ║                                           ║ SNOWFLAKE ║
    ╚══════════╝                                           ╚═══════════╝
  ┏━━━━━━━━━━━┓                                        ┏━━━━━━━━━━━┓
┌─┫ WAREHOUSE ┣─────┐                                ┌─┫ WAREHOUSE ┣───────────┐
│ ┗━━━━━━━━━━━┛     │                    ALTER       │ ┗━━━━━━━━━━━┛           │
│ name:         ETL │─────┐           ┌─ WAREHOUSE ─▶│ name:         ETL       │
│ auto_suspend: 60  │     │           │              │ auto_suspend: 300 -> 60 │
└───────────────────┘  ╔══▼═══════════╩═╗            └─────────────────────────┘
                       ║                ║
                       ║    SNOWCAP     ║
  ┏━━━━━━┓             ║                ║              ┏━━━━━━┓
┌─┫ ROLE ┣──────────┐  ╚══▲═══════════╦═╝            ┌─┫ ROLE ┣────────────────┐
│ ┗━━━━━━┛          │     │           │              │ ┗━━━━━━┛                │
│ name: TRANSFORMER │─────┘           └─ CREATE ────▶│ name: TRANSFORMER       │
└───────────────────┘                    ROLE        └─────────────────────────┘

Key Features

  • Declarative — Generates the right SQL to make your config and account match
  • Comprehensive — Nearly every Snowflake resource is supported
  • Flexible — Write resource configuration in YAML or Python
  • Fast — Snowcap runs 50-90% faster than Terraform and Permifrost
  • Migration-friendly — Generate config automatically with the export CLI

Real-World Pattern: Modular RBAC

Snowcap's for_each templating makes it easy to implement composable role architectures used in production environments.

The Problem

Managing Snowflake permissions typically leads to:

  • Scattered SQL scripts that drift from reality
  • No audit trail for "who granted what to whom"
  • Copy-paste errors when adding new resources
  • Overly permissive roles because granular management is painful

The Solution: Atomic Building Blocks

Create atomic roles that grant a single privilege on a single resource type, then compose them into functional roles.

┌─────────────────────────────────────────────────────────────┐
│                    FUNCTIONAL ROLES                         │
│            (analyst, loader, transformer_dbt)               │
│                  Users are assigned here                    │
└──────────────────────────┬──────────────────────────────────┘
                           │ inherits
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    ATOMIC ROLES                             │
│  z_db__<database>       → USAGE on database                 │
│  z_schema__<schema>     → USAGE on schema                   │
│  z_wh__<warehouse>      → USAGE + MONITOR on warehouse      │
│  z_stage__<stage>       → READ/WRITE on stage               │
│  z_tables_views__select → SELECT on tables/views            │
└─────────────────────────────────────────────────────────────┘

Implementation with Snowcap

Step 1: Define your databases

# databases.yml
vars:
  - name: databases
    type: list
    default:
      - name: raw
        owner: loader
      - name: analytics
        owner: transformer
      - name: reporting
        owner: transformer

Step 2: Auto-generate atomic roles with templates

# object_templates/database.yml

# Databases
databases:
  - for_each: var.databases
    name: "{{ each.value.name }}"
    owner: "{{ each.value.owner }}"

# Database roles
roles:
  - for_each: var.databases
    name: "z_db__{{ each.value.name }}"

# Database grants
grants:
  - for_each: var.databases
    priv: USAGE
    on: "database {{ each.value.name }}"
    to: "z_db__{{ each.value.name }}"

Step 3: Compose functional roles

# roles__functional.yml
roles:
  - name: analyst
  - name: loader
  - name: transformer

role_grants:
  - to_role: analyst
    roles:
      - z_db__analytics
      - z_db__reporting
      - z_schema__marts
      - z_schema__reports
      - z_wh__querying
      - z_tables_views__select

  - to_role: transformer
    roles:
      - z_db__raw
      - z_db__analytics
      - z_wh__transforming

Why This Pattern Works

Benefit Description
Auditable Each atomic role does one thing—easy to audit and understand
DRY Add a database to the list, templates auto-create the role + grants
Least Privilege Users get functional roles, never atomic roles directly
Composable Mix and match atomic roles to create new personas in minutes
Git-native Full audit trail via version control

Getting Started

Requirements: Python 3.10 or higher

Install from PyPI

# MacOS / Linux
python -m venv .venv
source .venv/bin/activate
pip install snowcap

# Windows
python -m venv .venv
.\.venv\Scripts\activate
pip install snowcap

Quick Start: Create a Warehouse

The simplest way to get started—define a single resource and deploy it:

# snowcap.yml
warehouses:
  - name: analytics
    warehouse_size: xsmall
    auto_suspend: 60

Create a .env file for credentials (add .env to .gitignore!):

# .env
SNOWFLAKE_ACCOUNT=my-account
SNOWFLAKE_USER=my-user
SNOWFLAKE_PASSWORD=my-password
SNOWFLAKE_ROLE=SYSADMIN

Run snowcap:

# Load environment variables
export $(cat .env | xargs)

# Preview changes
snowcap plan --config snowcap.yml

# Apply changes
snowcap apply --config snowcap.yml

That's it. Snowcap compares your config to Snowflake and generates the SQL to make them match.

Scaling Up: Directory Structure with Templates

As your infrastructure grows, organize configs into directories and use templates for scalability:

snowcap/
├── resources/
│   ├── databases.yml            # Database definitions
│   ├── schemas.yml              # Schema definitions
│   ├── warehouses.yml           # Warehouse definitions
│   ├── stages.yml               # Stage definitions
│   ├── users.yml                # User definitions
│   ├── roles__base.yml          # Atomic privilege roles
│   └── roles__functional.yml    # Functional roles + grants
│
└── object_templates/            # Auto-generate resources with for_each
    ├── database.yml
    ├── schema.yml
    └── warehouses.yml

databases.yml - Define your databases:

vars:
  - name: databases
    type: list
    default:
      - name: raw
        owner: loader
      - name: analytics
        owner: transformer
      - name: analytics_dev
        owner: transformer

object_templates/database.yml - Auto-generate databases, roles, and grants:

# Databases
databases:
  - for_each: var.databases
    name: "{{ each.value.name }}"
    owner: "{{ each.value.owner }}"

# Database roles
roles:
  - for_each: var.databases
    name: "z_db__{{ each.value.name }}"

# Database grants
grants:
  - for_each: var.databases
    priv: USAGE
    on: "database {{ each.value.name }}"
    to: "z_db__{{ each.value.name }}"

roles__functional.yml - Compose into functional roles:

roles:
  - name: analyst
  - name: loader
  - name: transformer

role_grants:
  - to_role: analyst
    roles:
      - z_db__analytics
      - z_schema__marts
      - z_wh__querying
      - z_tables_views__select

  - to_role: transformer
    roles:
      - z_db__raw
      - z_db__analytics
      - z_wh__transforming

Run snowcap:

# Load environment variables from .env
export $(cat .env | xargs)

# Preview all changes
snowcap plan --config ./snowcap/

# Apply all changes
snowcap apply --config ./snowcap/

Adding a new database? Just add one line to databases.yml—the template auto-creates the database, role, and grant.

Export Existing Resources

Already have a Snowflake environment? Generate config from your existing setup:

snowcap export \
  --resource=warehouse,role,grant \
  --out=snowcap.yml

Python Example

import os
import snowflake.connector

from snowcap.blueprint import Blueprint, print_plan
from snowcap.resources import Grant, Role, Warehouse

# Configure resources by instantiating Python objects.

role = Role(name="transformer")

warehouse = Warehouse(
    name="transforming",
    warehouse_size="large",
    auto_suspend=60,
)

usage_grant = Grant(priv="usage", to=role, on=warehouse)

# Snowcap compares your config to a Snowflake account. Create a Snowflake
# connection to allow Snowcap to connect to your account.

connection_params = {
    "account": os.environ["SNOWFLAKE_ACCOUNT"],
    "user": os.environ["SNOWFLAKE_USER"],
    "password": os.environ["SNOWFLAKE_PASSWORD"],
    "role": "SYSADMIN",
}
session = snowflake.connector.connect(**connection_params)

# Create a Blueprint and pass your resources into it. A Blueprint helps you
# validate and deploy a set of resources.

bp = Blueprint(resources=[
    role,
    warehouse,
    usage_grant,
])

# Blueprint works like Terraform. Calling plan(...) will compare your config
# to the state of your Snowflake account and return a list of changes.

plan = bp.plan(session)
print_plan(plan) # =>
"""
» snowcap
» Plan: 4 to add, 0 to change, 0 to destroy.

+ urn::ABCD123:warehouse/transforming {
  + name                                = "transforming"
  + owner                               = "SYSADMIN"
  + warehouse_type                      = "STANDARD"
  + warehouse_size                      = "LARGE"
  ...
}

+ urn::ABCD123:role/transformer {
  + name    = "transformer"
  + owner   = "USERADMIN"
  + tags    = None
  + comment = None
}

+ urn::ABCD123:grant/TRANSFORMER?priv=USAGE&on=warehouse/TRANSFORMING {
  + priv         = "USAGE"
  + on           = "transforming"
  + on_type      = "WAREHOUSE"
  + to           = TRANSFORMER
  ...
}
"""

# Calling apply(...) will convert your plan into the right set of SQL commands
# and run them against your Snowflake account.
bp.apply(session, plan) # =>
"""
[SNOWCAP_USER:SYSADMIN]  > USE SECONDARY ROLES ALL
[SNOWCAP_USER:SYSADMIN]  > CREATE WAREHOUSE TRANSFORMING warehouse_type = STANDARD ...
[SNOWCAP_USER:SYSADMIN]  > USE ROLE USERADMIN
[SNOWCAP_USER:USERADMIN] > CREATE ROLE TRANSFORMER
[SNOWCAP_USER:USERADMIN] > USE ROLE SYSADMIN
[SNOWCAP_USER:SYSADMIN]  > GRANT USAGE ON WAREHOUSE transforming TO TRANSFORMER
"""

Using the CLI

You can use the CLI to generate a plan, apply a plan, or export resources. To use the CLI, install the Python package and call python -m snowcap from the command line.

The CLI allows you to plan and apply a Snowcap YAML config. You can specify a single input file or a directory of configs.

CLI Commands

snowcap --help

# Commands:
#   apply    Apply a resource config to a Snowflake account
#   connect  Test the connection to Snowflake
#   export   Generate a resource config for existing Snowflake resources
#   plan     Compare a resource config to the current state of Snowflake

Environment Variables

To connect with Snowflake, the CLI uses environment variables:

Variable Description
SNOWFLAKE_ACCOUNT Your Snowflake account identifier
SNOWFLAKE_USER Username
SNOWFLAKE_PASSWORD Password
SNOWFLAKE_DATABASE Default database (optional)
SNOWFLAKE_SCHEMA Default schema (optional)
SNOWFLAKE_ROLE Role to use
SNOWFLAKE_WAREHOUSE Warehouse to use
SNOWFLAKE_MFA_PASSCODE MFA passcode (if required)
SNOWFLAKE_AUTHENTICATOR Authentication method

For key-pair auth, use these instead of SNOWFLAKE_PASSWORD:

  • SNOWFLAKE_PRIVATE_KEY_PATH
  • PRIVATE_KEY_PASSPHRASE (if using encrypted key)

Set SNOWFLAKE_AUTHENTICATOR to SNOWFLAKE_JWT when using key-pair auth.

Using the GitHub Action

Automate Snowflake deployments with GitHub Actions. Here's an example workflow:

# .github/workflows/snowcap.yml
name: Deploy to Snowflake with Snowcap

on:
  push:
    branches: [ main ]
    paths:
      - 'snowcap/**'

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

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install Snowcap
        run: pip install snowcap

      - name: Plan changes
        run: snowcap plan --config ./snowcap/
        env:
          SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
          SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
          SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}
          SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}

      - name: Apply changes
        if: github.ref == 'refs/heads/main'
        run: snowcap apply --config ./snowcap/
        env:
          SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }}
          SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }}
          SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }}
          SNOWFLAKE_ROLE: ${{ secrets.SNOWFLAKE_ROLE }}

Documentation

Full documentation is available at datacoves.github.io/snowcap.

Open Source

This project is a fork of Titan Core, originally created by Titan Systems. The original project appears to be unmaintained, so Datacoves has forked it to continue development, fix bugs, and add new features.

We are grateful to the Titan Systems team for creating this project and releasing it under an open source license.

This project is licensed under the Apache 2.0 License - see LICENSE for details.

Contributing

We welcome contributions! Please see our contributing guidelines for details.

Support

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

snowcap-1.0.0.tar.gz (242.1 kB view details)

Uploaded Source

Built Distribution

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

snowcap-1.0.0-py3-none-any.whl (201.9 kB view details)

Uploaded Python 3

File details

Details for the file snowcap-1.0.0.tar.gz.

File metadata

  • Download URL: snowcap-1.0.0.tar.gz
  • Upload date:
  • Size: 242.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.19

File hashes

Hashes for snowcap-1.0.0.tar.gz
Algorithm Hash digest
SHA256 36f8be0cbf4d4cbdac4fd2062bc000f5997207c111f4b33f739d4d9ff12dfb2f
MD5 1d42d75fcb715728bab790419cde59b0
BLAKE2b-256 42e8c289025e226ad81a7fb3df0fb6953f187af1a9fb4673deb91932187c3e11

See more details on using hashes here.

File details

Details for the file snowcap-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: snowcap-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 201.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.19

File hashes

Hashes for snowcap-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2d866e2438a27249560e5f5a633eeb9b96fa45f10960760e4906eaa367c984b1
MD5 e713496a6d7fb4a7172bd8af3f0b8aa8
BLAKE2b-256 7ac2f821ca7c319d32c02e6903f65f53056095de89ba5de653ff00c332564ece

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