Manages the lifecycle of a single Microsoft Fabric Notebook (
fabric_notebook) β display name, optional folder placement, an optional token-substitutable definition, and tag association β against themicrosoft/fabricprovider~> 1.12.0.
- π Creates and manages exactly one
fabric_notebookitem inside an existing Fabric workspace. - π Places the notebook at the workspace root by default, or under a governed folder when
folder_idis supplied. - 𧬠Models the notebook's
definitionas a well-typed, path-keyed map β mirroring the provider's own Attributes Map schema, following the pattern established by this batch's pattern-setter,tf-mod-fabric-lakehouse. - π Defaults
processing_modeto"None"(no template substitution) even though the provider's own native default is"GoTemplate"β no implicit token/parameter substitution happens unless a caller explicitly opts in. - π·οΈ Accepts an optional set of
fabric_tagGUIDs (tags) β a closed reference-set, not a free-form key/value tag map. - β±οΈ Exposes the Fabric universal
timeoutstail for per-operation overrides.
π‘ Why it matters: A notebook's
definitioncarries real business logic (transformation code that may read or write claims/financial data). Modelingdefinitionas a deeply-typed map β instead of a bare file-path string β means a caller's typo in a path key, an unsupportedtokens_delimiter, or an invalidprocessing_modefails atterraform validate, before any content ever reaches the Fabric API.
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
WS["tf-mod-fabric-workspace"]:::keystone
FOLD["tf-mod-fabric-folder"]:::sibling
TAG["tf-mod-fabric-tag"]:::sibling
NB["tf-mod-fabric-notebook"]:::this
WS -->|"workspace_id"| NB
FOLD -->|"folder_id (optional)"| NB
TAG -->|"tags (optional)"| NB
classDef this fill:#0F6CBD,color:#ffffff,stroke:#0F6CBD;
classDef keystone fill:#143551,color:#ffffff,stroke:#143551;
classDef sibling fill:#E5E7EB,color:#111111,stroke:#9CA3AF;
tf-mod-fabric-workspace is the required parent (workspace_id); tf-mod-fabric-folder and
tf-mod-fabric-tag are optional siblings this module consumes by id but never creates.
flowchart LR
subgraph Inputs
DN["display_name"]
WSID["workspace_id"]
FID["folder_id (optional)"]
FMT["format (optional)"]
DEF["definition map (optional)"]
TAGIDS["tags (optional)"]
TO["timeouts (optional)"]
end
RES["fabric_notebook.this"]:::this
DN --> RES
WSID --> RES
FID --> RES
FMT --> RES
DEF --> RES
TAGIDS --> RES
TO --> RES
RES -->|"id"| OUT_ID["id"]
RES -->|"display_name"| OUT_DN["display_name"]
classDef this fill:#0F6CBD,color:#ffffff,stroke:#0F6CBD;
Resource inventory: 1 resource β fabric_notebook.this (single instance; no child/relationship
records β this is a standalone module).
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
microsoft/fabric provider |
~> 1.12.0 (confirmed current at authoring time β reconfirm before reuse; the provider ships roughly every two weeks) |
| Provider configuration | None in this module β the caller configures provider "fabric" {} (auth, tenant) at the root |
Schema notes that bite:
definitionis a Terraform Plugin Framework Attributes Map, not a Block β it is assigned directly with=inmain.tf(a for-expression overvar.definition), never wrapped in adynamic "definition"block.timeoutsis likewise a nested Attributes object, not a Block, and is assigned directly too.- Accepted
definitionpath keys are format-dependent:format = "Default"accepts*.ipynb,notebook-content.py,notebook-content.r,notebook-content.scala,notebook-content.sql;format = "ipynb"accepts only*.ipynb;format = "py"accepts onlynotebook-content.py. A path key that doesn't match the chosen format is a provider/API-side rejection, not aterraform validate-time error β this module's variable typing validates the shape of each definition entry, not the path-key membership, so double-check the key against the chosenformatbefore applying. processing_mode's provider-native default is"GoTemplate"; this module's own default is"None"per this suite's Secure-by-default convention β no implicit template substitution unless a caller explicitly sets"GoTemplate"or"Parameters"on a given definition part.fabric_notebookhas nopropertiesblock in the live schema, unlikefabric_lakehouseandfabric_spark_job_definitionβ outputs are limited toidanddisplay_name.tagsschema note: this suite's naming conventions previously held that the fabric provider "has notagsargument anywhere in its schema." The live v1.12.0 schema contradicts this forfabric_notebookβ a realtagsargument exists (Set of String, "The set of tag IDs"). This module names its variabletagsdirectly, matching the provider's own argument name, per this suite's naming conventions (the current, corrected convention) β see Β§ Design Principles and this module'sSCOPE.mdfor the full flag.- This resource supports Service Principal authentication per the provider's own docs β no delegated/user-context auth exception applies here (unlike some Git integration and OneLake shortcut operations).
- Fabric workspace role: Contributor (or higher β Member, Admin) on the target workspace. Per Microsoft Learn's "Roles in workspaces in Microsoft Fabric" capability matrix, writing or deleting notebooks is permitted for Admin, Member, and Contributor roles; Viewer cannot create, modify, or delete notebooks. (Microsoft Learn: Roles in workspaces in Microsoft Fabric)
- Entra: the calling Service Principal or Managed Identity must be covered by the tenant's "Service
principals can use Fabric APIs" Developer setting (see Microsoft Fabric Prerequisites below) β without
it, no non-interactive
applyagainst anyfabric_*resource will succeed, regardless of workspace role. - If
folder_idis supplied, the caller's principal needs the same Contributor-or-higher role on the workspace that owns the target folder (folders inherit workspace-level roles; there is no separate folder-level RBAC surface in the current schema).
- The workspace's assigned capacity must be in
Activestate (not paused/suspended) for notebook creation and most read operations β a paused capacity has been observed to cause Terraform state to appear to "lose track" of capacity-hosted items until the capacity is resumed (see this suite's Region/Instance/Scope model convention). - The tenant-level "Service principals can use Fabric APIs" (or equivalent scoped-security-group) setting must be enabled in the Fabric Admin Portal before any Service Principal or Managed Identity can call the Fabric API at all β this is entirely outside Terraform's control and must be flipped once per tenant by a Fabric administrator.
- No Fabric trial/license prerequisite beyond an active (trial or paid) capacity backing the workspace.
tf-mod-fabric-notebook/
βββ providers.tf # required_version, fabric provider pin (~> 1.12.0); no provider {} block
βββ variables.tf # display_name, workspace_id, folder_id, format, definition, tags, timeouts
βββ main.tf # fabric_notebook.this β the sole keystone
βββ outputs.tf # id, display_name
βββ README.md # this file
βββ SCOPE.md # lightweight cross-module contract
βββ examples/
βββ basic/ # empty notebook, workspace-root placement
βββ complete/ # ipynb-format notebook with a definition part, folder, tags, timeouts
module "notebook" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-notebook.git?ref=v1.0.0"
display_name = "nb-claims-scratch-dev"
workspace_id = module.workspace.id
}The caller configures the fabric provider (Service Principal or Managed Identity, per this suite's
Authentication model convention) once at the root module β never inside this module.
Consumes
| Input | Type | Source module |
|---|---|---|
workspace_id |
string (required) |
tf-mod-fabric-workspace (id output) |
folder_id |
string (optional) |
tf-mod-fabric-folder (id output) |
tags |
set(string) (optional) |
tf-mod-fabric-tag (id outputs, once authored) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Notebook GUID | Downstream orchestration/data-plane tooling outside Terraform's direct reference graph |
display_name |
Notebook display name | Informational / cross-referencing |
1 Β· Minimal notebook β the safe empty call
module "notebook" {
source = "../../"
display_name = "nb-claims-scratch-dev"
workspace_id = "00000000-0000-0000-0000-000000000000"
}π‘ No
definition, nofolder_id, notags. Lands at the workspace root with no predefined content.
2 Β· Production naming convention
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-prod"
description = "Transforms raw claims events into the curated Delta layer. Owner: Data Engineering."
workspace_id = module.workspace.id
}βΉοΈ
display_nameencodes environment and purpose per this suite's naming conventions β this remains the primary governance surface even withtagsavailable.
3 Β· ipynb-format notebook with a single definition part
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
format = "ipynb"
definition = {
"notebook-content.ipynb" = {
source = "${path.module}/files/transform.ipynb"
}
}
}π‘
format = "ipynb"restrictsdefinitionto a single accepted key:*.ipynb.
4 Β· py-format notebook
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
format = "py"
definition = {
"notebook-content.py" = {
source = "${path.module}/files/transform.py"
}
}
}βΉοΈ
format = "py"accepts only the literal keynotebook-content.py.
5 Β· Default format with multiple simultaneous definition parts
module "notebook" {
source = "../../"
display_name = "nb-claims-multilang-dev"
workspace_id = module.workspace.id
format = "Default"
definition = {
"notebook-content.py" = {
source = "${path.module}/files/transform.py"
}
"notebook-content.sql" = {
source = "${path.module}/files/transform.sql"
}
}
}π‘
format = "Default"is the only format that accepts more than one simultaneous definition-part key.
6 Β· GoTemplate token substitution (explicit opt-out of the secure default)
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
format = "ipynb"
definition = {
"notebook-content.ipynb" = {
source = "${path.module}/files/transform.ipynb.tmpl"
processing_mode = "GoTemplate"
tokens = {
"MESSAGE" = "World"
"MyValue" = "Lorem Ipsum"
}
}
}
}π This module's own default for
processing_modeis"None"β this example is an explicit, caller-initiated opt-in to template substitution, matching the provider's own native default.
7 Β· Parameters processing mode
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
format = "ipynb"
definition = {
"notebook-content.ipynb" = {
source = "${path.module}/files/transform.ipynb"
processing_mode = "Parameters"
parameters = [
{
type = "JsonPathReplace"
find = "$.metadata.kernelspec.display_name"
value = "Python 3.10"
},
{
type = "TextReplace"
find = "OldValue"
value = "NewValue"
}
]
}
}
}π Another explicit opt-out of the secure
"None"default βparametersis only evaluated whenprocessing_mode = "Parameters".
8 Β· Custom tokens_delimiter
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
format = "ipynb"
definition = {
"notebook-content.ipynb" = {
source = "${path.module}/files/transform.ipynb.tmpl"
tokens_delimiter = "<<>>"
processing_mode = "GoTemplate"
tokens = {
"MESSAGE" = "World"
}
}
}
}βΉοΈ
tokens_delimiterdefaults to"{{}}"(the provider default); this example opts into"<<>>".
9 Β· Explicit folder_id placement
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
folder_id = module.folder_curated.id
}π‘ Omitting
folder_idplaces the notebook at the workspace root (the secure/least-surprise default); supplying it nests the notebook under a governed folder hierarchy.
10 Β· tags association
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
tags = [
"33333333-3333-3333-3333-333333333333",
]
}π
tagsreferences pre-existingfabric_tagGUIDs β this module never creates tags itself. See Β§ Design Principles for the schema note on this argument.
11 Β· Bootstrap-only definition (definition_update_enabled = false)
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
format = "ipynb"
definition_update_enabled = false
definition = {
"notebook-content.ipynb" = {
source = "${path.module}/files/transform.ipynb"
}
}
}βΉοΈ
definition_update_enabled = falsebootstraps the notebook's content once, then leaves it alone β subsequent source-file changes are not pushed back into the item. Useful when notebook authors iterate on content directly in the Fabric UI after initial provisioning.
12 Β· Custom per-operation timeouts
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-dev"
workspace_id = module.workspace.id
timeouts = {
create = "30m"
update = "20m"
}
}13 Β· Fully governed notebook β folder, tags, description, timeouts combined
module "notebook" {
source = "../../"
display_name = "nb-claims-transform-prod"
description = "Transforms raw claims events into the curated Delta layer. Owner: Data Engineering."
workspace_id = module.workspace.id
folder_id = module.folder_curated.id
format = "ipynb"
definition = {
"notebook-content.ipynb" = {
source = "${path.module}/files/transform.ipynb"
}
}
tags = [
module_tag_pii_id,
]
timeouts = {
create = "30m"
}
}14 Β· ποΈ End-to-end composition β workspace + folder + lakehouse feeding a transform notebook
module "workspace" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-workspace.git?ref=v1.0.0"
display_name = "ws-core-bronze-prod"
description = "Bronze-layer medallion workspace, core domain β prod. Owner: Data Engineering."
}
module "folder_curated" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-folder.git?ref=v1.0.0"
display_name = "curated"
workspace_id = module.workspace.id
}
module "lakehouse" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-lakehouse.git?ref=v1.0.0"
display_name = "lh-claims-curated-prod"
workspace_id = module.workspace.id
folder_id = module.folder_curated.id
}
module "notebook" {
source = "git::https://github.com/microsoftexpert/tf-mod-fabric-notebook.git?ref=v1.0.0"
display_name = "nb-claims-transform-prod"
description = "Transforms raw claims events landed in ${module.lakehouse.id} into curated Delta tables."
workspace_id = module.workspace.id
folder_id = module.folder_curated.id
format = "ipynb"
definition = {
"notebook-content.ipynb" = {
source = "${path.module}/files/claims-transform.ipynb.tmpl"
tokens = {
"LakehouseID" = module.lakehouse.id
}
}
}
}βΉοΈ There is no direct Terraform-managed reference between a notebook and the lakehouse it reads from/writes to today β the coupling is expressed through the notebook's own code (e.g. a
LakehouseIDtoken baked into its definition at plan time viatokens), not a provider-level relationship resource. This composition wirestf-mod-fabric-workspaceβtf-mod-fabric-folderβtf-mod-fabric-lakehouseoutputs into this module'sworkspace_id/folder_idinputs and threads the lakehouse'sidthrough as a definition token.
Summary β display_name and workspace_id are required; every other input is optional with a secure
default (folder_id β workspace root, format β provider-native, definition β empty, tags β empty,
timeouts β provider defaults).
Full input schema
| Name | Type | Default | Description |
|---|---|---|---|
display_name |
string |
β (required) | Notebook display name; encode environment/purpose |
description |
string |
null |
Free-text ownership/purpose context |
workspace_id |
string |
β (required) | Parent workspace GUID |
folder_id |
string |
null |
Optional folder GUID; null = workspace root |
format |
string |
null |
One of "Default", "ipynb", "py" |
definition |
map(object({...})) |
{} |
Path-keyed definition parts β see below |
definition_update_enabled |
bool |
true |
Push source/token changes back into the item |
tags |
set(string) |
[] |
fabric_tag GUIDs to associate |
timeouts |
object({...}) |
null |
Per-operation duration overrides |
definition map value object:
{
source = string # required
tokens_delimiter = optional(string, "{{}}")
tokens = optional(map(string))
processing_mode = optional(string, "None") # module default overrides provider's "GoTemplate"
parameters = optional(list(object({
type = string # "JsonPathReplace" | "TextReplace"
find = string
value = string
})), [])
}timeouts object:
{
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
}| Output | Description | Sensitive |
|---|---|---|
id |
Notebook GUID (primary output) | No |
display_name |
Notebook display name | No |
definitionis rendered as a direct for-expression attribute assignment βdefinition = length(var.definition) > 0 ? { for path, part in var.definition: path => {... } }: nullβ never adynamic "definition"block, because the provider documentsdefinitionas an Attributes Map, not a Block. The same is true oftimeouts, rendered as a direct conditional object assignment rather thandynamic "timeouts".- Definition parts are keyed by their file path (
"notebook-content.ipynb", etc.), which is exactly thefor_each/map-key stability this library requires elsewhere for child collections β adding a second definition part never disturbs the first. tags = length(var.tags) > 0 ? var.tags: nullβ an empty set is sent asnullrather than an empty set literal, avoiding a spurious diff against a resource that has never had tags assigned.- There is no
propertiesblock on this resource (unlikefabric_lakehouse/fabric_spark_job_definition) βoutputs.tfis deliberately limited toidanddisplay_name.
| Concern | Safe default | Opt-out |
|---|---|---|
| Folder placement | folder_id = null β workspace root |
Supply folder_id |
| Template substitution | processing_mode = "None" (module default; provider's own default is "GoTemplate") |
Set "GoTemplate" or "Parameters" explicitly per definition part |
| Tag association | tags = [] |
Supply one or more fabric_tag GUIDs |
| Definition content | definition = {} (no predefined content) |
Supply one or more definition-part entries |
tags is named to match the provider's own argument directly β this suite's earlier naming-convention wording ("no
tags argument anywhere in its schema") predates the live v1.12.0 schema, which now exposes a genuine
tags argument on fabric_notebook as a closed reference-set to fabric_tag GUIDs, not an azurerm-style
key/value map. See SCOPE.md Β§ Provider gotchas for the full flag.
cd C:\GitHubCode\newfabricmodules\tf-mod-fabric-notebook
terraform init -backend=false
terraform validate
terraform fmt -checkPin consumption at ?ref=v1.0.0 β never a branch. This module is plan-only; a human runs terraform apply
from a reviewed, approved CI pipeline.
Caught by validate/fmt:
- Type errors in
definition,timeouts, or any nested object. - Invalid
format,tokens_delimiter,processing_mode, orparameters[].typeenum values (all enforced viavalidation {}blocks with the complete legal value list inerror_message). - Malformed or missing required fields inside a
definitionentry (sourceis required).
Only exercised by a live plan/apply against a real tenant:
- Whether the assigned capacity is
Active(not paused). - Whether the calling principal actually holds Contributor-or-higher on the target workspace.
- Whether a
definitionpath key actually matches the accepted set for the chosenformatβ this module validates the shape of each entry, not path-key membership, so a mismatched key surfaces only atapplytime as a provider/API-side error. - Actual notebook execution behavior (kernel, language runtime, referenced Lakehouse availability).
$ terraform output
id = "9f2c1a4e-8b3d-4c5f-9a1b-2d3e4f5a6b7c"
display_name = "nb-claims-transform-prod"
| Symptom | Cause | Fix |
|---|---|---|
apply hangs or times out reading/writing the notebook |
Backing capacity is paused/suspended | Resume the capacity before retrying; consider a lifecycle { postcondition } guard on the data "fabric_capacity" feeding the workspace's capacity_id |
| Provider/API error referencing an unexpected definition part | definition path key doesn't match the accepted set for the chosen format (e.g. "notebook-content.py" under format = "ipynb") |
Confirm the key against Β§ Provider/Versions' accepted-key table for the current format |
403/401 on apply despite correct workspace role |
Tenant "Service principals can use Fabric APIs" setting not enabled, or SPN not in the scoped security group | Have a Fabric tenant admin verify/enable the setting in the Fabric Admin Portal |
| Token substitution appears to do nothing | processing_mode left at the module's secure default ("None") |
Explicitly set processing_mode = "GoTemplate" (or "Parameters") on the relevant definition part |
| Terraform wants to replace the notebook unexpectedly | format changed after creation |
Confirm whether format is force-new for this resource in the live schema before changing it on an existing notebook; treat as an apply-time risk to review, not merely a plan diff |
fabric_notebookprovider docstf-mod-fabric-workspaceβ required parent,workspace_idtf-mod-fabric-folderβ optional parent,folder_idtf-mod-fabric-tagβ optional sibling,tags(once authored)tf-mod-fabric-lakehouseβ sibling pattern-setter for thedefinition-map modeling approach- This module's
SCOPE.md