The generic dataset β any type, properties as raw JSON, and a name validator that almost never fires. Targets
hashicorp/azurerm ~> 4.0.
- π¦ Describes a dataset of any Data Factory type β the type is a string and its properties are raw JSON.
- π The provider wraps your JSON in a
typePropertiesenvelope, so supply the inner object only. A double-wrap is detected and reported. - π The JSON is validated for parseability only. Constraining a vendor-defined shape would reject legal payloads as they evolve β and block
terraform destroy. - π΄ The name validator is effectively inert: its regex rejects only a name made entirely of its "forbidden" characters, so
my/dataset:v1passes. - π The linked service is named by string, and nothing verifies it exists or suits the type.
- β±οΈ Timeouts here are the usual thirty minutes β unlike this family's credential resources, whose four all default to five.
π‘ Why it matters: almost nothing about this resource is checked. What the module can do is report β which keys the JSON actually contains, whether the envelope was supplied twice, and whether the name carries characters Azure may refuse β so a
checkblock can enforce intent where avalidation {}would be wrong.
If this module saves you time:
- β Star the repository β it helps others find it.
- πΌ Connect on LinkedIn β linkedin.com/in/microsoftexpert
- β Buy me a coffee β buymeacoffee.com/microsoftexpert
flowchart TB
ADF["azurerm_data_factory<br/>the parent - has tags"]
THIS["THIS MODULE<br/>custom dataset<br/>generic, any type"]
TYPED["azurerm_data_factory_dataset_*<br/>the typed alternatives"]
LS["a linked service<br/>named by STRING, never verified"]
CRED["credentials<br/>where authentication lives"]
PIPE["pipelines<br/>reference this BY NAME<br/>nothing points back"]
DATA["the underlying data<br/>described, never touched"]
ADF -->|"data_factory_id"| THIS
ADF -->|"data_factory_id"| TYPED
THIS -->|"linked_service.name"| LS
LS -->|"authenticates via"| CRED
PIPE -.->|"uses by name"| THIS
LS -.->|"reaches"| DATA
classDef me fill:#0078D4,stroke:#004578,color:#ffffff
classDef keystone fill:#004578,stroke:#002B44,color:#ffffff
classDef other fill:#F0F3F7,stroke:#B8C4D0,color:#1B1B1B
class THIS me
class ADF keystone
class TYPED,LS,CRED,PIPE,DATA other
Note the dotted edge from pipelines: they reference a dataset by name, and nothing points back. That is the fact to know before deleting one.
flowchart TB
IN1["var.name<br/>validator is INERT"]
IN2["var.data_factory_id"]
IN3["var.type + var.type_properties_json<br/>COUPLED and UNCHECKED"]
IN4["var.linked_service<br/>a NAME, never verified"]
IN5["var.schema_json / parameters<br/>additional_properties / annotations"]
R["azurerm_data_factory_custom_dataset.this"]
O1["id / name"]
O2["name_contains_characters<br/>_azure_may_reject"]
O3["type_property_keys<br/>type_properties_looks_double_wrapped"]
O4["the_json_shape_is_not<br/>_validated_by_anything"]
O5["the_module_cannot_see<br/>_which_pipelines_use_this"]
IN1 --> R
IN2 --> R
IN3 --> R
IN4 --> R
IN5 --> R
R --> O1
IN1 --> O2
IN3 --> O3
IN3 --> O4
R --> O5
classDef me fill:#0078D4,stroke:#004578,color:#ffffff
classDef io fill:#F0F3F7,stroke:#B8C4D0,color:#1B1B1B
class R me
class IN1,IN2,IN3,IN4,IN5,O1,O2,O3,O4,O5 io
Resource inventory
| Resource | Count | Note |
|---|---|---|
azurerm_data_factory_custom_dataset |
1 (this) |
One nested block, capped at a single entry. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None in this module β the caller configures the provider, its authentication, and its mandatory features {} block. |
Schema notes that bite
- π΄ The provider wraps
type_properties_jsonin atypePropertiesenvelope. Supply the inner object only; a payload already containing that key is double-nested and nothing reports it. - π΄ The name validator is effectively inert. Its regex
^[-.+?/<>*%&:\\]+$is anchored over the whole string, so it rejects only a name made entirely of those characters βmy/dataset:v1passes despite the message listing/and:as not allowed. - π΄ Nothing validates the JSON shape or its agreement with
typeβ not the provider, not this module. β οΈ Both JSON fields use semantic comparison, so reordering or reformatting produces no diff.β οΈ typehas no enum β any non-empty string, because the type set is Microsoft's and grows.β οΈ linked_serviceis capped at one block, a limit absent from the published schema, and its name is never verified to exist.β οΈ additional_propertieswrites arbitrary top-level keys and can collide with managed fields silently.β οΈ Onlyname,data_factory_idandtypeare force-new β everything describing what the dataset reads updates in place.β οΈ Timeouts are the usual 30m/5m/30m/30m, unlike this family's credential resources.
| Permission | Scope | Why |
|---|---|---|
Microsoft.DataFactory/factories/datasets/write |
the Data Factory | Create and update. |
Microsoft.DataFactory/factories/datasets/read |
the Data Factory | Refresh and plan. |
Microsoft.DataFactory/factories/datasets/delete |
the Data Factory | Destroy β which can break a pipeline still using it. |
Data Factory Contributor |
the Data Factory | The built-in role containing the above. |
π No permission on the data source is required or used. A dataset describes where data lives; it does not read it. Whoever can write datasets can describe any storage account or database β the access itself belongs to the linked service and the credential behind it. Delete is the operation to review, not create: removing a dataset breaks every pipeline referencing it by name, and nothing can see those references.
- An existing Data Factory.
- A linked service already configured in that factory, whose name is the only handle this dataset has.
- Knowledge of the correct
typestring and the property shape it expects β both Microsoft's, both unvalidated here and in the provider. - A name satisfying Microsoft's naming rules for Data Factory objects, which the provider does not meaningfully enforce.
terraform-azurerm-data-factory-custom-dataset/
βββ providers.tf # required_version + the pinned azurerm; no provider block
βββ variables.tf # twelve inputs, eleven validations
βββ main.tf # locals decoding the JSON and reporting what it contains
βββ outputs.tf # 46 outputs, led by the two provider behaviours that surprise
βββ README.md # this file
βββ SCOPE.md # the cross-module contract
βββ LICENSE # MIT
βββ .gitignore
provider "azurerm" {
features {}
}
module "dataset" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-custom-dataset.git?ref=v1.0.0"
name = "ds_landing"
data_factory_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-data-eastus/providers/Microsoft.DataFactory/factories/adf-corp"
type = "AzureBlob"
linked_service = {
name = "ls-blob"
}
# The INNER object only -- the provider adds the typeProperties envelope.
type_properties_json = jsonencode({
folderPath = "raw"
container = "landing"
})
}π‘
jsonencode()is the readable way to write these: it produces valid JSON from HCL, and the provider compares the field semantically so formatting never causes a diff.
Consumes
| Input | Type | Source |
|---|---|---|
data_factory_id |
string |
terraform-azurerm-data-factory (id) |
linked_service |
object |
caller β a linked service name |
type, type_properties_json |
string |
caller β coupled, neither checks the other |
schema_json, parameters, additional_properties, annotations, description, folder, timeouts |
β | caller |
Emits
| Output | Description |
|---|---|
id |
The dataset's Resource ID. |
name |
How a pipeline refers to it. |
type_property_keys |
What the JSON actually contains. |
type_properties_looks_double_wrapped |
Whether the envelope was supplied twice. |
name_contains_characters_azure_may_reject |
The suspect-character report. |
uses_additional_properties |
Whether the escape hatch is in use. |
1 Β· The minimum call
module "dataset" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-custom-dataset.git?ref=v1.0.0"
name = "ds_landing"
data_factory_id = var.data_factory_id
type = "AzureBlob"
linked_service = { name = "ls-blob" }
type_properties_json = jsonencode({
folderPath = "raw"
container = "landing"
})
}βΉοΈ Nothing here is verified: not the type, not the property names, not the linked service.
2 Β· The envelope trap
# WRONG -- the provider adds the envelope itself, so this nests twice:
#
# type_properties_json = jsonencode({
# typeProperties = { folderPath = "raw" }
# })
check "no_double_wrap" {
assert {
condition = !module.dataset.type_properties_looks_double_wrapped
error_message = "type_properties_json already contains a typeProperties key. The provider wraps this value itself, so the payload will be nested twice and Azure will not interpret it as intended."
}
}
β οΈ Reported rather than refused, because a dataset type could in principle have a property of that name. Thecheckblock is where the judgement belongs.
3 Β· Asserting the JSON contains what it should
check "blob_properties_present" {
assert {
condition = alltrue([
for k in ["folderPath", "container"] : contains(module.dataset.type_property_keys, k)
])
error_message = "The AzureBlob dataset is missing folderPath or container. Nothing in the provider or the module checks the JSON shape, so assert on the keys here."
}
}π‘ This is the nearest thing to shape validation that cannot reject legal input β and it lives in the caller, where the expected keys can change without a module release.
4 Β· The name validator almost never fires
module "awkward_name" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-custom-dataset.git?ref=v1.0.0"
name = "my/dataset:v1" # ACCEPTED -- by the provider and by this module
data_factory_id = var.data_factory_id
type = "AzureBlob"
linked_service = { name = "ls-blob" }
type_properties_json = jsonencode({ folderPath = "raw" })
}
output "the_defect" {
value = module.awkward_name.the_provider_name_validator_is_effectively_inert
}π΄ The provider's message lists twelve characters as not allowed; its regex rejects only a name composed entirely of them. The module mirrors that exactly rather than enforcing the message, because enforcing it would reject names the provider and Azure may accept β and a failed validation blocks
terraform destroy.
5 Β· Using the name report without drowning in it
output "name_check" {
value = module.dataset.name_contains_characters_azure_may_reject
}
β οΈ A hyphen or a period alone makes this true, and both appear in perfectly working names. Treat it as a prompt to check Microsoft's naming rules rather than as a verdict β which is why the module reports it instead of refusing.
6 Β· A schema, and why it is usually omitted
module "typed_dataset" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-custom-dataset.git?ref=v1.0.0"
name = "ds_customers"
data_factory_id = var.data_factory_id
type = "DelimitedText"
linked_service = { name = "ls-blob" }
type_properties_json = jsonencode({
location = { type = "AzureBlobStorageLocation", container = "curated", fileName = "customers.csv" }
columnDelimiter = ","
firstRowAsHeader = true
})
schema_json = jsonencode([
{ name = "id", type = "String" },
{ name = "created_at", type = "DateTime" },
])
}βΉοΈ Unlike the type properties,
schema_jsonis sent as supplied with no envelope added. Data Factory can read most sources without one, so omitting it is normal.
7 Β· Parameterised datasets
module "parameterised" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-custom-dataset.git?ref=v1.0.0"
name = "ds_by_date"
data_factory_id = var.data_factory_id
type = "AzureBlob"
linked_service = {
name = "ls-blob"
parameters = { environment = "prod" }
}
parameters = {
partitionDate = "2026-01-01"
}
type_properties_json = jsonencode({
folderPath = "@dataset().partitionDate"
container = "landing"
})
}π‘ Note the two parameter maps:
parametersbelongs to the dataset,linked_service.parametersis passed through to the linked service. They are not interchangeable.
8 Β· The escape hatch, flagged
module "with_extras" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-custom-dataset.git?ref=v1.0.0"
name = "ds_landing"
data_factory_id = var.data_factory_id
type = "AzureBlob"
linked_service = { name = "ls-blob" }
type_properties_json = jsonencode({ folderPath = "raw" })
additional_properties = {
someUnmodelledField = "value"
}
}
check "escape_hatch_is_deliberate" {
assert {
condition = !module.with_extras.uses_additional_properties
error_message = "This dataset uses additional_properties, which writes arbitrary top-level keys into the definition. Confirm it is intended -- a key colliding with a managed field is a conflict nothing reports."
}
}
β οΈ Thecheckabove is written to fail on this configuration deliberately, as a review gate. Remove it once the use is agreed.
9 Β· Reformatting the JSON is free
output "no_diff_on_reformat" {
value = module.dataset.json_key_ordering_produces_no_diff
}π‘ The provider compares both JSON fields semantically and suppresses key-ordering differences. Switching from a hand-written string to
jsonencode(), or reordering keys, produces no plan diff at all.
10 Β· Several datasets over one linked service
locals {
containers = {
landing = "raw"
curated = "curated"
}
}
module "datasets" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-custom-dataset.git?ref=v1.0.0"
for_each = local.containers
name = "ds_${each.key}"
data_factory_id = var.data_factory_id
type = "AzureBlob"
linked_service = { name = "ls-blob" }
type_properties_json = jsonencode({
folderPath = each.value
container = each.key
})
}
output "property_counts" {
value = { for k, m in module.datasets : k => m.type_property_count }
}π‘ The provider takes no lock on the factory, so these are created concurrently. Key by a stable label.
11 Β· What the module cannot see
output "unverifiable" {
value = {
linked_service = module.dataset.the_linked_service_is_not_verified_to_exist
json_shape = module.dataset.the_json_shape_is_not_validated_by_anything
type_pairing = module.dataset.the_type_and_the_json_are_coupled_and_unchecked
consumers = module.dataset.the_module_cannot_see_which_pipelines_use_this
}
}π‘ The last is the one to read before a destroy: pipelines reference a dataset by name, and nothing points back from the dataset to them.
12 Β· Re-pointing a dataset is an in-place update
output "change_surface" {
value = {
force_new = module.dataset.force_new_fields # name, factory, type
in_place = module.dataset.fields_that_can_change_after_creation # everything else
}
}
β οΈ Changing where a dataset points β its linked service, its folder, its container β is an in-place update. Only its identity and its type force a replacement, so a review scanning for destroys will not surface a dataset being redirected at different data.
13 Β· Importing an existing dataset
import {
to = module.dataset.azurerm_data_factory_custom_dataset.this
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-data-eastus/providers/Microsoft.DataFactory/factories/adf-corp/datasets/ds_landing"
}
β οΈ Thedatasetspath segment is shared with every typed dataset resource in the provider, so the ID alone does not tell you whether a dataset should be imported here or into a typed resource. Check its type in the portal first.
14 Β· ποΈ End-to-end composition
provider "azurerm" {
features {}
}
# 1. The factory -- the parent, and the only taggable resource here.
module "data_factory" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory.git?ref=v1.0.0"
name = "adf-corp"
resource_group_name = "rg-data-eastus"
location = "eastus"
tags = { owner = "data-platform" }
}
# 2. A credential the linked service will authenticate with. No secret passes
# through Terraform -- it names a Key Vault secret.
module "credential" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-credential-user-managed-identity.git?ref=v1.0.0"
name = "cred-managed"
data_factory_id = module.data_factory.id
identity_id = var.user_assigned_identity_id
depends_on = [module.data_factory]
}
# 3. THIS MODULE -- a description of where data lives. It reads nothing.
module "dataset" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-custom-dataset.git?ref=v1.0.0"
name = "ds_landing"
data_factory_id = module.data_factory.id
type = "AzureBlob"
# The linked service is named by STRING and is not created here -- it is
# configured in the factory and referenced by name.
linked_service = { name = "ls-blob" }
type_properties_json = jsonencode({
folderPath = "raw"
container = "landing"
})
description = "Landing-zone blobs written by the ingest pipeline."
folder = "raw"
annotations = ["owner:data-platform"]
}
check "json_has_the_expected_keys" {
assert {
condition = alltrue([
for k in ["folderPath", "container"] : contains(module.dataset.type_property_keys, k)
])
error_message = "The AzureBlob dataset is missing folderPath or container."
}
}
check "no_double_wrap" {
assert {
condition = !module.dataset.type_properties_looks_double_wrapped
error_message = "type_properties_json already contains a typeProperties key; the provider adds that envelope itself."
}
}
output "dataset_status" {
description = "What is described, and what Terraform deliberately cannot confirm."
value = {
dataset = module.dataset.id
used_by_name = module.dataset.name
properties = module.dataset.type_property_keys
linked_service = module.dataset.linked_service_name
credential = module.credential.name
reads_anything = false # A dataset is a description; pipelines do the work.
}
}
β οΈ The literalfalseis deliberate and is not a module output. Creating a dataset moves no data, incurs no cost, and touches no storage account β a pipeline must use it before anything happens.
| Input | Type | Required | Note |
|---|---|---|---|
name |
string |
β | Force-new. Validator mirrored; suspect characters reported. |
data_factory_id |
string |
β | Force-new. Anchored at both ends. |
type |
string |
β | Force-new. No enum. |
linked_service |
object |
β | A name, plus optional parameters. |
type_properties_json |
string |
β | The inner object; parseability checked only. |
schema_json |
string |
β | Parseability checked only. |
parameters |
map(string) |
β | The dataset's own parameters. |
additional_properties |
map(string) |
β | An escape hatch; reported. |
annotations |
list(string) |
β | Not tags. |
description, folder |
string |
β | Optional; empty refused. |
timeouts |
object |
β | Four keys, 30m/5m/30m/30m. |
Full schemas
variable "name" {
type = string
# Non-empty, and refuses a name made ENTIRELY of - . + ? / < > * % & : \
# -- which is the only thing the provider actually rejects. A name merely
# CONTAINING them is accepted, and reported.
}
variable "data_factory_id" {
type = string
# /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.DataFactory
# /factories/<factory>
}
variable "type" {
type = string
# Non-empty only. NO enum -- the dataset type set is Microsoft's and grows.
# Decides what type_properties_json must contain; the pairing is unchecked.
}
variable "linked_service" {
type = object({
name = string # a NAME in this factory, not an ID
parameters = optional(map(string))
})
# A single object: the provider permits one block, a limit absent from its
# published schema.
}
variable "type_properties_json" {
type = string
# Checked for PARSEABILITY and that it decodes to an OBJECT. Nothing else.
# SUPPLY THE INNER OBJECT -- the provider adds a typeProperties envelope.
}
variable "schema_json" {
type = string
default = null
# Parseability only. Sent as supplied, with no envelope.
}
variable "parameters" {
type = map(string)
default = {}
}
variable "additional_properties" {
type = map(string)
default = {}
# An escape hatch: arbitrary top-level keys, able to collide with managed
# fields without either Terraform or the provider reporting it.
}
variable "annotations" {
type = list(string)
default = []
# Data Factory annotations -- a LIST, unrelated to Azure resource tags.
}
variable "description" {
type = string
default = null
}
variable "folder" {
type = string
default = null
# Omit for the root level. Purely an authoring convenience.
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
# Provider defaults: create 30m, update 30m, read 5m, delete 30m.
}| Output | Description | Note |
|---|---|---|
id |
The dataset's Resource ID. | first |
name |
How a pipeline refers to it. | |
data_factory_id / data_factory_name |
The parent factory. | |
resource_group_name / subscription_id |
Where the factory lives. | |
name_contains_characters_azure_may_reject |
The suspect-character report. | derived |
the_provider_name_validator_is_effectively_inert |
The provider defect. | constant |
microsoft_naming_rules_are_the_real_authority |
Who actually decides. | constant |
type |
The dataset type. | |
type_property_keys / type_property_count |
What the JSON contains. | derived |
type_properties_looks_double_wrapped |
The envelope supplied twice. | derived |
the_provider_wraps_the_type_properties_json |
The easiest mistake. | constant |
the_json_shape_is_not_validated_by_anything |
The design decision. | constant |
the_type_and_the_json_are_coupled_and_unchecked |
Two arguments, no cross-check. | constant |
json_key_ordering_produces_no_diff |
Reformatting is free. | constant |
has_schema_json |
Whether a schema was supplied. | derived |
linked_service_name / linked_service_parameter_count |
The linked service. | |
the_linked_service_is_not_verified_to_exist |
Named, never checked. | constant |
only_one_linked_service_block_is_permitted |
Why the input is an object. | constant |
parameters / parameter_count |
The dataset's parameters. | |
uses_additional_properties / additional_property_count |
The escape hatch, flagged. | derived |
annotations / annotation_count / has_annotations |
A list, not tags. | derived |
description / has_description / folder / has_folder |
Metadata. | |
force_new_fields |
name, factory, type. |
list |
fields_that_can_change_after_creation |
Everything describing what it reads. | list |
fields_azure_returns_on_read |
What a refresh repopulates. | list |
this_dataset_moves_no_data_by_itself |
It is a description. | constant |
the_module_cannot_see_which_pipelines_use_this |
Read before deleting. | constant |
timeouts_here_are_the_usual_thirty_minutes |
Unlike the credential siblings. | constant |
destroying_this_does_not_touch_the_underlying_data |
No blobs are harmed. | constant |
destroying_the_factory_destroys_this_dataset_too |
Ownership. | constant |
lifecycle_prevent_destroy_is_not_available_to_a_module_caller |
Use a management lock. | constant |
this_is_a_real_azure_resource_not_a_composite |
Real ID, real delete. | constant |
the_provider_takes_no_lock_on_the_data_factory |
Concurrent datasets are safe. | constant |
this_resource_supports_no_azure_resource_tags |
Tag the factory. | constant |
no_secret_is_accepted_or_emitted_by_this_module |
With one caveat β see below. | constant |
No output is sensitive. Note, though, that type_properties_json is free-form: a caller could write
something sensitive into it, nothing would stop them, and it would sit in plain text in state.
Almost nothing about this resource is validated, and the module's job is to report rather than to invent
rules. The type is any string, its properties are raw JSON, and the linked service is a name β so the
module checks that the JSON parses and decodes to an object, and stops there. Encoding Microsoft's per-type
property sets into a Terraform module would put them somewhere they go stale independently of the provider,
and because a failed validation {} blocks terraform destroy as well as apply, a wrong constraint would
strand anyone already using a payload it rejected. Instead the module emits type_property_keys, so a
check block in the caller can assert that expected properties are present β shape enforcement placed where
it can be maintained without a module release.
The provider wraps your JSON, and the double-wrap is the easiest mistake here. It sends the supplied value
as {"typeProperties": <your json>}, so the caller provides the inner object. A payload that already carries
a typeProperties key gets nested twice, produces no error, and simply does not do what was intended.
type_properties_looks_double_wrapped detects it β reported rather than refused, since a dataset type could
in principle have a property of that name.
The name validator is a defect worth knowing about. Its message lists twelve characters as not allowed in
a dataset name; its regex is anchored over the whole string with +, so it matches only a name made
entirely of them. "---" is rejected and "my/dataset:v1" is accepted. The module mirrors the provider
exactly β never rejecting what the provider accepts β and reports the suspect characters instead, with the
report's own description warning that a hyphen or period alone makes it true. Without that warning the flag
would fire on most real names and be ignored.
The interesting change is an in-place update. Only name, data_factory_id and type are force-new;
the linked service, the type properties and the schema all update in place. So re-pointing a dataset at a
different container, folder or file is an attribute change rather than a replacement, and a review that scans
plans for destroys will not surface it.
And nothing points back from a dataset to its consumers. Pipelines reference a dataset by name. Destroying one breaks every pipeline that uses it, and neither Terraform nor this module can see those references β which is why the fact is emitted rather than left for a reader to discover.
| Concern | This module's position | Opt-out |
|---|---|---|
| Raw JSON | Validated for parseability and object-ness only; the shape is reported, never constrained. | β |
| Provider defects | The inert name validator is mirrored exactly and reported, not corrected. | β |
| Rejecting legal input | Nothing the provider accepts is refused here β not a wrong-shaped payload, not an unusual name, not a misspelled type. | β |
| Escape hatches | additional_properties is emitted as a flag so its use is visible in review. |
β |
| Invisible constraints | The one-block linked_service cap, absent from the published schema, is mirrored by the object type. |
β |
| Unverifiable facts | The linked service, the type pairing and the consuming pipelines are all stated as uncheckable. | β |
| Secrets | None accepted or emitted β with the caveat that free-form JSON could carry one into state. | β |
There is no security-relevant boolean or enum here to default, so the empty call does not exist: every consequential argument is required.
terraform init -backend=false
terraform validate
terraform fmt -checkPin the source at ?ref=v1.0.0, never a branch. This module is plan-only; a human applies from CI.
terraform validate and terraform fmt -check cover the module's structure and all eleven validation {}
blocks: the two name rules, the anchored factory ID, the non-empty type, the two linked-service rules, both
JSON parseability checks, the decodes-to-an-object check, and the description and folder rules.
terraform console against a root module is the harness that actually exercises them, because validate on a
calling configuration does not fire them. Terraform also skips a validation whose referenced variable has
already failed, so a short error list is not evidence a check is missing.
The accepting direction is the real test here. A name containing / and : must be accepted β if it is
refused, the module has become stricter than the provider and would reject legal input. So must valid JSON of
the wrong shape for the declared type, and a payload that already carries the typeProperties envelope: both
are reported, not refused, and a fixture proving that is worth more than any of the rejections.
Also worth exercising in both directions: type_properties_looks_double_wrapped, uses_additional_properties,
has_schema_json, and the name-character report β including a name with no suspect characters at all, which
is the only case that returns false.
What no offline check can reach: whether the factory or the linked service exists, whether the type string is real, whether the JSON matches that type, and which pipelines depend on the dataset.
id = "/subscriptions/00000000-.../factories/adf-corp/datasets/ds_landing"
name = "ds_landing"
data_factory_name = "adf-corp"
type = "AzureBlob"
type_property_keys = ["container", "folderPath"]
type_property_count = 2
type_properties_looks_double_wrapped = false
name_contains_characters_azure_may_reject = false
linked_service_name = "ls-blob"
has_schema_json = false
uses_additional_properties = false
annotation_count = 1
force_new_fields = ["name", "data_factory_id", "type"]
this_dataset_moves_no_data_by_itself = true
| Symptom | Cause | Fix |
|---|---|---|
| The pipeline fails saying the dataset is misconfigured | The JSON does not match the declared type. Nothing checks the pairing. |
Compare type_property_keys against Microsoft's properties for that dataset type. |
| The dataset applies but behaves as if empty | The typeProperties envelope was supplied twice, producing a doubly-nested payload. |
Check type_properties_looks_double_wrapped and supply the inner object only. |
A name with / or : was accepted and Azure rejected it |
The provider's validator only refuses names made entirely of those characters. | Check name_contains_characters_azure_may_reject and Microsoft's naming rules. |
name_contains_characters_azure_may_reject is true for an ordinary name |
A hyphen or period alone makes it true. | Treat it as a prompt, not a verdict. |
| The dataset cannot reach the data | The linked service name is wrong, or the linked service lacks a working credential. | The name is never verified β confirm it in the factory, and check the credential behind it. |
| Reformatting the JSON produced no diff | The provider compares both JSON fields semantically. | Expected, and safe. |
| A pipeline broke after removing a dataset | Pipelines reference datasets by name and nothing points back. | Search the factory's pipelines before deleting a dataset. |
| Re-pointing a dataset showed no replacement | The linked service and type properties update in place. | Expected. Assert on the derived outputs rather than reviewing for destroys. |
An additional_properties key had no effect, or an unexpected one |
It writes arbitrary top-level keys and can collide with managed fields. | Check uses_additional_properties; prefer the modelled arguments. |
| A create timed out sooner than expected | These timeouts are 30m β but the credential resources in this family are 5m. | Check which resource is actually timing out. |
azurerm_data_factory_custom_dataset- Datasets in Azure Data Factory
- Azure Data Factory naming rules
- Sibling modules:
terraform-azurerm-data-factory,terraform-azurerm-data-factory-credential-user-managed-identity,terraform-azurerm-data-factory-credential-service-principal - This module's
SCOPE.md
π "Infrastructure as Code should be standardized, consistent, and secure."