Skip to content

Commit c734ddf

Browse files
committed
Alternative implementation of databricks_permission resource for managing permissions for individual principals.
This resource provides fine-grained control over permissions by managing a single principal's access to a single object, unlike `databricks_permissions`, which manages all principals' access to an object at once. This is particularly useful for: - Managing permissions for different teams independently - Token and password authorization permissions that previously required all principals in one resource - Avoiding conflicts when multiple configurations manage different principals on the same object Caveat: Since we cannot remove an individual permission, the `Delete` operation is performed as `Read/Put`, so we need to use a lock around each object.
1 parent 423b28b commit c734ddf

File tree

9 files changed

+655
-622
lines changed

9 files changed

+655
-622
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
* Add `databricks_users` data source ([#4028](https://github.com/databricks/terraform-provider-databricks/pull/4028))
1010
* Improve `databricks_service_principals` data source ([#5164](https://github.com/databricks/terraform-provider-databricks/pull/5164))
11-
* Added `databricks_permission` resource for managing permissions on Databricks objects for individual principals ([#5161](https://github.com/databricks/terraform-provider-databricks/pull/5161)).
11+
* Added `databricks_permission` resource for managing permissions on Databricks objects for individual principals ([#5186](https://github.com/databricks/terraform-provider-databricks/pull/5186)).
1212

1313
### Bug Fixes
1414

docs/resources/permission.md

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This resource allows you to manage permissions for a single principal on a Datab
1010

1111
~> This resource is _authoritative_ for the specified object-principal pair. Configuring this resource will manage the permission for the specified principal only, without affecting permissions for other principals.
1212

13+
~> **Warning:** Do not use both `databricks_permission` and `databricks_permissions` resources for the same object. This will cause conflicts as both resources manage the same permissions.
14+
1315
-> Use `databricks_permissions` when you need to manage all permissions for an object in a single resource. Use `databricks_permission` (singular) when you want to manage permissions for individual principals independently.
1416

1517
## Example Usage
@@ -35,14 +37,16 @@ resource "databricks_group" "data_engineers" {
3537
3638
# Grant CAN_RESTART permission to a group
3739
resource "databricks_permission" "cluster_de" {
38-
cluster_id = databricks_cluster.shared.id
40+
object_type = "clusters"
41+
object_id = databricks_cluster.shared.id
3942
group_name = databricks_group.data_engineers.display_name
4043
permission_level = "CAN_RESTART"
4144
}
4245
4346
# Grant CAN_ATTACH_TO permission to a user
4447
resource "databricks_permission" "cluster_analyst" {
45-
cluster_id = databricks_cluster.shared.id
48+
object_type = "clusters"
49+
object_id = databricks_cluster.shared.id
4650
user_name = "analyst@company.com"
4751
permission_level = "CAN_ATTACH_TO"
4852
}
@@ -71,14 +75,16 @@ resource "databricks_job" "etl" {
7175
7276
# Grant CAN_MANAGE to a service principal
7377
resource "databricks_permission" "job_sp" {
74-
job_id = databricks_job.etl.id
78+
object_type = "jobs"
79+
object_id = databricks_job.etl.id
7580
service_principal_name = databricks_service_principal.automation.application_id
7681
permission_level = "CAN_MANAGE"
7782
}
7883
7984
# Grant CAN_VIEW to a group
8085
resource "databricks_permission" "job_viewers" {
81-
job_id = databricks_job.etl.id
86+
object_type = "jobs"
87+
object_id = databricks_job.etl.id
8288
group_name = "Data Viewers"
8389
permission_level = "CAN_VIEW"
8490
}
@@ -99,14 +105,16 @@ resource "databricks_notebook" "analysis" {
99105
100106
# Grant CAN_RUN to a user
101107
resource "databricks_permission" "notebook_user" {
102-
notebook_path = databricks_notebook.analysis.path
108+
object_type = "notebooks"
109+
object_id = databricks_notebook.analysis.path
103110
user_name = "data.scientist@company.com"
104111
permission_level = "CAN_RUN"
105112
}
106113
107114
# Grant CAN_EDIT to a group
108115
resource "databricks_permission" "notebook_editors" {
109-
notebook_path = databricks_notebook.analysis.path
116+
object_type = "notebooks"
117+
object_id = databricks_notebook.analysis.path
110118
group_name = "Notebook Editors"
111119
permission_level = "CAN_EDIT"
112120
}
@@ -119,19 +127,22 @@ This resource solves the limitation where all token permissions must be defined
119127
```hcl
120128
# Multiple resources can now manage different principals independently
121129
resource "databricks_permission" "tokens_team_a" {
122-
authorization = "tokens"
130+
object_type = "authorization"
131+
object_id = "tokens"
123132
group_name = "Team A"
124133
permission_level = "CAN_USE"
125134
}
126135
127136
resource "databricks_permission" "tokens_team_b" {
128-
authorization = "tokens"
137+
object_type = "authorization"
138+
object_id = "tokens"
129139
group_name = "Team B"
130140
permission_level = "CAN_USE"
131141
}
132142
133143
resource "databricks_permission" "tokens_service_account" {
134-
authorization = "tokens"
144+
object_type = "authorization"
145+
object_id = "tokens"
135146
service_principal_name = databricks_service_principal.ci_cd.application_id
136147
permission_level = "CAN_USE"
137148
}
@@ -147,7 +158,8 @@ resource "databricks_sql_endpoint" "analytics" {
147158
}
148159
149160
resource "databricks_permission" "warehouse_users" {
150-
sql_endpoint_id = databricks_sql_endpoint.analytics.id
161+
object_type = "sql/warehouses"
162+
object_id = databricks_sql_endpoint.analytics.id
151163
group_name = "SQL Users"
152164
permission_level = "CAN_USE"
153165
}
@@ -157,6 +169,29 @@ resource "databricks_permission" "warehouse_users" {
157169

158170
The following arguments are required:
159171

172+
* `object_type` - (Required) The type of object to manage permissions for. Valid values include:
173+
* `clusters` - For cluster permissions
174+
* `cluster-policies` - For cluster policy permissions
175+
* `instance-pools` - For instance pool permissions
176+
* `jobs` - For job permissions
177+
* `pipelines` - For Delta Live Tables pipeline permissions
178+
* `notebooks` - For notebook permissions (use path as `object_id`)
179+
* `directories` - For directory permissions (use path as `object_id`)
180+
* `workspace-files` - For workspace file permissions (use path as `object_id`)
181+
* `registered-models` - For registered model permissions
182+
* `experiments` - For experiment permissions
183+
* `sql-dashboards` - For legacy SQL dashboard permissions
184+
* `sql/warehouses` - For SQL warehouse permissions
185+
* `queries` - For query permissions
186+
* `alerts` - For alert permissions
187+
* `dashboards` - For Lakeview dashboard permissions
188+
* `repos` - For repo permissions
189+
* `authorization` - For authorization permissions (use `tokens` or `passwords` as `object_id`)
190+
* `serving-endpoints` - For model serving endpoint permissions
191+
* `vector-search-endpoints` - For vector search endpoint permissions
192+
193+
* `object_id` - (Required) The ID or path of the object. For notebooks, directories, and workspace files, use the path (e.g., `/Shared/notebook`). For authorization, use `tokens` or `passwords`. For other objects, use the resource ID.
194+
160195
* `permission_level` - (Required) The permission level to grant. The available permission levels depend on the object type. Common values include `CAN_MANAGE`, `CAN_USE`, `CAN_VIEW`, `CAN_RUN`, `CAN_EDIT`, `CAN_READ`, `CAN_RESTART`, `CAN_ATTACH_TO`.
161196

162197
Exactly one of the following principal identifiers must be specified:
@@ -165,32 +200,6 @@ Exactly one of the following principal identifiers must be specified:
165200
* `group_name` - (Optional) Group name to grant permissions to. Conflicts with `user_name` and `service_principal_name`.
166201
* `service_principal_name` - (Optional) Application ID of the service principal. Conflicts with `user_name` and `group_name`.
167202

168-
Exactly one of the following object identifiers must be specified:
169-
170-
* `cluster_id` - (Optional) ID of the [databricks_cluster](cluster.md).
171-
* `cluster_policy_id` - (Optional) ID of the [databricks_cluster_policy](cluster_policy.md).
172-
* `instance_pool_id` - (Optional) ID of the [databricks_instance_pool](instance_pool.md).
173-
* `job_id` - (Optional) ID of the [databricks_job](job.md).
174-
* `pipeline_id` - (Optional) ID of the [databricks_pipeline](pipeline.md).
175-
* `notebook_id` - (Optional) ID of the [databricks_notebook](notebook.md). Can be used when the notebook is referenced by ID.
176-
* `notebook_path` - (Optional) Path to the [databricks_notebook](notebook.md).
177-
* `directory_id` - (Optional) ID of the [databricks_directory](directory.md).
178-
* `directory_path` - (Optional) Path to the [databricks_directory](directory.md).
179-
* `workspace_file_id` - (Optional) ID of the [databricks_workspace_file](workspace_file.md).
180-
* `workspace_file_path` - (Optional) Path to the [databricks_workspace_file](workspace_file.md).
181-
* `registered_model_id` - (Optional) ID of the [databricks_mlflow_model](mlflow_model.md).
182-
* `experiment_id` - (Optional) ID of the [databricks_mlflow_experiment](mlflow_experiment.md).
183-
* `sql_dashboard_id` - (Optional) ID of the legacy [databricks_sql_dashboard](sql_dashboard.md).
184-
* `sql_endpoint_id` - (Optional) ID of the [databricks_sql_endpoint](sql_endpoint.md).
185-
* `sql_query_id` - (Optional) ID of the [databricks_query](query.md).
186-
* `sql_alert_id` - (Optional) ID of the [databricks_alert](alert.md).
187-
* `dashboard_id` - (Optional) ID of the [databricks_dashboard](dashboard.md) (Lakeview).
188-
* `repo_id` - (Optional) ID of the [databricks_repo](repo.md).
189-
* `repo_path` - (Optional) Path to the [databricks_repo](repo.md).
190-
* `authorization` - (Optional) Type of authorization. Currently supports `tokens` and `passwords`.
191-
* `serving_endpoint_id` - (Optional) ID of the [databricks_model_serving](model_serving.md) endpoint.
192-
* `vector_search_endpoint_id` - (Optional) ID of the [databricks_vector_search_endpoint](vector_search_endpoint.md).
193-
194203
## Attribute Reference
195204

196205
In addition to all arguments above, the following attributes are exported:
@@ -249,21 +258,24 @@ resource "databricks_permissions" "cluster_all" {
249258

250259
```hcl
251260
resource "databricks_permission" "cluster_de" {
252-
cluster_id = databricks_cluster.shared.id
261+
object_type = "clusters"
262+
object_id = databricks_cluster.shared.id
253263
group_name = "Data Engineers"
254264
permission_level = "CAN_RESTART"
255265
}
256266
257267
resource "databricks_permission" "cluster_analyst" {
258-
cluster_id = databricks_cluster.shared.id
268+
object_type = "clusters"
269+
object_id = databricks_cluster.shared.id
259270
user_name = "analyst@company.com"
260271
permission_level = "CAN_ATTACH_TO"
261272
}
262273
263274
# Adding a third principal is a separate resource
264275
# No need to modify existing resources
265276
resource "databricks_permission" "cluster_viewer" {
266-
cluster_id = databricks_cluster.shared.id
277+
object_type = "clusters"
278+
object_id = databricks_cluster.shared.id
267279
group_name = "Viewers"
268280
permission_level = "CAN_ATTACH_TO"
269281
}
Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,58 @@
11
package permissions
22

33
import (
4+
"fmt"
45
"sync"
56
)
67

7-
// objectMutexManager manages mutexes per object ID to prevent concurrent
8+
// objectMutexManager manages mutexes per object to prevent concurrent
89
// operations on the same Databricks object that could lead to race conditions.
910
//
1011
// This is particularly important for Delete operations where multiple
1112
// databricks_permission resources for the same object might be deleted
1213
// concurrently, each doing GET -> filter -> SET, which could result in
1314
// lost permission updates.
1415
type objectMutexManager struct {
15-
mutexes map[string]*sync.Mutex
16-
mapLock sync.Mutex
16+
mutexes sync.Map // map[string]*sync.Mutex
1717
}
1818

1919
// globalObjectMutexManager is the singleton instance used by all permission resources
20-
var globalObjectMutexManager = &objectMutexManager{
21-
mutexes: make(map[string]*sync.Mutex),
22-
}
20+
var globalObjectMutexManager = &objectMutexManager{}
2321

24-
// Lock acquires a mutex for the given object ID.
25-
// Each object ID gets its own mutex to allow concurrent operations on different objects
22+
// Lock acquires a mutex for the given object type and ID.
23+
// Each object gets its own mutex to allow concurrent operations on different objects
2624
// while serializing operations on the same object.
27-
func (m *objectMutexManager) Lock(objectID string) {
28-
m.mapLock.Lock()
29-
mu, exists := m.mutexes[objectID]
30-
if !exists {
31-
mu = &sync.Mutex{}
32-
m.mutexes[objectID] = mu
33-
}
34-
m.mapLock.Unlock()
25+
func (m *objectMutexManager) Lock(objectType, objectID string) {
26+
key := fmt.Sprintf("%s/%s", objectType, objectID)
27+
28+
// LoadOrStore returns the existing value if present, otherwise stores and returns the given value
29+
value, _ := m.mutexes.LoadOrStore(key, &sync.Mutex{})
30+
mu := value.(*sync.Mutex)
3531

36-
// Lock the object-specific mutex (outside the map lock to avoid deadlock)
32+
// Lock the object-specific mutex
3733
mu.Lock()
3834
}
3935

40-
// Unlock releases the mutex for the given object ID.
41-
func (m *objectMutexManager) Unlock(objectID string) {
42-
m.mapLock.Lock()
43-
mu, exists := m.mutexes[objectID]
44-
m.mapLock.Unlock()
36+
// Unlock releases the mutex for the given object type and ID.
37+
func (m *objectMutexManager) Unlock(objectType, objectID string) {
38+
key := fmt.Sprintf("%s/%s", objectType, objectID)
4539

46-
if exists {
40+
value, ok := m.mutexes.Load(key)
41+
if ok {
42+
mu := value.(*sync.Mutex)
4743
mu.Unlock()
4844
}
4945
}
5046

51-
// lockObject acquires a lock for the given object ID.
47+
// lockObject acquires a lock for the given object type and ID.
5248
// This should be called at the start of any operation that modifies permissions.
53-
func lockObject(objectID string) {
54-
globalObjectMutexManager.Lock(objectID)
49+
func lockObject(objectType, objectID string) {
50+
globalObjectMutexManager.Lock(objectType, objectID)
5551
}
5652

57-
// unlockObject releases the lock for the given object ID.
53+
// unlockObject releases the lock for the given object type and ID.
5854
// This should be called at the end of any operation that modifies permissions.
5955
// Use defer to ensure it's always called even if the operation panics.
60-
func unlockObject(objectID string) {
61-
globalObjectMutexManager.Unlock(objectID)
56+
func unlockObject(objectType, objectID string) {
57+
globalObjectMutexManager.Unlock(objectType, objectID)
6258
}

0 commit comments

Comments
 (0)