Getting Started
This guide walks you through setting up Snowplane and creating your first managed Snowflake resources.
Prerequisites
- A running Kubernetes cluster (kind, minikube, or remote — see Development Guide for local setup)
- Snowplane CRDs installed
- The Snowplane controller running
- A Snowflake account with the SYSADMIN role (or equivalent)
Create Credentials
Choose an authentication method and create the credentials Secret:
# Option A: Key pair (recommended for production)
kubectl create secret generic snowflake-creds \
--from-file=privateKey=/path/to/your/rsa_key.p8 \
-n default
# Option A2: Key pair with encrypted private key (passphrase-protected)
kubectl create secret generic snowflake-creds \
--from-file=privateKey=/path/to/your/encrypted_rsa_key.p8 \
--from-literal=passphrase='YourPassphrase' \
-n default
# Option B: Username/password (simpler for development)
echo -n 'YourPassword123!' > /tmp/sf-password.txt
kubectl create secret generic snowflake-creds \
--from-file=password=/tmp/sf-password.txt \
-n default
rm -f /tmp/sf-password.txt
# Option C: External OAuth token
echo -n '<your-oauth-access-token>' > /tmp/sf-token.txt
kubectl create secret generic snowflake-creds \
--from-file=token=/tmp/sf-token.txt \
-n default
rm -f /tmp/sf-token.txt
Always use
--from-fileinstead of--from-literalfor credentials.--from-literalvalues are visible in process listings (/proc/*/cmdline) and shell history.
Workload Identity Federation (WIF)
For WIF, no Secret is needed. The controller reads an OAuth token from a projected service account token volume:
volumes:
- name: snowflake-token
projected:
sources:
- serviceAccountToken:
audience: "https://your-snowflake-account.snowflakecomputing.com"
expirationSeconds: 3600
path: token
See the Workload Identity Guide for cloud-specific setup.
Create a ProviderConfig
The ProviderConfig tells Snowplane how to connect to your Snowflake account.
Key Pair Authentication
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: ProviderConfig
metadata:
name: default
spec:
account: "xy12345"
region: "us-east-1"
user: "SNOWPLANE_USER"
role: "SYSADMIN"
warehouse: "COMPUTE_WH"
authenticationType: KeyPair
credentials:
secretRef:
name: snowflake-creds
namespace: default
key: privateKey
# Optional: for encrypted PKCS#8 private keys, specify the key
# within the same Secret that holds the passphrase.
# passphraseKey: passphrase
Username/Password Authentication
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: ProviderConfig
metadata:
name: default
spec:
account: "xy12345"
region: "us-east-1"
user: "SNOWPLANE_USER"
role: "SYSADMIN"
warehouse: "COMPUTE_WH"
authenticationType: UsernamePassword
credentials:
secretRef:
name: snowflake-creds
namespace: default
key: password
Workload Identity Federation (WIF)
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: ProviderConfig
metadata:
name: default
spec:
account: "xy12345"
region: "us-east-1"
user: "SNOWPLANE_USER"
role: "SYSADMIN"
warehouse: "COMPUTE_WH"
authenticationType: WorkloadIdentity
workloadIdentity:
audience: "https://xy12345.us-east-1.snowflakecomputing.com"
kubectl apply -f providerconfig.yaml
Verify Connectivity
kubectl get pc default -o yaml
Look for these conditions:
status:
conditions:
- type: Ready
status: "True"
reason: Available
message: "Snowflake connection verified"
- type: Synced
status: "True"
reason: ReconcileSuccess
Troubleshooting
| Reason | Description | Fix |
|---|---|---|
| SecretNotFound | Referenced Secret doesn’t exist | kubectl get secret snowflake-creds -n default |
| InvalidConfig | Secret is missing the expected key | kubectl describe pc default |
| PingFailed | Connection to Snowflake failed | Check account, region, network connectivity |
| ClientCreationFailed | Snowflake client init failure | Ensure valid PEM key (PKCS8 or PKCS1) |
# Controller logs
kubectl logs -n snowplane-system deployment/snowplane-controller-manager -f
# Events
kubectl get events --field-selector involvedObject.name=default
Create a Database
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Database
metadata:
name: my-database
spec:
name: MY_DATABASE
providerRef:
name: default
kubectl apply -f database.yaml
kubectl get databases # or: kubectl get db
A healthy Database:
NAME DATABASE READY SYNCED OWNER AGE
my-database MY_DATABASE True True SYSADMIN 2m
Database with Full Options
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Database
metadata:
name: analytics-database
spec:
name: ANALYTICS_DB
comment: "Analytics data warehouse managed by Snowplane"
dataRetentionTimeInDays: 14
maxDataExtensionTimeInDays: 28
replaceInvalidCharacters: true
defaultDDLCollation: "en-ci"
storageSerializationPolicy: OPTIMIZED
logLevel: INFO
metricLevel: ALL
traceLevel: ON_EVENT
deletionPolicy: Delete
providerRef:
name: default
Transient Database
Transient databases have no Fail-safe period, reducing storage costs:
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Database
metadata:
name: staging-database
spec:
name: STAGING_DB
transient: true
dataRetentionTimeInDays: 1
providerRef:
name: default
Orphan Deletion Policy
To keep the Snowflake database when the CR is deleted:
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Database
metadata:
name: production-database
spec:
name: PRODUCTION_DB
deletionPolicy: Orphan
providerRef:
name: default
Nil-Means-Unmanaged Semantics
Optional pointer fields (*string, *int32, *bool) follow the nil-means-unmanaged convention:
- nil (omitted): Snowflake’s server-side default is preserved
- non-nil (set): The controller manages it declaratively and corrects any drift
Reverting to Snowflake Defaults (UNSET)
Remove a previously-set field from the spec to trigger ALTER ... UNSET:
# Step 1: Set retention
spec:
name: MY_DATABASE
dataRetentionTimeInDays: 14
# Step 2: Remove it — controller issues ALTER ... UNSET
spec:
name: MY_DATABASE
Conditions
Each resource reports its state through Kubernetes conditions:
| Condition | Meaning |
|---|---|
Ready |
Resource exists in Snowflake and is in the desired state |
Synced |
Last reconciliation succeeded |
Terminal |
Non-retryable error — requires user intervention |
Recoverable |
Transient error — will be retried automatically |
ReferencesResolved |
All cross-resource references are resolved |
CredentialsInvalid |
ProviderConfig credentials are invalid or expired |
CRD-Level Validation (CEL Rules)
Snowplane uses CRD-embedded CEL validation rules at the API server level — no webhooks or cert-manager required.
Schema Defaults: deletionPolicy → Delete, providerRef.name → "default", User type → PERSON.
Immutable fields: spec.name, spec.transient, spec.warehouseType, spec.type, spec.databaseRef are rejected on UPDATE.
Immutable fields are enforced at two layers — CRD schema (CEL
self == oldSelf) and reconciler-level terminal error.
Identifier fields: spec.databaseName and spec.schemaName expect simple Snowflake identifiers (e.g. ANALYTICS, PUBLIC), not fully-qualified names. The controller constructs the FQN from databaseName + schemaName + name. Dots in these fields are rejected by CEL validation. Use databaseRef/schemaRef to reference Snowplane-managed parents instead.
Create a Schema
Schemas belong to a Database. The Schema controller resolves the databaseRef, waiting for the parent Database to be Ready.
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Schema
metadata:
name: my-schema
spec:
name: PUBLIC
databaseRef:
name: my-database
# namespace: infra # optional — reference a Database in another namespace
providerRef:
name: default
kubectl get schemas
NAME SCHEMA DATABASE READY SYNCED REFS OWNER AGE
my-schema PUBLIC MY_DATABASE True True True SYSADMIN 1m
Database + Schema Together
---
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Database
metadata:
name: analytics-db
spec:
name: ANALYTICS
providerRef:
name: default
---
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Schema
metadata:
name: raw-schema
spec:
name: RAW
databaseRef:
name: analytics-db
providerRef:
name: default
The order does not matter — the Schema controller requeues until its Database is Ready.
Cascading Deletion
Delete a Database CR even if Schema CRs still exist — the Schema controller uses cached status.databaseName for DROP SCHEMA.
ProviderConfig is protected by an in-use finalizer — it cannot be deleted while any managed resource references it.
Create a Warehouse
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Warehouse
metadata:
name: analytics-wh
spec:
providerRef:
name: default
name: ANALYTICS_WH
warehouseSize: XSMALL
autoSuspend: 300
autoResume: true
Detect-Only Drift Policy
spec:
managementPolicies:
driftPolicy: detect-only
See Drift Detection for details.
Create Account Roles
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: AccountRole
metadata:
name: data-reader
spec:
providerRef:
name: default
name: DATA_READER
kubectl get accountroles # or: kubectl get ar
Use Role (USE ROLE)
All resource types support spec.useRole to switch the active Snowflake role before operations:
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: Database
metadata:
name: analytics-db
spec:
name: ANALYTICS_DB
useRole: DATA_ADMIN
providerRef:
name: default
spec.useRoleis immutable after creation. The service user must have the target role directly granted.
Create Users
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: User
metadata:
name: etl-service
spec:
name: ETL_SERVICE_USER
type: SERVICE
defaultRole: ETL_ROLE
rsaPublicKey:
name: etl-service-keys
key: rsa-public-key
providerRef:
name: default
Grant Privileges
Three grant CRDs are available:
# GrantPrivilegesToAccountRole — grant a privilege on an account-level object
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: GrantPrivilegesToAccountRole
metadata:
name: usage-on-analytics-db
spec:
privilege: USAGE
on:
accountObject:
objectType: DATABASE
objectName: ANALYTICS_DB
accountRole: DATA_READER
providerRef:
name: default
# GrantPrivilegesToAccountRole — grant ALL PRIVILEGES
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: GrantPrivilegesToAccountRole
metadata:
name: all-on-analytics-db
spec:
allPrivileges: true
on:
accountObject:
objectType: DATABASE
objectName: ANALYTICS_DB
accountRole: DATA_ADMIN
providerRef:
name: default
# GrantPrivilegesToDatabaseRole — grant a privilege scoped to a database role
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: GrantPrivilegesToDatabaseRole
metadata:
name: usage-on-my-db
spec:
privilege: USAGE
on:
database: MY_DB
databaseRole: MY_DB.READER
providerRef:
name: default
# GrantPrivilegesToShare — grant a privilege to a share
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: GrantPrivilegesToShare
metadata:
name: usage-on-shared-db
spec:
privilege: USAGE
on:
database: SHARED_DB
share: MY_SHARE
providerRef:
name: default
Use
allPrivileges: trueinstead ofprivilege: "ALL PRIVILEGES"on GrantPrivilegesToAccountRole and GrantPrivilegesToDatabaseRole.allPrivilegesandprivilegeare mutually exclusive. GrantPrivilegesToShare does not supportallPrivileges.
withGrantOption: trueenables re-granting. Revoking the original Grant CR does NOT cascade-revoke re-grants.
All grant spec fields are immutable after creation.
Export Fields
FieldExport copies values from any managed resource’s status into a ConfigMap or Secret:
apiVersion: snowplane.hupe1980.github.io/v1alpha1
kind: FieldExport
metadata:
name: db-name-export
spec:
from:
resource:
kind: Database
name: my-database
path: ".status.showOutput.name"
to:
kind: ConfigMap
name: snowplane-exports
key: database-name
CREATE OR ALTER
For Database, Schema, Table, Warehouse, Task, Tag, View, FileFormat, MaskingPolicy, PasswordPolicy, AuthenticationPolicy, NetworkRule, RowAccessPolicy, and User, CREATE OR ALTER is enabled by default. To opt out and use the legacy CREATE IF NOT EXISTS + ALTER two-step flow:
spec:
managementPolicies:
createOrAlter: false
CREATE OR ALTERis a Snowflake preview feature. If Snowflake returns an unsupported-feature error, the controller automatically falls back to the two-step flow.
Abandon on Delete
To delete a Kubernetes resource without dropping the Snowflake object, set the abandon-on-delete annotation:
metadata:
annotations:
snowplane.hupe1980.github.io/abandon-on-delete: "true"
This is useful for unblocking stuck deletions (e.g., when DROP fails due to revoked permissions) or migrating ownership to another tool.
See Annotations Reference for the full list of annotations.
What’s Next
Snowplane manages many Snowflake resource types including Task, StreamOnTable, StreamOnView, StreamOnExternalTable, StreamOnDirectoryTable, StreamOnDynamicTable, Tag, NetworkPolicy, NetworkRule, PasswordPolicy, AuthenticationPolicy, ResourceMonitor, MaskingPolicy, RowAccessPolicy, StorageIntegrationAWS, StorageIntegrationGCS, StorageIntegrationAzure, ExternalOAuthIntegration, SAML2Integration, SCIMIntegration, EmailNotificationIntegration, QueueNotificationIntegration, WebhookNotificationIntegration, FileFormat, Pipe, DynamicTable, MaterializedView, Sequence, ExternalTable, AccountRoleAssignment, and DatabaseRoleAssignment.
See config/samples/ for example CRs of every resource type.