Manages a single Snowflake task (
snowflake_task) — SQL execution, schedule/DAG triggering, and compute source — targetingsnowflakedb/snowflake ~> 2.17.
- ❄️ Creates and owns exactly one
snowflake_task.this— no children, nofor_each. - 🧱 Defaults to the safest posture Snowflake allows for a newly-applied task:
started = false(suspended) — a task never silently starts running SQL against a live account on its first apply. - 🔌 Accepts a warehouse, a notification integration, and predecessor/finalizer tasks purely
by reference (
warehouse,error_integration,after,finalize) — every referenced object is owned elsewhere, most commonly a sibling module or another instance of this same module. - 🧬 Models the schedule/finalize/after trigger-mechanism exclusivity, the
warehouse/user_task_managed_initial_warehouse_size compute-source exclusivity, the
configJSON well-formedness and$$-wrapping constraint, and the string-typedallow_overlapping_executionfield — every schema quirk the live provider exposes for this resource. - 🚫 Owns no privilege grants and no DAG-wide invariants across multiple task instances — a caller composing several instances into a DAG is responsible for that graph-level discipline (exactly one root, no orphaned finalizer, no cycles).
💡 Why it matters: an empty call to this module produces a task that is fully configured but suspended — nothing executes until a caller explicitly reviews the SQL, schedule, and DAG wiring and sets
started = true. This is the same "the empty call must produce the safe resource" posture this suite's secure-by-default convention requires everywhere else in the > Snowflake estate, applied to the one Snowflake object type in this library that can execute arbitrary SQL on a timer.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
classDef moduleNode fill:#29B5E8,color:#ffffff,stroke:#1b7ea6,stroke-width:1px;
classDef keystoneNode fill:#11567F,color:#ffffff,stroke:#0b3e5c,stroke-width:1px;
classDef neutralNode fill:#eef1f5,color:#1a1a1a,stroke:#c7ccd4,stroke-width:1px;
DB["tf-mod-snowflake-database"]:::neutralNode
SCHEMA["tf-mod-snowflake-schema"]:::neutralNode
WH["tf-mod-snowflake-warehouse"]:::neutralNode
NI["tf-mod-snowflake-notification-integration"]:::neutralNode
STREAM["tf-mod-snowflake-stream"]:::neutralNode
SIBTASK["sibling task instance (DAG predecessor/root)"]:::neutralNode
MOD["tf-mod-snowflake-task"]:::moduleNode
RES["snowflake_task.this"]:::keystoneNode
DB -->|"name output to database input"| MOD
SCHEMA -->|"name output to schema input"| MOD
WH -->|"fully_qualified_name output to warehouse input"| MOD
NI -->|"fully_qualified_name output to error_integration input"| MOD
STREAM -->|"fully_qualified_name interpolated into sql_statement"| MOD
MOD -->|"creates"| RES
SIBTASK -->|"fully_qualified_name output to after or finalize input"| MOD
MOD -->|"fully_qualified_name output to after or finalize input"| SIBTASK
This module is standalone, but sits at the center of a rich sibling family: database/schema
are sourced from tf-mod-snowflake-database's/tf-mod-snowflake-schema's name outputs;
warehouse is consumed by reference from tf-mod-snowflake-warehouse's fully_qualified_name
(mutually exclusive with user_task_managed_initial_warehouse_size, which needs no sibling since
it targets serverless compute); error_integration is consumed from
tf-mod-snowflake-notification-integration's fully_qualified_name; sql_statement typically
interpolates a tf-mod-snowflake-stream instance's fully_qualified_name as a plain string (not
a structured input/output wire — the same distinction tf-mod-snowflake-stream's own README
draws for its on_table reference); and after/finalize are self-referential edges back to
other instances of this same module, forming a task DAG. Validated via the Mermaid Chart MCP
before embedding.
flowchart LR
classDef moduleNode fill:#29B5E8,color:#ffffff,stroke:#1b7ea6,stroke-width:1px;
classDef keystoneNode fill:#11567F,color:#ffffff,stroke:#0b3e5c,stroke-width:1px;
classDef neutralNode fill:#eef1f5,color:#1a1a1a,stroke:#c7ccd4,stroke-width:1px;
subgraph INPUTS["Typed inputs"]
direction TB
I1["database / name / schema / sql_statement (required)"]:::neutralNode
I2["started"]:::neutralNode
I3["schedule / after / finalize (mutually exclusive trigger)"]:::neutralNode
I4["warehouse / user_task_managed_initial_warehouse_size (mutually exclusive compute)"]:::neutralNode
I5["serverless_task_max_statement_size / serverless_task_min_statement_size"]:::neutralNode
I6["when / error_integration / config / allow_overlapping_execution"]:::neutralNode
I7["suspend_task_after_num_failures / task_auto_retry_attempts / user_task_timeout_ms / user_task_minimum_trigger_interval_in_seconds"]:::neutralNode
I8["target_completion_interval"]:::neutralNode
I9["comment"]:::neutralNode
end
RES["snowflake_task.this"]:::keystoneNode
subgraph OUTPUTS["Outputs"]
direction TB
O1["fully_qualified_name"]:::neutralNode
O2["name"]:::neutralNode
O3["id"]:::neutralNode
end
INPUTS -->|"rendered 1:1, no for_each (standalone)"| RES
RES -->|"computed attributes"| OUTPUTS
Resource inventory: exactly one resource, snowflake_task.this. No for_each, no count,
no child resources of any kind — this is a standalone module. Validated via the Mermaid Chart MCP
before embedding.
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
snowflakedb/snowflake provider |
~> 2.17 (resolved to v2.18.0 at authoring time) |
| Provider block | None — the caller's root module configures it, including any role alias |
Schema notes that bite (verified against the live v2.18.0 schema):
scheduleconflicts with bothfinalizeandafter(confirmed in the provider's own schema text);finalize/aftermutual exclusivity is this module's own inference from Snowflake's task-graph model, not a directly-confirmed schema fact — see Architecture Notes.warehouseanduser_task_managed_initial_warehouse_sizeare mutually exclusive (confirmed directly in the schema text).allow_overlapping_executionis a STRING field ("true"/"false"/"default"), not a boolean — this module exposes it as a realbooland converts it inmain.tf.configis wrapped in$$...$$by the provider itself; the literal substring$$is disallowed inside the value.config,show_output.config, andshow_output.definitionare not marked sensitive by the provider — do not put regulated data in them.user_task_managed_initial_warehouse_size's own schema documentation is truncated in the live v2.18.0 docs (the live provider schema returned a literal unrendered"%s"for its legal-value list) — this module infers the same enum documented in full forserverless_task_max_statement_size/serverless_task_min_statement_size.target_completion_intervalis a nested block (hours/minutes/seconds), not the bare string the resource's Example Usage block misleadingly shows — the authoritative Schema section defines it as a block, and this module follows the Schema section.- The resource also exposes a generic Terraform Plugin Framework
timeouts {}meta-argument (create/delete/read/update). This module deliberately does not surface it — the Snowflake universal tail in this library iscommentonly.
CREATE TASKon the schema (to create the keystonesnowflake_task.this).USAGEon the database and schema.USAGEon the warehouse named inwarehouse, if set (grant viatf-mod-snowflake-role-grant-bundle, not inside this module).EXECUTE TASKat the account level — required to resume (started = true) or otherwise execute a task, and transitively for any task this task lists as a predecessor viaafter.OWNERSHIP(or the ability to acquire it) on the task for most lifecycle operations beyond initial creation.
⚠️ Flagged for human/security review: Snowflake's account-privilege documentation (not the Terraform provider's schema docs, which this authoring pass is grounded in and which do not model privilege requirements at all) additionally documents anEXECUTE MANAGED TASKaccount-level privilege specifically for running serverless tasks (nowarehouseset). This module's authoring process could not independently verify that requirement against ground truth — confirm the exact privilege set for serverless tasks against Snowflake's current account-privilege documentation, or a live account, before assumingEXECUTE TASKalone is sufficient. SeeSCOPE.mdfor the full callout.
- No preview feature flag required —
snowflake_taskis a GA ("Stable") resource as of provider v2.18.0. - The target
database/schemamust already exist before this module applies. - Any
warehouseorerror_integrationreferenced must already exist before this module applies. - Any task referenced in
afterorfinalizemust already exist (or be created in the sameapply, with Terraform's dependency graph ordering creation via thefully_qualified_namereference) before this task can be started.
tf-mod-snowflake-task/
├── providers.tf # required_providers + required_version — no provider {} block
├── variables.tf # deeply-typed variable schema, secure defaults, universal tail (comment)
├── main.tf # the total renderer — one keystone `this`, no children
├── outputs.tf # fully_qualified_name, then name, then id
├── README.md # this file
├── SCOPE.md # lightweight standalone spec (design intent, privileges, prerequisites, emits, gotchas)
└── examples/ # runnable example call sites
module "nightly_refresh" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = module.reporting_db.name
schema = module.reporting_schema.name
name = "TASK_NIGHTLY_REFRESH"
warehouse = module.wh_etl.fully_qualified_name
sql_statement = "CALL REFRESH_REPORTING_TABLES"
schedule = { using_cron = "0 2 * * *" }
}The caller's root module configures the snowflake provider (account identifier, authentication,
and any role alias) — this module never declares a provider {} block or a credential-shaped
variable. The empty/minimal call produces started = false (suspended) — the caller must
explicitly set started = true after reviewing the task.
Consumes
| Input | Type | Source module |
|---|---|---|
database |
string (name) |
tf-mod-snowflake-database |
schema |
string (name) |
tf-mod-snowflake-schema |
warehouse |
string (optional, fully_qualified_name) |
tf-mod-snowflake-warehouse |
error_integration |
string (optional, fully_qualified_name) |
tf-mod-snowflake-notification-integration |
after / finalize |
set(string) / string (optional, fully_qualified_name) |
Another instance of this same module |
sql_statement (typically) |
string (plain interpolation, not a structured wire) |
tf-mod-snowflake-stream |
Emits
| Output | Description | Consumed by |
|---|---|---|
fully_qualified_name |
The task's fully-qualified identifier | Another instance of this same module (after/finalize) |
name |
The task's bare name | Documentation / display only |
id |
The task's Terraform resource id | Import / drift-detection tooling |
1 · Minimal call — suspended by secure default
module "task_minimal" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_MINIMAL"
warehouse = "WH_ETL"
sql_statement = "SELECT 1"
}
⚠️ starteddefaults tofalse(suspended) — this task will not run on any schedule until the caller explicitly setsstarted = true, after reviewing the SQL, schedule, and any DAG wiring. A newly-applied task must never silently start executing SQL against a live account.
2 · Standalone scheduled task — minutes interval
module "task_every_five_minutes" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_REFRESH_5MIN"
warehouse = "WH_ETL"
sql_statement = "CALL REFRESH_STAGING"
started = true
schedule = { minutes = 5 }
}💡
scheduleis a single nested object; exactly one ofhours,minutes,seconds, orusing_cronmust be set — enforced atterraform validatetime.
3 · Standalone scheduled task — cron expression
module "task_nightly_cron" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_NIGHTLY_CRON"
warehouse = "WH_ETL"
sql_statement = "CALL NIGHTLY_ROLLUP"
started = true
schedule = { using_cron = "0 2 * * *" }
}ℹ️
using_cronsupports a subset of standard cron syntax per the provider's own documentation; this module validates only that it is the sole non-null sub-field ofschedule, not the cron expression's own syntax.
4 · Two-task DAG — root task with schedule, child task with after
module "task_dag_root" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_DAG_ROOT"
warehouse = "WH_ETL"
sql_statement = "CALL LOAD_RAW"
started = true
schedule = { hours = 1 }
}
module "task_dag_child" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_DAG_CHILD"
warehouse = "WH_ETL"
sql_statement = "CALL TRANSFORM_RAW"
started = true
after = [module.task_dag_root.fully_qualified_name]
}
⚠️ scheduleandafterare mutually exclusive, enforced atterraform validatetime — the root task carries the schedule; the child task carriesafterand must NOT also setschedule. This module does not verify DAG-wide invariants (e.g. thattask_dag_rootreally is the graph's only root) — that discipline is the caller's responsibility across however many instances are composed.
5 · Finalizer task
module "task_dag_finalizer" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_DAG_FINALIZER"
warehouse = "WH_ETL"
sql_statement = "CALL CLEANUP_AND_NOTIFY"
started = true
finalize = module.task_dag_root.fully_qualified_name
}
⚠️ finalizeis mutually exclusive with bothscheduleandafter— a finalizer task runs only after every other task in the graph rooted attask_dag_rootcompletes.
6 · Serverless task — Snowflake-managed compute
module "task_serverless" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_SERVERLESS"
sql_statement = "CALL LIGHTWEIGHT_REFRESH"
started = true
schedule = { minutes = 15 }
user_task_managed_initial_warehouse_size = "XSMALL"
}
⚠️ warehouseanduser_task_managed_initial_warehouse_sizeare mutually exclusive, enforced atterraform validatetime — omitwarehouseentirely to use Snowflake-managed serverless compute, optionally hinting an initial size for the first few runs.
7 · Serverless task with statement-size bounds and a target completion interval
module "task_serverless_bounded" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_SERVERLESS_BOUNDED"
sql_statement = "CALL VARIABLE_LOAD_JOB"
started = true
schedule = { minutes = 30 }
serverless_task_min_statement_size = "XSMALL"
serverless_task_max_statement_size = "LARGE"
target_completion_interval = { minutes = 20 }
}ℹ️
serverless_task_max_statement_size/serverless_task_min_statement_sizeandtarget_completion_intervalare task-specific fields this module's scaffold omitted from its original field list — added during authoring since they are genuinely task-specific rather than generic session parameters. Not cross-validated against each other by this module; an inconsistent min/max pair surfaces as a Snowflake API error at apply time.
8 · Task gated by a boolean SQL condition
module "task_conditional" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_CONDITIONAL_REFRESH"
warehouse = "WH_ETL"
sql_statement = "CALL PROCESS_NEW_ROWS"
started = true
schedule = { minutes = 10 }
when = "SYSTEM$STREAM_HAS_DATA('ANALYTICS.STAGING.RAW_EVENTS_STREAM')"
}💡 If
whenevaluates false at a scheduled occurrence, the task skips that run — and any task naming this one as a predecessor viaafteralso doesn't run.
9 · Task with a shared JSON config payload
module "task_with_config" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_ML_SCORING"
warehouse = "WH_ETL"
sql_statement = "CALL RUN_SCORING_JOB"
started = true
schedule = { hours = 6 }
config = jsonencode({ output_dir = "/temp/", learning_rate = 0.1 })
}🔒
configis validated as well-formed JSON and as not containing the literal substring$$atterraform validatetime — but it is not marked sensitive by the provider. Do not put member/borrower PII or other regulated data in this value.
10 · Task with error-notification integration
module "task_with_error_notifications" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_CRITICAL_LOAD"
warehouse = "WH_ETL"
sql_statement = "CALL CRITICAL_LOAD"
started = true
error_integration = "NI_TASK_FAILURES"
schedule = { hours = 1 }
}💡
error_integrationis typically wired from a siblingtf-mod-snowflake-notification- integrationinstance'sfully_qualified_nameoutput — see the end-to-end composition below.
11 · Task allowing overlapping executions
module "task_overlap_allowed" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_LONG_RUNNING"
warehouse = "WH_ETL"
sql_statement = "CALL LONG_RUNNING_JOB"
started = true
allow_overlapping_execution = true
schedule = { minutes = 5 }
}
⚠️ allow_overlapping_executiondefaults tofalsein this module (matching Snowflake's own effective account default) — a caller must opt in explicitly, as shown here, to permit multiple concurrent runs of this task's DAG.
12 · Task with retry, timeout, and auto-suspend tuning
module "task_tuned" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_FLAKY_UPSTREAM"
warehouse = "WH_ETL"
sql_statement = "CALL SYNC_FROM_FLAKY_UPSTREAM"
started = true
schedule = { minutes = 15 }
suspend_task_after_num_failures = 5
task_auto_retry_attempts = 3
user_task_timeout_ms = 1800000
user_task_minimum_trigger_interval_in_seconds = 60
}ℹ️
suspend_task_after_num_failuresdefaults to0(the provider's own documented default — never auto-suspend).task_auto_retry_attempts,user_task_timeout_ms, anduser_task_minimum_trigger_interval_in_secondsdefault tonull— the live schema documents no explicit computed default for any of the three, sonulldefers to Snowflake's own account/object-level default rather than this module guessing a value.
13 · Hours-based schedule with a comment
module "task_hourly_with_comment" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_HOURLY_SYNC"
warehouse = "WH_ETL"
sql_statement = "CALL HOURLY_SYNC"
started = true
comment = "Hourly sync of upstream reference data"
schedule = { hours = 1 }
}ℹ️
commentis the only universal-tail variable this provider carries — notags, notimeouts. This example also demonstrates the thirdscheduleinterval variant (hours), alongside theminutesandusing_cronvariants shown earlier.
14 · Rejecting a malformed call (illustrative — fails terraform validate)
module "task_invalid_example" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_INVALID"
warehouse = "WH_ETL"
sql_statement = "SELECT 1"
schedule = { minutes = 5 }
after = [module.task_dag_root.fully_qualified_name] # invalid: conflicts with schedule
}
⚠️ This call fails atterraform validatewith an actionable error namingscheduleandafter— never as an opaque Snowflake API error atapplytime. Included here to demonstrate the type-as-contract philosophy, not as a call site to actually use.
15 · 🏗️ End-to-end composition: stream, warehouse, and notification integration wired into one task
module "raw_events_stream" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-stream.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "RAW_EVENTS_STREAM"
on_table = "ANALYTICS.STAGING.RAW_EVENTS"
}
module "wh_etl" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-warehouse.git?ref=v1.0.0"
name = "WH_ETL"
warehouse_size = "SMALL"
}
module "ni_task_failures" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-notification-integration.git?ref=v1.0.0"
name = "NI_TASK_FAILURES"
#... notification-provider-specific configuration
}
module "task_process_stream" {
source = "git::https://github.com/microsoftexpert/tf-mod-snowflake-task.git?ref=v1.0.0"
database = "ANALYTICS"
schema = "STAGING"
name = "TASK_PROCESS_STREAM"
warehouse = module.wh_etl.fully_qualified_name
# ℹ️ sql_statement is a plain string interpolation of the stream's fully_qualified_name — not
# a structured input/output wire between the two modules, the same distinction
# tf-mod-snowflake-stream's own README draws for its on_table reference.
sql_statement = "INSERT INTO ANALYTICS.STAGING.PROCESSED_EVENTS SELECT * FROM ${module.raw_events_stream.fully_qualified_name}"
error_integration = module.ni_task_failures.fully_qualified_name
when = "SYSTEM$STREAM_HAS_DATA('${module.raw_events_stream.fully_qualified_name}')"
started = true
schedule = { minutes = 5 }
}💡 This wires three real sibling relationships:
tf-mod-snowflake-stream'sfully_qualified_nameinterpolated into this task'ssql_statementandwhen(plain string interpolation, not a structured wire),tf-mod-snowflake-warehouse'sfully_qualified_nameinto this task'swarehouseinput, andtf-mod-snowflake-notification-integration'sfully_qualified_nameinto this task'serror_integrationinput.
Grouped summary: identity (database, name, schema, sql_statement), lifecycle
(started), trigger mechanism (schedule, after, finalize — mutually exclusive), compute
source (warehouse, user_task_managed_initial_warehouse_size — mutually exclusive;
serverless_task_max_statement_size, serverless_task_min_statement_size), execution tuning
(when, error_integration, allow_overlapping_execution, config,
suspend_task_after_num_failures, task_auto_retry_attempts, user_task_timeout_ms,
user_task_minimum_trigger_interval_in_seconds, target_completion_interval), and the universal
tail (comment).
Full variable schema
| Variable | Type | Default | Notes |
|---|---|---|---|
database |
string |
— (required) | Bare identifier; avoid |, ., ". |
name |
string |
— (required) | Bare identifier; avoid |, ., ". |
schema |
string |
— (required) | Bare identifier; avoid |, ., ". |
sql_statement |
string |
— (required) | Non-empty; Snowflake validates the SQL itself. |
started |
bool |
false |
House secure default — suspended until reviewed. |
after |
set(string) |
[] |
Predecessor task fully_qualified_names; mutually exclusive with schedule/finalize. |
schedule |
object({hours,minutes,seconds,using_cron}) |
null |
Exactly one sub-field when set; mutually exclusive with after/finalize. |
finalize |
string |
null |
Root task fully_qualified_name; mutually exclusive with schedule/after. |
warehouse |
string |
null |
fully_qualified_name; mutually exclusive with user_task_managed_initial_warehouse_size. |
when |
string |
null |
Boolean SQL gating expression. |
error_integration |
string |
null |
fully_qualified_name of a notification integration. |
allow_overlapping_execution |
bool |
false |
Converted to provider string "true"/"false" in main.tf. |
config |
string |
null |
Must be valid JSON and must not contain $$. |
comment |
string |
null |
Universal tail. |
suspend_task_after_num_failures |
number |
0 |
Provider's own documented default. |
task_auto_retry_attempts |
number |
null |
No documented provider default; defers to account default. |
user_task_managed_initial_warehouse_size |
string |
null |
Size enum inferred from sibling fields (see gotchas). |
serverless_task_max_statement_size |
string |
null |
Full size enum confirmed in schema. |
serverless_task_min_statement_size |
string |
null |
Full size enum confirmed in schema. |
user_task_timeout_ms |
number |
null |
No documented provider default; defers to account default. |
user_task_minimum_trigger_interval_in_seconds |
number |
null |
No documented provider default; defers to account default. |
target_completion_interval |
object({hours,minutes,seconds}) |
null |
Exactly one sub-field when set. |
| Output | Description | Sensitive / Conditional |
|---|---|---|
fully_qualified_name |
The task's fully-qualified identifier | No |
name |
The task's bare name | No |
id |
The task's Terraform resource id | No |
started = falsesecure default: a deliberate house choice (the provider has no computed default of its own for this required field) per this suite's "Object dropping protection"/ cautious-default philosophy — a newly-applied task must never silently start executing SQL against a live account on its first apply. The caller must setstarted = trueexplicitly, and only after reviewing the task's SQL, schedule, and DAG wiring.- The schedule/finalize/after mutual-exclusivity rule is the single most important validation
in this module. Confirmed directly against the live provider schema text for
schedule: "(Conflicts with finalize and after;...)" — a fact from the provider's own docs. The schema text does not separately confirm thatfinalizeandafterconflict with each other; this module enforces that third leg anyway, as a reasonable inference from Snowflake's task-graph model (a finalizer task is a distinct role, not composable with predecessor linkage) — not a directly-confirmed schema fact. Seevariables.tf's comments on theschedule/finalizevalidation blocks for the precise fact-vs-inference boundary, and re-verify against a live plan if a use case genuinely requires combiningafterandfinalize. warehouse/user_task_managed_initial_warehouse_sizemutual exclusivity: confirmed directly in the schema text (warehouse's own description states "Conflicts with user_task_managed_initial_warehouse_size"). Omitting both is valid — Snowflake-managed serverless compute with no initial size hint.allow_overlapping_executionbool-to-string conversion: the underlying field is a STRING ("true"/"false"/"default"), not a boolean. This module models it as a realbool, defaultingfalse, and never surfaces"default"as a caller-facing option —main.tfperforms the conversion inline:var.allow_overlapping_execution ? "true": "false".- Deliberate v1.0.0 scope decision — generic session parameters omitted: the live
snowflake_taskresource exposes roughly 60 pass-through Snowflake session-parameter arguments (autocommit,timezone,statement_timeout_in_seconds,query_tag, everyclient_*/timestamp_*/date_*/time_*parameter, and dozens more). This module does not model any of them individually, and does not add a genericsession_parametersescape hatch either — the gap is left explicit rather than papered over. A future minor version could add either the individually-typed high-value parameters or a narrowly-scoped escape hatch; that decision is deliberately deferred, not an oversight. after/finalizeare self-referential: both are expected to be another instance of this same module'sfully_qualified_nameoutput. This module does not manage DAG-wide concerns (ensuring exactly one root task per graph, detecting orphaned finalizers, detecting cycles in theafterchain) — that discipline is the caller's responsibility across however many module instances they compose.for_eachnote: not applicable — this is a standalone module with no child/relationship records, so there is nofor_eachkey-stability concern to document here.
| Concern | Secure default | Opt-out (caller must be explicit) |
|---|---|---|
| Task lifecycle state | started = false — suspended until reviewed |
Caller sets started = true explicitly |
| Overlapping executions | false — one DAG instance at a time |
Caller sets allow_overlapping_execution = true explicitly |
| Future grants / grant scope | Not applicable — this module owns no grants | N/A |
| Object comment | null — no default disclosure of intent |
Caller sets comment explicitly |
| Suspend-after-failures | 0 — provider's own documented default (never auto-suspend) |
Caller sets an explicit non-zero value |
| Compute source | null/null — neither warehouse nor user_task_managed_initial_warehouse_size set implicitly |
Caller supplies one explicitly (or neither, for unbounded serverless) |
cd C:\GitHubCode\newsnowflakemodules\tf-mod-snowflake-task
terraform init -backend=false
terraform validate
terraform fmt -checkPin ?ref=v1.0.0 in every call site — never a branch. This is a plan-only authoring pipeline; a
human runs terraform apply from a governed CI pipeline.
terraform init -backend=false/terraform validate/terraform fmt -checkcover: type correctness (everyobject/scalar schema matches the live provider schema), HCL syntax, and every closed-enum / cross-variablevalidation {}block (schedule/finalize/after exclusivity, warehouse/user_task_managed_initial_warehouse_size exclusivity,configJSON well-formedness) catching a malformed input before any API call.- What these three commands do not cover: privilege sufficiency (e.g. whether the executing
role actually holds
CREATE TASK,EXECUTE TASK, or the flagged-for-reviewEXECUTE MANAGED TASK), object-existence dependencies (whether a referencedwarehouse,error_integration, or predecessor/finalizer task actually exists), DAG-wide invariants across multiple task instances, andDESC/SHOW-output-driven permadiffs. Only a realterraform planagainst a live Snowflake account exercises those — and per this library's plan-only posture, that step belongs to a human running it from governed CI.
$ terraform output
fully_qualified_name = "\"ANALYTICS\".\"STAGING\".\"TASK_NIGHTLY_REFRESH\""
name = "TASK_NIGHTLY_REFRESH"
id = "ANALYTICS|STAGING|TASK_NIGHTLY_REFRESH"
| Symptom | Cause | Fix |
|---|---|---|
terraform validate fails on schedule/finalize/after |
More than one of the three trigger mechanisms is set | Set exactly one: schedule for a standalone/root task, after for a child task, finalize for a finalizer task. |
terraform validate fails on warehouse/user_task_managed_initial_warehouse_size |
Both are set | Choose one — a named warehouse, or a serverless size hint — or omit both for unbounded serverless compute. |
terraform validate fails on config |
Value is not valid JSON, or contains the literal substring $$ |
Fix the JSON, or remove the $$ — the provider wraps the value in $$...$$ itself. |
Task never runs even though started = true |
schedule and after are both unset, and this is not a Triggered Task |
Set a schedule for a standalone/root task, or trigger it manually via the provider's snowflake_execute resource. |
| Apply fails resuming the task with an authorization error | Executing role lacks EXECUTE TASK (or, for serverless tasks, potentially EXECUTE MANAGED TASK — see the flagged privilege-review note in §7/SCOPE.md) |
Re-verify the executing role's privileges against Snowflake's current account-privilege documentation or a live account. |
Unexpected diff on comment/config every plan after import |
Import didn't populate an optional field in the generated config | Set the field explicitly to the live value shown in terraform show after import (a cross-cutting behavior noted for several resources in this provider, not confirmed specifically for snowflake_task). |
| Child task never runs after its predecessor completes | after references a task by bare name instead of fully_qualified_name, or the predecessor task itself is suspended (started = false) |
Wire module.task_x.fully_qualified_name into after, and confirm the predecessor's started = true. |
- Provider resource docs:
snowflake_task - Sibling modules:
tf-mod-snowflake-database,tf-mod-snowflake-schema,tf-mod-snowflake-warehouse,tf-mod-snowflake-notification-integration,tf-mod-snowflake-stream - This module's
SCOPE.md