Skip to content

Commit 4312330

Browse files
authored
Added missing schema grants privileges (#4139)
## Changes Added missing schema grants privileges ## Why Fixes #4008 ## Tests Added a test which compares all privileges available in the SDK with those defined in the schema / skiplist <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
1 parent 2ffcb5c commit 4312330

File tree

4 files changed

+167
-17
lines changed

4 files changed

+167
-17
lines changed

NEXT_CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* Allow domain_friendly_name to be used in name_prefix in development mode ([#4173](https://github.com/databricks/cli/pull/4173))
1010

1111
### Bundles
12+
* Pass SYSTEM_ACCESSTOKEN from env to the Terraform provider ([#4135](https://github.com/databricks/cli/pull/4135))
13+
* Added missing schema grants privileges ([#4139](https://github.com/databricks/cli/pull/4139))
1214
* Fix app deployment failure when app is in `DELETING` state ([#4176](https://github.com/databricks/cli/pull/4176))
1315
* Add `ipykernel` to the `default` template to enable Databricks Connect notebooks in Cursor/VS Code ([#4164](https://github.com/databricks/cli/pull/4164))
1416
* Add interactive SQL warehouse picker to `default-sql` and `dbt-sql` bundle templates ([#4170](https://github.com/databricks/cli/pull/4170))

bundle/config/resources/schema.go

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,22 @@ import (
1717
type SchemaGrantPrivilege string
1818

1919
const (
20-
SchemaGrantPrivilegeAllPrivileges SchemaGrantPrivilege = "ALL_PRIVILEGES"
21-
SchemaGrantPrivilegeApplyTag SchemaGrantPrivilege = "APPLY_TAG"
22-
SchemaGrantPrivilegeCreateFunction SchemaGrantPrivilege = "CREATE_FUNCTION"
23-
SchemaGrantPrivilegeCreateTable SchemaGrantPrivilege = "CREATE_TABLE"
24-
SchemaGrantPrivilegeCreateVolume SchemaGrantPrivilege = "CREATE_VOLUME"
25-
SchemaGrantPrivilegeManage SchemaGrantPrivilege = "MANAGE"
26-
SchemaGrantPrivilegeUseSchema SchemaGrantPrivilege = "USE_SCHEMA"
27-
SchemaGrantPrivilegeExecute SchemaGrantPrivilege = "EXECUTE"
28-
SchemaGrantPrivilegeModify SchemaGrantPrivilege = "MODIFY"
29-
SchemaGrantPrivilegeRefresh SchemaGrantPrivilege = "REFRESH"
30-
SchemaGrantPrivilegeSelect SchemaGrantPrivilege = "SELECT"
31-
SchemaGrantPrivilegeReadVolume SchemaGrantPrivilege = "READ_VOLUME"
32-
SchemaGrantPrivilegeWriteVolume SchemaGrantPrivilege = "WRITE_VOLUME"
20+
SchemaGrantPrivilegeAllPrivileges SchemaGrantPrivilege = "ALL_PRIVILEGES"
21+
SchemaGrantPrivilegeApplyTag SchemaGrantPrivilege = "APPLY_TAG"
22+
SchemaGrantPrivilegeCreateFunction SchemaGrantPrivilege = "CREATE_FUNCTION"
23+
SchemaGrantPrivilegeCreateMaterializedView SchemaGrantPrivilege = "CREATE_MATERIALIZED_VIEW"
24+
SchemaGrantPrivilegeCreateModel SchemaGrantPrivilege = "CREATE_MODEL"
25+
SchemaGrantPrivilegeCreateTable SchemaGrantPrivilege = "CREATE_TABLE"
26+
SchemaGrantPrivilegeCreateVolume SchemaGrantPrivilege = "CREATE_VOLUME"
27+
SchemaGrantPrivilegeExecute SchemaGrantPrivilege = "EXECUTE"
28+
SchemaGrantPrivilegeExternalUseSchema SchemaGrantPrivilege = "EXTERNAL_USE_SCHEMA"
29+
SchemaGrantPrivilegeManage SchemaGrantPrivilege = "MANAGE"
30+
SchemaGrantPrivilegeModify SchemaGrantPrivilege = "MODIFY"
31+
SchemaGrantPrivilegeReadVolume SchemaGrantPrivilege = "READ_VOLUME"
32+
SchemaGrantPrivilegeRefresh SchemaGrantPrivilege = "REFRESH"
33+
SchemaGrantPrivilegeSelect SchemaGrantPrivilege = "SELECT"
34+
SchemaGrantPrivilegeUseSchema SchemaGrantPrivilege = "USE_SCHEMA"
35+
SchemaGrantPrivilegeWriteVolume SchemaGrantPrivilege = "WRITE_VOLUME"
3336
)
3437

3538
// Values returns all valid SchemaGrantPrivilege values
@@ -38,15 +41,18 @@ func (SchemaGrantPrivilege) Values() []SchemaGrantPrivilege {
3841
SchemaGrantPrivilegeAllPrivileges,
3942
SchemaGrantPrivilegeApplyTag,
4043
SchemaGrantPrivilegeCreateFunction,
44+
SchemaGrantPrivilegeCreateMaterializedView,
45+
SchemaGrantPrivilegeCreateModel,
4146
SchemaGrantPrivilegeCreateTable,
4247
SchemaGrantPrivilegeCreateVolume,
43-
SchemaGrantPrivilegeManage,
44-
SchemaGrantPrivilegeUseSchema,
4548
SchemaGrantPrivilegeExecute,
49+
SchemaGrantPrivilegeExternalUseSchema,
50+
SchemaGrantPrivilegeManage,
4651
SchemaGrantPrivilegeModify,
52+
SchemaGrantPrivilegeReadVolume,
4753
SchemaGrantPrivilegeRefresh,
4854
SchemaGrantPrivilegeSelect,
49-
SchemaGrantPrivilegeReadVolume,
55+
SchemaGrantPrivilegeUseSchema,
5056
SchemaGrantPrivilegeWriteVolume,
5157
}
5258
}

bundle/config/resources/schema_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package resources
22

33
import (
44
"context"
5+
"fmt"
6+
"sort"
57
"testing"
68

79
"github.com/databricks/databricks-sdk-go/apierr"
810
"github.com/databricks/databricks-sdk-go/experimental/mocks"
11+
"github.com/databricks/databricks-sdk-go/service/catalog"
12+
"github.com/stretchr/testify/assert"
913
"github.com/stretchr/testify/mock"
1014
"github.com/stretchr/testify/require"
1115
)
@@ -24,3 +28,141 @@ func TestSchemaNotFound(t *testing.T) {
2428
require.Falsef(t, exists, "Exists should return false when getting a 404 response from Workspace")
2529
require.NoErrorf(t, err, "Exists should not return an error when getting a 404 response from Workspace")
2630
}
31+
32+
func TestSchemaGrantPrivilegesExhaustive(t *testing.T) {
33+
// Privileges that are NOT valid for schemas and should be skipped.
34+
// These are valid for other securable types (catalogs, connections, etc.)
35+
// Source: https://docs.databricks.com/en/data-governance/unity-catalog/manage-privileges/privileges.html
36+
skippedPrivileges := map[catalog.Privilege]string{
37+
// Catalog-level privileges
38+
catalog.PrivilegeCreateCatalog: "catalog-level",
39+
catalog.PrivilegeCreateSchema: "catalog-level",
40+
catalog.PrivilegeUseCatalog: "catalog-level",
41+
42+
// Connection-level privileges
43+
catalog.PrivilegeCreateConnection: "connection-level",
44+
catalog.PrivilegeUseConnection: "connection-level",
45+
46+
// Storage-level privileges
47+
catalog.PrivilegeCreateExternalLocation: "storage-level",
48+
catalog.PrivilegeCreateExternalTable: "storage-level",
49+
catalog.PrivilegeCreateExternalVolume: "storage-level",
50+
catalog.PrivilegeCreateManagedStorage: "storage-level",
51+
catalog.PrivilegeCreateStorageCredential: "storage-level",
52+
catalog.PrivilegeReadFiles: "storage-level",
53+
catalog.PrivilegeReadPrivateFiles: "storage-level",
54+
catalog.PrivilegeWriteFiles: "storage-level",
55+
catalog.PrivilegeWritePrivateFiles: "storage-level",
56+
57+
// Metastore-level privileges
58+
catalog.PrivilegeCreateProvider: "metastore-level",
59+
catalog.PrivilegeCreateRecipient: "metastore-level",
60+
catalog.PrivilegeCreateShare: "metastore-level",
61+
catalog.PrivilegeManageAllowlist: "metastore-level",
62+
catalog.PrivilegeUseProvider: "metastore-level",
63+
catalog.PrivilegeUseRecipient: "metastore-level",
64+
catalog.PrivilegeUseMarketplaceAssets: "metastore-level",
65+
catalog.PrivilegeCreateServiceCredential: "metastore-level",
66+
67+
// Share-level privileges
68+
catalog.PrivilegeSetSharePermission: "share-level",
69+
catalog.PrivilegeUseShare: "share-level",
70+
71+
// Clean room-level privileges
72+
catalog.PrivilegeCreateCleanRoom: "clean-room-level",
73+
catalog.PrivilegeExecuteCleanRoomTask: "clean-room-level",
74+
catalog.PrivilegeModifyCleanRoom: "clean-room-level",
75+
76+
// Foreign securable privileges
77+
catalog.PrivilegeCreateForeignCatalog: "foreign-securable",
78+
catalog.PrivilegeCreateForeignSecurable: "foreign-securable",
79+
80+
// Table/view-level privileges (not directly grantable on schema)
81+
catalog.PrivilegeCreateView: "table-level",
82+
83+
// Generic privileges that don't apply to schemas
84+
catalog.PrivilegeAccess: "generic",
85+
catalog.PrivilegeBrowse: "generic",
86+
catalog.PrivilegeCreate: "generic",
87+
catalog.PrivilegeUsage: "generic",
88+
}
89+
90+
// Get all SDK privileges dynamically
91+
var p catalog.Privilege
92+
sdkPrivileges := p.Values()
93+
94+
// Get all privileges defined in our SchemaGrantPrivilege enum
95+
definedPrivileges := SchemaGrantPrivilege("").Values()
96+
definedPrivilegeMap := make(map[catalog.Privilege]bool)
97+
for _, priv := range definedPrivileges {
98+
definedPrivilegeMap[catalog.Privilege(priv)] = true
99+
}
100+
101+
// Build list of missing and unexpected privileges
102+
var missingPrivileges []catalog.Privilege
103+
var unexpectedPrivileges []catalog.Privilege
104+
105+
// Check each SDK privilege
106+
for _, sdkPriv := range sdkPrivileges {
107+
isInDefined := definedPrivilegeMap[sdkPriv]
108+
isInSkipList := skippedPrivileges[sdkPriv] != ""
109+
110+
if !isInDefined && !isInSkipList {
111+
// SDK has a privilege that we neither defined nor explicitly skipped
112+
missingPrivileges = append(missingPrivileges, sdkPriv)
113+
}
114+
115+
if isInDefined && isInSkipList {
116+
// We defined a privilege that's in the skip list (contradiction)
117+
unexpectedPrivileges = append(unexpectedPrivileges, sdkPriv)
118+
}
119+
}
120+
121+
// Check for privileges we defined that don't exist in SDK
122+
sdkPrivilegeMap := make(map[catalog.Privilege]bool)
123+
for _, priv := range sdkPrivileges {
124+
sdkPrivilegeMap[priv] = true
125+
}
126+
127+
var invalidPrivileges []SchemaGrantPrivilege
128+
for _, definedPriv := range definedPrivileges {
129+
if !sdkPrivilegeMap[catalog.Privilege(definedPriv)] {
130+
invalidPrivileges = append(invalidPrivileges, definedPriv)
131+
}
132+
}
133+
134+
// Report errors
135+
if len(missingPrivileges) > 0 {
136+
sort.Slice(missingPrivileges, func(i, j int) bool {
137+
return missingPrivileges[i] < missingPrivileges[j]
138+
})
139+
assert.Fail(t, fmt.Sprintf(
140+
"Found %d SDK privilege(s) that are not in SchemaGrantPrivilege and not in skip list.\n"+
141+
"If these privileges are valid for schemas, add them to SchemaGrantPrivilege in schema.go.\n"+
142+
"If they are NOT valid for schemas, add them to skippedPrivileges in schema_test.go.\n"+
143+
"Missing privileges: %v",
144+
len(missingPrivileges), missingPrivileges))
145+
}
146+
147+
if len(unexpectedPrivileges) > 0 {
148+
sort.Slice(unexpectedPrivileges, func(i, j int) bool {
149+
return unexpectedPrivileges[i] < unexpectedPrivileges[j]
150+
})
151+
assert.Fail(t, fmt.Sprintf(
152+
"Found %d privilege(s) that are both defined in SchemaGrantPrivilege AND in skip list.\n"+
153+
"This is a contradiction - remove them from either SchemaGrantPrivilege or the skip list.\n"+
154+
"Conflicting privileges: %v",
155+
len(unexpectedPrivileges), unexpectedPrivileges))
156+
}
157+
158+
if len(invalidPrivileges) > 0 {
159+
sort.Slice(invalidPrivileges, func(i, j int) bool {
160+
return invalidPrivileges[i] < invalidPrivileges[j]
161+
})
162+
assert.Fail(t, fmt.Sprintf(
163+
"Found %d privilege(s) in SchemaGrantPrivilege that don't exist in the SDK.\n"+
164+
"Remove these from SchemaGrantPrivilege in schema.go.\n"+
165+
"Invalid privileges: %v",
166+
len(invalidPrivileges), invalidPrivileges))
167+
}
168+
}

bundle/internal/validation/generated/enum_fields.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)