Skip to content

microsoftexpert/tf-mod-fabric-notebook

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“Š Microsoft Fabric Notebook Terraform Module

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 the microsoft/fabric provider ~> 1.12.0.

Terraform Provider Module Type Resources Posture


🧩 Overview

  • πŸ““ Creates and manages exactly one fabric_notebook item inside an existing Fabric workspace.
  • πŸ“ Places the notebook at the workspace root by default, or under a governed folder when folder_id is supplied.
  • 🧬 Models the notebook's definition as 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_mode to "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_tag GUIDs (tags) β€” a closed reference-set, not a free-form key/value tag map.
  • ⏱️ Exposes the Fabric universal timeouts tail for per-operation overrides.

πŸ’‘ Why it matters: A notebook's definition carries real business logic (transformation code that may read or write claims/financial data). Modeling definition as a deeply-typed map β€” instead of a bare file-path string β€” means a caller's typo in a path key, an unsupported tokens_delimiter, or an invalid processing_mode fails at terraform validate, before any content ever reaches the Fabric API.


❀️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

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!


πŸ—ΊοΈ Where this fits

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;
Loading

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.


🧬 What this builds

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;
Loading

Resource inventory: 1 resource β€” fabric_notebook.this (single instance; no child/relationship records β€” this is a standalone module).


βœ… Provider / Versions

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:

  • definition is a Terraform Plugin Framework Attributes Map, not a Block β€” it is assigned directly with = in main.tf (a for-expression over var.definition), never wrapped in a dynamic "definition" block. timeouts is likewise a nested Attributes object, not a Block, and is assigned directly too.
  • Accepted definition path 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 only notebook-content.py. A path key that doesn't match the chosen format is a provider/API-side rejection, not a terraform 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 chosen format before 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_notebook has no properties block in the live schema, unlike fabric_lakehouse and fabric_spark_job_definition β€” outputs are limited to id and display_name.
  • tags schema note: this suite's naming conventions previously held that the fabric provider "has no tags argument anywhere in its schema." The live v1.12.0 schema contradicts this for fabric_notebook β€” a real tags argument exists (Set of String, "The set of tag IDs"). This module names its variable tags directly, matching the provider's own argument name, per this suite's naming conventions (the current, corrected convention) β€” see Β§ Design Principles and this module's SCOPE.md for 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).

πŸ”‘ Required Fabric / Entra Permissions

  • 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 apply against any fabric_* resource will succeed, regardless of workspace role.
  • If folder_id is 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).

Microsoft Fabric Prerequisites

  • The workspace's assigned capacity must be in Active state (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.

πŸ“ Module Structure

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

βš™οΈ Quick Start

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.


πŸ”Œ Cross-Module Contract

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

πŸ“š Example Library

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, no folder_id, no tags. 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_name encodes environment and purpose per this suite's naming conventions β€” this remains the primary governance surface even with tags available.

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" restricts definition to 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 key notebook-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_mode is "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 β€” parameters is only evaluated when processing_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_delimiter defaults 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_id places 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",
  ]
}

πŸ”’ tags references pre-existing fabric_tag GUIDs β€” 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 = false bootstraps 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 LakehouseID token baked into its definition at plan time via tokens), not a provider-level relationship resource. This composition wires tf-mod-fabric-workspace β†’ tf-mod-fabric-folder β†’ tf-mod-fabric-lakehouse outputs into this module's workspace_id/folder_id inputs and threads the lakehouse's id through as a definition token.


πŸ“₯ Inputs

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)
}

🧾 Outputs

Output Description Sensitive
id Notebook GUID (primary output) No
display_name Notebook display name No

🧠 Architecture Notes

  • definition is rendered as a direct for-expression attribute assignment β€” definition = length(var.definition) > 0 ? { for path, part in var.definition: path => {... } }: null β€” never a dynamic "definition" block, because the provider documents definition as an Attributes Map, not a Block. The same is true of timeouts, rendered as a direct conditional object assignment rather than dynamic "timeouts".
  • Definition parts are keyed by their file path ("notebook-content.ipynb", etc.), which is exactly the for_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 as null rather than an empty set literal, avoiding a spurious diff against a resource that has never had tags assigned.
  • There is no properties block on this resource (unlike fabric_lakehouse/fabric_spark_job_definition) β€” outputs.tf is deliberately limited to id and display_name.

🧱 Design Principles

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.


πŸš€ Runbook

cd C:\GitHubCode\newfabricmodules\tf-mod-fabric-notebook
terraform init -backend=false
terraform validate
terraform fmt -check

Pin 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.


πŸ§ͺ Testing

Caught by validate/fmt:

  • Type errors in definition, timeouts, or any nested object.
  • Invalid format, tokens_delimiter, processing_mode, or parameters[].type enum values (all enforced via validation {} blocks with the complete legal value list in error_message).
  • Malformed or missing required fields inside a definition entry (source is 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 definition path key actually matches the accepted set for the chosen format β€” this module validates the shape of each entry, not path-key membership, so a mismatched key surfaces only at apply time as a provider/API-side error.
  • Actual notebook execution behavior (kernel, language runtime, referenced Lakehouse availability).

πŸ’¬ Example Output

$ terraform output

id = "9f2c1a4e-8b3d-4c5f-9a1b-2d3e4f5a6b7c"
display_name = "nb-claims-transform-prod"

πŸ” Troubleshooting

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

πŸ”— Related Docs

  • fabric_notebook provider docs
  • tf-mod-fabric-workspace β€” required parent, workspace_id
  • tf-mod-fabric-folder β€” optional parent, folder_id
  • tf-mod-fabric-tag β€” optional sibling, tags (once authored)
  • tf-mod-fabric-lakehouse β€” sibling pattern-setter for the definition-map modeling approach
  • This module's SCOPE.md

Packages

 
 
 

Contributors

Languages