Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.0.1 - 2026-09-16

- Validate module inputs and access rules before provider API requests.
- Reject invalid permissions and conflicting settings without changing resource addresses.
- Preserve provider defaults for optional values.

## 1.0.0 - 2026-09-09

- Declare the module contract stable. The inputs, outputs, resource addresses, and provider
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The resource addresses above are part of the DataTF import contract. Do not rena
```hcl
module "external_location" {
source = "536tech/external-location/databricks"
version = "1.0.0"
version = "1.0.1"

name = "lake_raw"
url = "abfss://raw@lake.dfs.core.windows.net/"
Expand Down Expand Up @@ -66,6 +66,20 @@ The workspace pattern module checks the complete DataTF contract and its integra

[Apache-2.0](LICENSE).

## Input safeguards

The module rejects blank required names and invalid access inputs during the plan.
Cross-input preconditions preserve the Terraform 1.5 minimum and existing resource addresses.
Null remains valid for inputs where the provider supplies a default.
Provider and API checks still apply. These checks do not prove complete permission visibility.

Each grant needs one unique principal and at least one nonblank privilege.
The module does not freeze the Unity Catalog privilege list; the provider and API check supported privileges.
`databricks_grants` manages the complete direct grant set on the object.
Preserve the caller's required grants and review the plan before apply.
Empty grants omit the grant resource; they do not declare an empty authoritative grant set.
See [provider grant semantics](https://github.com/databricks/terraform-provider-databricks/blob/v1.130.0/docs/resources/grants.md).

<!-- BEGIN_TF_DOCS -->
## Requirements

Expand Down
51 changes: 51 additions & 0 deletions tests/module.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,54 @@ run "without_access" {
error_message = "Empty access must omit the access resources."
}
}

run "reject_blank_name" {
command = plan
variables {
name = " "
}
expect_failures = [var.name]
}

run "reject_blank_principal" {
command = plan
variables {
grants = [{ principal = " ", privileges = ["SELECT"] }]
}
expect_failures = [var.grants]
}

run "reject_empty_privileges" {
command = plan
variables {
grants = [{ principal = "readers", privileges = [] }]
}
expect_failures = [var.grants]
}

run "reject_duplicate_principal" {
command = plan
variables {
grants = [{ principal = "readers", privileges = ["SELECT"] }, { principal = "readers", privileges = ["MODIFY"] }]
}
expect_failures = [var.grants]
}

run "reject_isolation_mode" {
command = plan
variables {
isolation_mode = "INVALID"
}
expect_failures = [var.isolation_mode]
}

run "accept_provider_defaults" {
command = plan
variables {
owner = null
isolation_mode = null
read_only = null
fallback = null
enable_file_events = null
}
}
39 changes: 38 additions & 1 deletion variables.tf
Original file line number Diff line number Diff line change
@@ -1,26 +1,54 @@
variable "name" {
description = "External location name."
type = string
nullable = false

validation {
condition = try(length(trimspace(var.name)) > 0, false)
error_message = "name must not be empty or blank."
}
}

variable "url" {
description = "Storage URL, for example abfss://container@account.dfs.core.windows.net/path."
type = string
nullable = false

validation {
condition = try(length(trimspace(var.url)) > 0, false)
error_message = "url must not be empty or blank."
}
}

variable "credential_name" {
description = "Storage credential that grants access to the URL."
type = string
nullable = false

validation {
condition = try(length(trimspace(var.credential_name)) > 0, false)
error_message = "credential_name must not be empty or blank."
}
}

variable "isolation_mode" {
description = "Isolation mode: ISOLATION_MODE_OPEN or ISOLATION_MODE_ISOLATED."
type = string

validation {
condition = var.isolation_mode == null ? true : contains(["ISOLATION_MODE_OPEN", "ISOLATION_MODE_ISOLATED"], var.isolation_mode)
error_message = "isolation_mode must be ISOLATION_MODE_OPEN or ISOLATION_MODE_ISOLATED."
}
}

variable "owner" {
description = "External location owner. A user, group, or service principal."
type = string

validation {
condition = var.owner == null ? true : try(length(trimspace(var.owner)) > 0, false)
error_message = "owner must not be empty or blank."
}
}

variable "read_only" {
Expand Down Expand Up @@ -50,7 +78,16 @@ variable "grants" {
principal = string
privileges = list(string)
}))
default = []
default = []
nullable = false

validation {
condition = try(alltrue([for grant in var.grants :
length(trimspace(grant.principal)) > 0 && length(grant.privileges) > 0 &&
alltrue([for privilege in grant.privileges : length(trimspace(privilege)) > 0])
]) && length(distinct([for grant in var.grants : grant.principal])) == length(var.grants), false)
error_message = "Each grant needs a unique nonblank principal and at least one nonblank privilege."
}
}

variable "force_destroy" {
Expand Down