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
1 change: 1 addition & 0 deletions .nextchanges/bundles/5818.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* `bundle validate` now reports a clear error when a `sql_warehouse` is missing a `name` (including whitespace-only names), and a warning when a grant is missing a `principal` ([#5818](https://github.com/databricks/cli/pull/5818)).
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ app resource 'rname' should have either source_code_path or git_source field
}

=== resources.sql_warehouses.rname ===
Error: sql_warehouse name is required
at resources.sql_warehouses.rname
in databricks.yml:6:12

{
"sql_warehouses": {
"rname": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ Warning: unknown field: grants
at resources.sql_warehouses.rname
in databricks.yml:7:7

Error: sql_warehouse name is required
at resources.sql_warehouses.rname
in databricks.yml:7:7

{
"sql_warehouses": {
"rname": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ app resource 'rname' should have either source_code_path or git_source field
}

=== resources.sql_warehouses.rname ===
Error: sql_warehouse name is required
at resources.sql_warehouses.rname
in databricks.yml:7:7

{
"sql_warehouses": {
"rname": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
bundle:
name: test-bundle

resources:
catalogs:
my_catalog:
name: my_catalog
grants:
# Missing principal (warns).
- privileges:
- ALL_PRIVILEGES
schemas:
my_schema:
catalog_name: my_catalog
name: my_schema
grants:
# Valid grant (principal set).
- principal: alice@example.com
privileges:
- USE_SCHEMA

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions acceptance/bundle/validate/grants_required_principal/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

>>> [CLI] bundle validate
Warning: grant principal is required
at resources.catalogs.my_catalog.grants[0]
in databricks.yml:10:11

Name: test-bundle
Target: default
Workspace:
User: [USERNAME]
Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default

Found 1 warning
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
trace $CLI bundle validate
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Catalogs and schemas are only supported by the direct engine.
[EnvMatrix]
DATABRICKS_BUNDLE_ENGINE = ["direct"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
bundle:
name: test-bundle

resources:
sql_warehouses:
# Missing name (required by the backend).
my_warehouse:
cluster_size: "2X-Small"
# Whitespace-only name, treated as missing (name.trim.nonEmpty).
blank_warehouse:
name: " "
cluster_size: "2X-Small"

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions acceptance/bundle/validate/sql_warehouse_required_name/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

>>> [CLI] bundle validate
Error: sql_warehouse name is required
at resources.sql_warehouses.blank_warehouse
in databricks.yml:11:7

Error: sql_warehouse name is required
at resources.sql_warehouses.my_warehouse
in databricks.yml:8:7

Name: test-bundle
Target: default
Workspace:
User: [USERNAME]
Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default

Found 2 errors

Exit code: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
trace $CLI bundle validate
71 changes: 68 additions & 3 deletions bundle/config/validate/required.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"fmt"
"slices"
"strings"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/internal/validation/generated"
Expand Down Expand Up @@ -68,7 +69,14 @@ func warnForMissingFields(ctx context.Context, b *bundle.Bundle) diag.Diagnostic
return diag.FromErr(err)
}

// Sort diagnostics to make them deterministic
sortDiagnostics(diags)

return diags
}

// sortDiagnostics orders diagnostics deterministically, since they are collected
// by walking maps with random iteration order.
func sortDiagnostics(diags diag.Diagnostics) {
slices.SortFunc(diags, func(a, b diag.Diagnostic) int {
// First sort by summary
if n := cmp.Compare(a.Summary, b.Summary); n != 0 {
Expand All @@ -78,8 +86,6 @@ func warnForMissingFields(ctx context.Context, b *bundle.Bundle) diag.Diagnostic
// Finally sort by locations as a tie breaker if summaries are the same.
return cmp.Compare(fmt.Sprintf("%v", a.Locations), fmt.Sprintf("%v", b.Locations))
})

return diags
}

// Bespoke code to error for fields that are not marked as required in the Go SDK / OpenAPI spec.
Expand Down Expand Up @@ -119,14 +125,73 @@ func errorForMissingFields(ctx context.Context, b *bundle.Bundle) diag.Diagnosti
})
}

// sql_warehouses.name is optional in the SDK (json:"name,omitempty") but required
// by the backend, which rejects whitespace-only names (name.trim.nonEmpty).
for key, warehouse := range b.Config.Resources.SqlWarehouses {
if strings.TrimSpace(warehouse.Name) == "" {
path := "resources.sql_warehouses." + key
diags = diags.Append(diag.Diagnostic{
Severity: diag.Error,
Summary: "sql_warehouse name is required",
Locations: b.Config.GetLocations(path),
Paths: []dyn.Path{dyn.MustPathFromString(path)},
})
}
}

sortDiagnostics(diags)

return diags
}

// warnForMissingGrantPrincipals warns for any grant that is missing a principal.
// grants[*].principal is optional in the SDK (json:"principal,omitempty") but the
// backend requires it. Grants exist on every securable, so match any resource type.
func warnForMissingGrantPrincipals(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
diags := diag.Diagnostics{}

_, err := dyn.MapByPattern(
b.Config.Value(),
dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey(), dyn.Key("grants"), dyn.AnyIndex()),
func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
if isMissingOrEmptyString(v.Get("principal")) {
diags = diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: "grant principal is required",
Locations: v.Locations(),
Paths: []dyn.Path{slices.Clone(p)},
})
}
return v, nil
},
)
if err != nil {
return diag.FromErr(err)
}

sortDiagnostics(diags)

return diags
}

// isMissingOrEmptyString reports whether v is unset, null, or an empty string.
func isMissingOrEmptyString(v dyn.Value) bool {
switch v.Kind() {
case dyn.KindInvalid, dyn.KindNil:
return true
case dyn.KindString:
return v.MustString() == ""
default:
return false
}
}

func (f *required) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
diags := errorForMissingFields(ctx, b)
if diags.HasError() {
return diags
}
diags = diags.Extend(warnForMissingFields(ctx, b))
diags = diags.Extend(warnForMissingGrantPrincipals(ctx, b))
return diags
}
Loading