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-file instead of --from-literal for credentials. --from-literal values 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.useRole is 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: true instead of privilege: "ALL PRIVILEGES" on GrantPrivilegesToAccountRole and GrantPrivilegesToDatabaseRole. allPrivileges and privilege are mutually exclusive. GrantPrivilegesToShare does not support allPrivileges.

withGrantOption: true enables 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 ALTER is 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.