| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A typed Data Factory dataset describing a table in a PostgreSQL database reached through a linked service, targeting hashicorp/azurerm ~> 4.0.
💡 Why it matters: the family has refuted the clone hypothesis three times, and here it finally holds at the argument surface. It still does not hold in the provider code — the schema-identical SQL Server Table resource raises a requires-import error naming a type that does not exist.
If this module saved you time:
flowchart TB
RG["terraform-azurerm-resource-group"]
ADF["terraform-azurerm-data-factory"]
LS["a PostgreSQL linked service, which chooses the server AND the database"]
THIS["terraform-azurerm-data-factory-dataset-postgresql"]
SQLS["terraform-azurerm-data-factory-dataset-sql-server-table"]
MYSQLDS["terraform-azurerm-data-factory-dataset-mysql"]
AZSQL["terraform-azurerm-data-factory-dataset-azure-sql-table"]
PIPE["a Data Factory pipeline"]
TBL["a table in a PostgreSQL schema"]
RG -->|"name"| ADF
ADF -->|"id"| THIS
ADF -->|"id"| SQLS
ADF -->|"id"| MYSQLDS
ADF -->|"id"| AZSQL
LS -->|"linked_service_name, a bare NAME"| THIS
LS -->|"linked_service_id, a full Resource ID"| AZSQL
THIS -->|"schema-identical to these two"| SQLS
THIS -->|"schema-identical to these two"| MYSQLDS
THIS -->|"name, referenced by"| PIPE
PIPE -->|"reads only at run time, never here"| TBL
classDef this fill:#0078D4,stroke:#004578,color:#ffffff,stroke-width:2px
classDef keystone fill:#004578,stroke:#00243d,color:#ffffff,stroke-width:2px
classDef sibling fill:#eef3f8,stroke:#b9c8d8,color:#1b2733
class THIS this
class ADF keystone
class RG,LS,SQLS,MYSQLDS,AZSQL,PIPE,TBL sibling
The linked service node carries the fact most easily missed: it chooses the server and the database. This dataset names only a table — and on PostgreSQL, not even the schema.
flowchart TB
subgraph INPUTS["Inputs"]
NAME["name (force-new)"]
ADFID["data_factory_id (force-new)"]
LSNAME["linked_service_name, a bare NAME"]
TBL["table_name, optional and unqualified"]
COLS["schema_column list"]
META["parameters, additional_properties, annotations, description, folder"]
end
THIS["the dataset resource -- this shape is shared by the schema-identical trio"]
subgraph OUTPUTS["Outputs"]
OID["id and name"]
OTBL["table_name and has_table_name"]
ONONE["describes_no_specific_table"]
OQUAL["table_name_looks_qualified"]
OUNTYPED["untyped_schema_column_names"]
OGUARD["the import-guard fact, which is the ONE output that differs across the trio"]
end
NAME --> THIS
ADFID --> THIS
LSNAME --> THIS
TBL --> THIS
COLS --> THIS
META --> THIS
THIS --> OID
TBL --> OTBL
TBL --> ONONE
META --> ONONE
TBL --> OQUAL
COLS --> OUNTYPED
THIS --> OGUARD
classDef this fill:#0078D4,stroke:#004578,color:#ffffff,stroke-width:2px
classDef sibling fill:#eef3f8,stroke:#b9c8d8,color:#1b2733
class THIS this
class NAME,ADFID,LSNAME,TBL,COLS,META,OID,OTBL,ONONE,OQUAL,OUNTYPED,OGUARD sibling
ℹ️ This shape diagram is shared with the SQL Server Table and MySQL modules, because all three resources are schema-identical. Drawing two different pictures would invent a distinction a reader would then have to go and verify.
| Resource | Count | Notes |
|---|---|---|
| azurerm_data_factory_dataset_postgresql.this | 1 | The keystone. A real ARM child of the factory. |
| schema_column | dynamic, 0..n | Optional column definitions. |
| timeouts | dynamic, 0..1 | All four keys exist. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
| hashicorp/azurerm | ~> 4.0 |
| Provider block | None. The caller configures the provider, including the mandatory features {} block. |
Schema notes that bite — verified against the live provider source, not inferred from the schema:
| 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 — see the warning below. |
| Data Factory Contributor | the Data Factory | The built-in role containing all three. |
🔒 No permission on the PostgreSQL server is required or used, and none is available to grant — PostgreSQL authentication is not an Azure RBAC concern. Access belongs to the linked service, so whoever can write datasets can describe any table in whatever database that linked service points at.
⚠️ Delete is the operation to review, not create. Removing a dataset breaks every pipeline referencing it by name.
terraform-azurerm-data-factory-dataset-postgresql/ ├── providers.tf # required_version + the pinned azurerm; no provider block ├── variables.tf # 11 typed inputs, 12 validations ├── main.tf # the keystone, its locals, and two dynamic blocks ├── outputs.tf # 48 outputs; id first ├── README.md # this file ├── SCOPE.md # the cross-module contract ├── LICENSE # MIT └── .gitignore
provider "azurerm" {
features {}
}
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = "orders"
}ℹ️ No database is named here — the linked service chose it.
Consumes
| Input | Type | Typical source |
|---|---|---|
| data_factory_id | string | terraform-azurerm-data-factory → id |
| linked_service_name | string | a PostgreSQL linked service's name |
| table_name | string, optional | the caller |
Emits (selected — 48 in total)
| Output | Description |
|---|---|
| id | The dataset's Resource ID. |
| table_name, has_table_name | What it points at. |
| describes_no_specific_table | No table and no parameters. |
| this_resource_has_no_separate_schema_or_database_argument | Constant. The linked service chooses the database. |
| the_import_guard_names_this_resource_correctly | Constant. Two siblings get this wrong. |
| this_resource_is_schema_identical_to_the_sql_server_table_and_mysql_datasets | Constant. |
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
}2 · Naming a table⚠️ Legal, and it describes nothing a pipeline can resolve — no table, no parameters. describes_no_specific_table is true.
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = "orders"
}3 · The database is not yours to choose here💡 Unqualified is the normal case here — the database came from the linked service, and this resource has no argument for one.
# The SAME dataset definition reads from a different database purely by
# pointing at a different linked service.
module "orders_prod" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders_prod"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_prod_linked_service_name
table_name = "orders"
}
module "orders_staging" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders_staging"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_staging_linked_service_name
table_name = "orders"
}4 · Passing a Resource ID is refused⚠️ Changing linked_service_name on an existing dataset is an in-place update, so a dataset can be moved between production and staging databases without a destroy appearing in the plan.
# ❌ NOT ACCEPTED — this resource takes a NAME
module "wrong_parent_reference" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = "/subscriptions/.../linkedservices/ls_mysql" # an ID
}5 · Guarding against a dataset that resolves to nothingℹ️ Only azurerm_data_factory_dataset_azure_sql_table takes an ID, out of the whole twelve-resource family.
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = var.table_name # may be null
}
check "dataset_points_somewhere" {
assert {
condition = !module.orders.describes_no_specific_table
error_message = "ds_orders names no table and exposes no parameters; no pipeline can resolve it."
}
}module "any_table" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_any_table"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
parameters = {
tableName = ""
}
}7 · A typed column schema💡 With parameters set, describes_no_specific_table is false even with no table.
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = "orders"
schema_column = [
{ name = "order_id", type = "Int64", description = "Primary key." },
{ name = "placed_at", type = "DateTimeOffset" },
{ name = "notes" }, # untyped -- legal
]
}
check "every_column_is_typed" {
assert {
condition = length(module.orders.untyped_schema_column_names) == 0
error_message = "Untyped columns: ${join(", ", module.orders.untyped_schema_column_names)}"
}
}8 · Lowercase column types are refused🔒 The fifteen types are case-sensitive, and are Data Factory's types rather than PostgreSQL's — Int64, not BIGINT.
# ❌ NOT ACCEPTED — the type set is case-sensitive
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
schema_column = [{ name = "order_id", type = "int64" }] # wants "Int64"
}locals {
tables = ["orders", "customers", "ledger"]
}
module "tables" {
for_each = toset(local.tables)
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_${each.key}"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = each.key
folder = "bronze/postgresql"
}
check "no_table_resolves_to_nothing" {
assert {
condition = alltrue([for m in module.tables : !m.describes_no_specific_table])
error_message = "At least one dataset names no table."
}
}10 · Annotations, description and an authoring folder💡 The provider takes no lock on the Data Factory for this resource, so these are created concurrently and safely.
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = "orders"
description = "Order header rows, read hourly by the curated pipeline."
folder = "bronze/postgresql"
annotations = ["bronze", "hourly"]
}11 · The `additional_properties` escape hatchℹ️ annotations is a list of strings, unrelated to Azure resource tags — which this resource does not support. Tag the Data Factory instead.
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = "orders"
additional_properties = {
"structure" = "[]"
}
}12 · Importing — and why this module says its guard is correct⚠️ Merged as top-level keys alongside the managed fields. Nothing validates them.
module "existing" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_existing"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = "orders"
}
output "import_with" {
value = module.existing.import_address
}13 · Custom timeouts✅ This resource's requires-import error names azurerm_data_factory_dataset_postgresql — correct. The schema-identical SQL Server Table resource names a type that does not exist, and the JSON dataset names a different real type. the_import_guard_names_this_resource_correctly records the contrast, so the divergence is visible from either module.
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = var.data_factory_id
linked_service_name = var.postgresql_linked_service_name
table_name = "orders"
timeouts = {
create = "45m"
read = "10m"
update = "45m"
delete = "45m"
}
}14 · 🏗️ End-to-end compositionℹ️ Defaults are 30m / 5m / 30m / 30m. Terraform silently discards an object key the type does not declare.
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-analytics-eastus"
location = "eastus"
}
module "adf" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory.git?ref=v1.0.0"
name = "adf-analytics-eastus"
resource_group_name = module.rg.name
location = module.rg.location
identity = {
type = "SystemAssigned"
}
}
# No linked-service module exists in this suite yet, so the linked service is
# declared directly. Its connection string chooses the SERVER and the DATABASE;
# provision the credential out of band and reference it, never inline.
resource "azurerm_data_factory_linked_service_postgresql" "app" {
name = "ls_postgresql_app"
data_factory_id = module.adf.id
connection_string = var.postgresql_connection_string # sensitive; supplied out of band
}
module "orders" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-factory-dataset-postgresql.git?ref=v1.0.0"
name = "ds_orders"
data_factory_id = module.adf.id
linked_service_name = azurerm_data_factory_linked_service_postgresql.app.name
table_name = "orders"
folder = "bronze/postgresql"
schema_column = [
{ name = "order_id", type = "Int64" },
{ name = "placed_at", type = "DateTimeOffset" },
]
annotations = ["bronze", "hourly"]
}
check "wiring_is_consistent" {
assert {
condition = !module.orders.describes_no_specific_table
error_message = "The dataset resolves to nothing."
}
}
output "dataset_id" {
value = module.orders.id
}
output "reads_from" {
value = module.orders.table_name
}🔒 The PostgreSQL connection string carries the credential and belongs to the linked service. Keep it in a secret store and pass a reference; this module accepts no secret and emits none. Note that sensitive = true redacts plan output and does not encrypt state — the protection that matters is an encrypted, access-controlled backend.
Required: name, data_factory_id, linked_service_name. What it points at: table_name, parameters. Shape: schema_column, additional_properties. Metadata: description, folder, annotations. Tail: timeouts. There is no tags variable — the resource supports none.
Full input schemas| Name | Type | Default | Notes |
|---|---|---|---|
| name | string | — | Force-new. Non-empty only; a leading / is refused. |
| data_factory_id | string | — | Force-new. Anchored Resource-ID validator. |
| linked_service_name | string | — | A bare name. A Resource ID is refused. It chooses the server and the database. |
| table_name | string | null | Optional. No database or schema qualifier belongs here. |
| schema_column | list(object({ name, type, description })) | [] | Closed, case-sensitive set of fifteen types. |
| parameters | map(string) | {} | Supplied per pipeline run. |
| additional_properties | map(string) | {} | Unvalidated top-level keys. |
| annotations | list(string) | [] | Not tags. |
| description | string | null | Empty string refused; omission accepted. |
| folder | string | null | The authoring tree. |
| timeouts | object({ create, read, update, delete }) | null | 30m / 5m / 30m / 30m. |
| Output | Description | Notes |
|---|---|---|
| id | The dataset's Resource ID. | First, by convention. |
| name, data_factory_id, data_factory_name | Identity and parent. | Name parsed from the end of the ID. |
| resource_group_name, subscription_id | Where the factory lives. | |
| linked_service_name | The linked service, as supplied. | Chooses the server and database. |
| table_name, has_table_name | What it points at. | |
| describes_no_specific_table | No table and no parameters. | Assert on this. |
| table_name_looks_qualified | Contains a dot. | Reported, never enforced. |
| this_resource_has_no_separate_schema_or_database_argument | Constant. | |
| parameters, parameter_count | The run-time inputs. | |
| schema_column_count, has_schema_columns, schema_column_names, untyped_schema_column_names | The column schema. | |
| the_schema_column_type_set_is_closed_and_case_sensitive | Constant. | Fifteen values. |
| the_schema_column_block_is_identical_on_ten_of_the_twelve_datasets | Constant. Identical on ten of twelve; binary has no block and snowflake's differs. | Eleven of twelve. |
| uses_additional_properties, additional_property_count | The escape hatch. | |
| annotations, annotation_count, has_annotations | A list, not tags. | |
| description, has_description, folder, has_folder | Metadata. | |
| force_new_fields, fields_that_can_change_after_creation, fields_azure_returns_on_read | The change surface. | |
| import_address | The Resource ID to import. | |
| the_import_guard_names_this_resource_correctly | Constant. | Two siblings get this wrong. |
| this_resource_is_schema_identical_to_the_sql_server_table_and_mysql_datasets | Constant. | The only true clone cluster. |
| this_resource_takes_a_linked_service_NAME_not_an_ID | Constant. | The family splits 11-to-1. |
| the_linked_service_is_not_verified_to_exist | Constant. | |
| this_dataset_moves_no_data_by_itself | Constant. | |
| the_module_cannot_see_which_pipelines_use_this | Constant. | Read before destroying. |
| the_module_cannot_verify_the_table_exists, no_credential_is_configured_here | Constants. | |
| destroying_the_factory_destroys_this_dataset_too, destroying_this_does_not_touch_the_table | Constants. | |
| lifecycle_prevent_destroy_is_not_available_to_a_module_caller | Constant. | |
| this_is_a_real_azure_resource_not_a_composite, the_provider_takes_no_lock_on_the_data_factory | Constants. | |
| this_resource_supports_no_azure_resource_tags | Constant. | Why there is no tags variable. |
| no_secret_is_accepted_or_emitted_by_this_module | Constant. |
No output is sensitive, and none can be.
The clone cluster is real, and it stops at the schema. This resource, the SQL Server Table dataset and the MySQL dataset declare exactly the same arguments and blocks, with byte-identical table_name definitions. That is the first time in this family the resemblance has survived inspection — the parent reference, the location rules, the compression enums and the schema_column block have each broken it before. And the members still differ in their provider code: this one's requires-import guard is correct, the SQL Server Table one names a type that does not exist, and the JSON dataset's names a different real type. The offline harness proves the agreement by running the same fixtures through both modules and asserting the results match, then asserts the divergence separately.
The linked service chooses the database, and that is the fact most easily missed. This dataset names a table and nothing else. Two datasets with identical definitions read from different databases purely because they point at different linked services — and since linked_service_name is not force-new, moving one between production and staging is an in-place attribute change that no destroy-scan will surface.
There is no schema argument at all, unlike the Snowflake dataset. table_name_looks_qualified reports whether a dot is present and does not enforce anything: a legal PostgreSQL table name may contain one, and an unqualified name is the normal case.
Only identity forces replacement. lifecycle is not valid inside a module block, so a caller cannot add prevent_destroy; a CanNotDelete lock prevents deletion but not the replacement that editing name would cause.
The module cannot see downstream. Pipelines reference a dataset by name and nothing points back.
| Concern | This module's default | Opt-out |
|---|---|---|
| Secrets | None accepted, none emitted. The connection string lives on the linked service. | Not available — by design. |
| Credentials | Live on the linked service, never here. | — |
| PostgreSQL access | No permission required or used, and none is grantable through Azure RBAC. | — |
| A Resource ID passed as linked_service_name | Refused — a leading / is the probable mistake. | None; pass a name. |
| A dataset that resolves to nothing | Reported via describes_no_specific_table. | Ignore the output. |
| A qualified table name | Reported, never enforced. | Ignore the output. |
| Column types | Enforced against the provider's case-sensitive fifteen. | None — the set is closed. |
| tags | Not offered — the resource supports none. | Tag the factory. |
🔒 There is no risky toggle to close here: the resource holds no credential and touches no data. The exposures are the delete and the silent database switch that a linked-service change performs — the module documents both.
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module with ?ref=v1.0.0 — never a branch. This library is plan-only: a human applies from CI.
terraform plan and the module's own validation {} blocks cover, offline and without credentials — note it is plan and not terraform validate, which through a module call evaluates no variable values and reports success:
The clone claim is executed, not asserted. The harness runs every negative, positive and derived-value fixture through both this module and the SQL Server Table module and asserts they agree — 80 module-runs — then asserts separately that the import-guard outputs differ, which is the one place they must not match.
Only a real plan or apply can tell you whether the linked service exists, whether the table exists, or whether a pipeline still depends on the dataset.
dataset_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-analytics-eastus/providers/Microsoft.DataFactory/factories/adf-analytics-eastus/datasets/ds_orders" reads_from = "orders" linked_service_name = "ls_postgresql_app" has_table_name = true describes_no_specific_table = false table_name_looks_qualified = false schema_column_names = ["order_id", "placed_at"] untyped_schema_column_names = [] force_new_fields = ["name", "data_factory_id"]
| Symptom | Cause | Fix |
|---|---|---|
| linked_service_name must be a bare linked service NAME, not a Resource ID. | A Resource ID was passed. | Pass the linked service's name; only the Azure SQL Table dataset takes an ID. |
| every schema_column type must be one of Byte, Byte[], ... on "int64" | Case-sensitive, and these are Data Factory's types rather than PostgreSQL's. | Use "Int64", not "bigint". |
| table_name may be omitted, but must not be set to an empty | A blank string was passed. | Omit the argument, or give it a value. |
| A dataset applies but no pipeline can use it | No table and no parameters. | Check describes_no_specific_table. |
| A configuration with a database or schema argument will not plan | This resource has neither. | The linked service chooses the database. |
| The dataset suddenly reads from a different database | linked_service_name changed — an in-place update. | Review in-place dataset changes, not only replacements. |
| A pipeline breaks after a clean destroy | Pipelines reference datasets by name; nothing points back. | Check consumers before destroying; consider a CanNotDelete lock. |
| A timeouts key seems to have no effect | Terraform silently discards an undeclared object key. | Compare against object({ create, read, update, delete }). |
💙 "Infrastructure as Code should be standardized, consistent, and secure."
| Back | FazBrowse Home | New Git URL |