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
15 changes: 14 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 "instance_pool" {
source = "536tech/instance-pool/databricks"
version = "1.0.0"
version = "1.0.1"

name = "shared-pool"
node_type_id = "Standard_DS3_v2"
Expand Down Expand Up @@ -70,6 +70,19 @@ 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.
A null or zero `max_capacity` keeps the provider's unlimited-capacity behavior.
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 permission needs exactly one nonblank principal and a supported resource-specific permission level.
`databricks_permissions` manages the object's permission set. Keep one state owner for that set.
Empty permissions omit the permission resource.
See [provider permission semantics](https://github.com/databricks/terraform-provider-databricks/blob/v1.130.0/docs/resources/permissions.md).

<!-- BEGIN_TF_DOCS -->
## Requirements

Expand Down
7 changes: 7 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
resource "databricks_instance_pool" "this" {
lifecycle {
precondition {
condition = var.max_capacity == null ? true : (var.max_capacity == 0 || var.max_capacity >= coalesce(var.min_idle_instances, 0))
error_message = "A positive max_capacity must be at least min_idle_instances."
}
}

instance_pool_name = var.name
node_type_id = var.node_type_id
min_idle_instances = var.min_idle_instances
Expand Down
73 changes: 73 additions & 0 deletions tests/module.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,76 @@ 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_missing_principal" {
command = plan
variables {
permissions = [{ permission_level = "CAN_ATTACH_TO" }]
}
expect_failures = [var.permissions]
}

run "reject_multiple_principals" {
command = plan
variables {
permissions = [{ permission_level = "CAN_ATTACH_TO", user_name = "user@example.com", group_name = "readers" }]
}
expect_failures = [var.permissions]
}

run "reject_blank_principal" {
command = plan
variables {
permissions = [{ permission_level = "CAN_ATTACH_TO", group_name = " " }]
}
expect_failures = [var.permissions]
}

run "reject_invalid_permission" {
command = plan
variables {
permissions = [{ permission_level = "INVALID", group_name = "readers" }]
}
expect_failures = [var.permissions]
}

run "reject_negative_idle" {
command = plan
variables {
min_idle_instances = -1
}
expect_failures = [var.min_idle_instances]
}

run "reject_capacity_below_idle" {
command = plan
variables {
max_capacity = 1
min_idle_instances = 2
}
expect_failures = [databricks_instance_pool.this]
}

run "accept_unlimited_zero_capacity" {
command = plan
variables {
max_capacity = 0
min_idle_instances = 2
}
}

run "accept_provider_defaults" {
command = plan
variables {
min_idle_instances = null
enable_elastic_disk = null
}
}
42 changes: 41 additions & 1 deletion variables.tf
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
variable "name" {
description = "Instance pool name."
type = string
nullable = false

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

variable "node_type_id" {
description = "Azure VM size for the pool, for example Standard_DS3_v2."
type = string
nullable = false

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

variable "min_idle_instances" {
description = "Instances the pool keeps ready."
type = number

validation {
condition = var.min_idle_instances == null ? true : try(var.min_idle_instances >= 0 && floor(var.min_idle_instances) == var.min_idle_instances, false)
error_message = "min_idle_instances must be an integer of at least 0."
}
}

variable "idle_instance_autotermination_minutes" {
description = "Minutes an idle instance stays in the pool above min_idle_instances."
type = number
nullable = false

validation {
condition = try(var.idle_instance_autotermination_minutes >= 0 && floor(var.idle_instance_autotermination_minutes) == var.idle_instance_autotermination_minutes, false)
error_message = "idle_instance_autotermination_minutes must be an integer of at least 0."
}
}

variable "enable_elastic_disk" {
Expand All @@ -32,6 +55,10 @@ variable "max_capacity" {
description = "Maximum number of instances in the pool."
type = number
default = null
validation {
condition = var.max_capacity == null ? true : try(var.max_capacity >= 0 && floor(var.max_capacity) == var.max_capacity, false)
error_message = "max_capacity must be null or a nonnegative integer; zero means no limit."
}
}

variable "custom_tags" {
Expand Down Expand Up @@ -61,5 +88,18 @@ variable "permissions" {
service_principal_name = optional(string)
}))

default = []
default = []
nullable = false

validation {
condition = try(alltrue([for permission in var.permissions :
length([for principal in [permission.group_name, permission.user_name, permission.service_principal_name] :
principal if principal != null
]) == 1 &&
alltrue([for principal in [permission.group_name, permission.user_name, permission.service_principal_name] :
principal == null ? true : length(trimspace(principal)) > 0
]) && contains(["CAN_ATTACH_TO", "CAN_MANAGE"], permission.permission_level)
]), false)
error_message = "Each permission needs exactly one nonblank principal and a supported level: CAN_ATTACH_TO, CAN_MANAGE."
}
}