From 6c1c73740d5c487085de826235e53c110ad9559d Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 15 Sep 2025 11:43:47 +0200 Subject: [PATCH 01/15] Add support for .dbalert.json files --- bundle/config/mutator/load_dbalert_files.go | 112 ++++++++++++++++++++ bundle/config/resources/alerts.go | 5 + 2 files changed, 117 insertions(+) create mode 100644 bundle/config/mutator/load_dbalert_files.go diff --git a/bundle/config/mutator/load_dbalert_files.go b/bundle/config/mutator/load_dbalert_files.go new file mode 100644 index 0000000000..1c0794b0a6 --- /dev/null +++ b/bundle/config/mutator/load_dbalert_files.go @@ -0,0 +1,112 @@ +package mutator + +import ( + "context" + "encoding/json" + "fmt" + "os" + "slices" + "strings" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/databricks-sdk-go/service/sql" +) + +type loadDBAlertFiles struct{} + +func LoadDBAlertFiles() bundle.Mutator { + return &loadDBAlertFiles{} +} + +func (m *loadDBAlertFiles) Name() string { + return "LoadDBAlertFiles" +} + +type dbalertFile struct { + sql.AlertV2 + + // query_text and custom_description are split into lines to make it easier to view the diff + // in a Git editor. + QueryLines []string `json:"query_lines,omitempty"` + CustomDescriptionLines []string `json:"custom_description_lines,omitempty"` +} + +func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + // Fields that are only settable in the API, and are not allowed in .dbalert.json. + // We will only allow these fields to be set in the bundle YAML when an .dbalert.json is + // specified. This is done to only have one way to set these fields when a .dbalert.json is + // specified. + allowedInYAML := []string{"warehouse_id", "display_name"} + + for k, alert := range b.Config.Resources.Alerts { + if alert.FilePath == "" { + continue + } + + alertV, err := dyn.GetByPath(b.Config.Value(), dyn.NewPath(dyn.Key("resources"), dyn.Key("alerts"), dyn.Key(k))) + if err != nil { + return diag.FromErr(err) + } + + // No other fields other than allowedInYAML should be set in the bundle YAML. + m, ok := alertV.AsMap() + if !ok { + return diag.FromErr(fmt.Errorf("internal error: alert value is not a map: %w", err)) + } + + for _, p := range m.Pairs() { + k := p.Key.MustString() + v := p.Value + + if slices.Contains(allowedInYAML, k) { + continue + } + + if v.Kind() == dyn.KindNil || v.Kind() == dyn.KindInvalid { + continue + } + + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("field %s is not allowed in the bundle YAML when a .dbalert.json is specified. Please set it in the .dbalert.json file instead. Only allowed fields are: %s", k, strings.Join(allowedInYAML, ", ")), + Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.%s", k, k))}, + Locations: v.Locations(), + }, + } + } + + content, err := os.ReadFile(alert.FilePath) + if err != nil { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("failed to read .dbalert.json file %s: %s", alert.FilePath, err), + Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", k))}, + Locations: alertV.Get("file_path").Locations(), + }, + } + } + + var dbalertFromFile dbalertFile + err = json.Unmarshal(content, &dbalertFromFile) + if err != nil { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: fmt.Sprintf("failed to parse .dbalert.json file %s: %s", alert.FilePath, err), + Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", k))}, + Locations: alertV.Get("file_path").Locations(), + }, + } + } + + // TODO: Parse that the file does not have any variable interpolations. + + // write values from the file to the alert. + b.Config.Resources.Alerts[k].AlertV2 = dbalertFromFile.AlertV2 + + } +} diff --git a/bundle/config/resources/alerts.go b/bundle/config/resources/alerts.go index 6a571ca8af..ea3f699650 100644 --- a/bundle/config/resources/alerts.go +++ b/bundle/config/resources/alerts.go @@ -34,6 +34,11 @@ type Alert struct { sql.AlertV2 //nolint AlertV2 also defines Id and URL field with the same json tag "id" and "url" Permissions []AlertPermission `json:"permissions,omitempty"` + + // Filepath points to the local .dbalert.json file containing the alert definition. + // If specified, any fields that are part of the .dbalert.json file schema will not be allowed in + // the bundle config. + FilePath string `json:"file_path,omitempty"` } func (a *Alert) UnmarshalJSON(b []byte) error { From e187e232be5a7f0c6cd03790f4a52e15af253d33 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 8 Dec 2025 02:55:12 +0100 Subject: [PATCH 02/15] update with latest changes --- acceptance/bundle/refschema/out.fields.txt | 1 + .../alerts/with_file/alert.dbalert.json | 26 +++++++ .../alerts/with_file/databricks.yml.tmpl | 9 +++ .../resources/alerts/with_file/out.test.toml | 5 ++ .../resources/alerts/with_file/output.txt | 68 ++++++++++++++++ .../bundle/resources/alerts/with_file/script | 18 +++++ .../resources/alerts/with_file/test.toml | 7 ++ .../alert.dbalert.json | 26 +++++++ .../databricks.yml | 11 +++ .../out.test.toml | 5 ++ .../output.txt | 17 ++++ .../with_file_not_allowed_field_error/script | 1 + .../test.toml | 3 + .../alert.dbalert.json | 26 +++++++ .../databricks.yml | 9 +++ .../out.test.toml | 5 ++ .../output.txt | 15 ++++ .../script | 1 + .../test.toml | 3 + bundle/config/mutator/load_dbalert_files.go | 69 ++++++++++++++++- .../config/mutator/load_dbalert_files_test.go | 77 ++++--------------- .../resourcemutator/resource_mutator.go | 4 + 22 files changed, 338 insertions(+), 68 deletions(-) create mode 100644 acceptance/bundle/resources/alerts/with_file/alert.dbalert.json create mode 100644 acceptance/bundle/resources/alerts/with_file/databricks.yml.tmpl create mode 100644 acceptance/bundle/resources/alerts/with_file/out.test.toml create mode 100644 acceptance/bundle/resources/alerts/with_file/output.txt create mode 100755 acceptance/bundle/resources/alerts/with_file/script create mode 100644 acceptance/bundle/resources/alerts/with_file/test.toml create mode 100644 acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/alert.dbalert.json create mode 100644 acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/databricks.yml create mode 100644 acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/out.test.toml create mode 100644 acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt create mode 100755 acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/script create mode 100644 acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/test.toml create mode 100644 acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/alert.dbalert.json create mode 100644 acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/databricks.yml create mode 100644 acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/out.test.toml create mode 100644 acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt create mode 100755 acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/script create mode 100644 acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/test.toml diff --git a/acceptance/bundle/refschema/out.fields.txt b/acceptance/bundle/refschema/out.fields.txt index 2394ea7989..5f932d62c1 100644 --- a/acceptance/bundle/refschema/out.fields.txt +++ b/acceptance/bundle/refschema/out.fields.txt @@ -30,6 +30,7 @@ resources.alerts.*.evaluation.threshold.value *sql.AlertV2OperandValue ALL resources.alerts.*.evaluation.threshold.value.bool_value bool ALL resources.alerts.*.evaluation.threshold.value.double_value float64 ALL resources.alerts.*.evaluation.threshold.value.string_value string ALL +resources.alerts.*.file_path string INPUT resources.alerts.*.id string ALL resources.alerts.*.lifecycle resources.Lifecycle INPUT resources.alerts.*.lifecycle.prevent_destroy bool INPUT diff --git a/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json new file mode 100644 index 0000000000..9d88eab236 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json @@ -0,0 +1,26 @@ +{ + "custom_summary": "My alert from file", + "evaluation": { + "comparison_operator": "EQUAL", + "notification": { + "notify_on_ok": false, + "retrigger_seconds": 1 + }, + "source": { + "aggregation": "MAX", + "display": "1", + "name": "1" + }, + "threshold": { + "value": { + "double_value": 2 + } + } + }, + "query_text": "select 2", + "schedule": { + "pause_status": "UNPAUSED", + "quartz_cron_schedule": "44 19 */1 * * ?", + "timezone_id": "Europe/Amsterdam" + } +} diff --git a/acceptance/bundle/resources/alerts/with_file/databricks.yml.tmpl b/acceptance/bundle/resources/alerts/with_file/databricks.yml.tmpl new file mode 100644 index 0000000000..db35cf5a4e --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file/databricks.yml.tmpl @@ -0,0 +1,9 @@ +bundle: + name: alerts-with-file-$UNIQUE_NAME + +resources: + alerts: + myalert: + warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID + display_name: "My alert with file" + file_path: ./alert.dbalert.json diff --git a/acceptance/bundle/resources/alerts/with_file/out.test.toml b/acceptance/bundle/resources/alerts/with_file/out.test.toml new file mode 100644 index 0000000000..01ed6822af --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = true + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/alerts/with_file/output.txt b/acceptance/bundle/resources/alerts/with_file/output.txt new file mode 100644 index 0000000000..ffcc45609d --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file/output.txt @@ -0,0 +1,68 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] alerts-v2 get-alert [ALERT_ID] +{ + "display_name": "My alert with file", + "lifecycle_state": "ACTIVE", + "custom_summary": "My alert from file", + "evaluation": { + "comparison_operator": "EQUAL", + "notification": { + "notify_on_ok": false, + "retrigger_seconds": 1 + }, + "source": { + "aggregation": "MAX", + "display": "1", + "name": "1" + }, + "threshold": { + "value": { + "double_value": 2 + } + } + }, + "query_text": "select 2", + "schedule": { + "pause_status": "UNPAUSED", + "quartz_cron_schedule": "44 19 */1 * * ?", + "timezone_id": "Europe/Amsterdam" + }, + "warehouse_id": "[TEST_DEFAULT_WAREHOUSE_ID]" +} + +=== assert that no permanent drift happens +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.alerts.myalert + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! + +>>> [CLI] alerts-v2 get-alert [ALERT_ID] +{ + "display_name": "My alert with file", + "lifecycle_state": "DELETED" +} + +>>> [CLI] bundle summary +Name: alerts-with-file-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default +Resources: + Alerts: + myalert: + Name: My alert with file + URL: (not deployed) diff --git a/acceptance/bundle/resources/alerts/with_file/script b/acceptance/bundle/resources/alerts/with_file/script new file mode 100755 index 0000000000..21e11b09af --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file/script @@ -0,0 +1,18 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +trace $CLI bundle deploy + +alert_id=$($CLI bundle summary --output json | jq -r '.resources.alerts.myalert.id') + +echo "$alert_id:ALERT_ID" >> ACC_REPLS + +trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state, custom_summary, evaluation, query_text, schedule, warehouse_id}' + +title "assert that no permanent drift happens" +trace $CLI bundle plan + +trace $CLI bundle destroy --auto-approve + +trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state}' + +trace $CLI bundle summary diff --git a/acceptance/bundle/resources/alerts/with_file/test.toml b/acceptance/bundle/resources/alerts/with_file/test.toml new file mode 100644 index 0000000000..e6228608dd --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file/test.toml @@ -0,0 +1,7 @@ +Local = true +Cloud = true +RecordRequests = false +Ignore = [".databricks"] + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/alert.dbalert.json new file mode 100644 index 0000000000..9d88eab236 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/alert.dbalert.json @@ -0,0 +1,26 @@ +{ + "custom_summary": "My alert from file", + "evaluation": { + "comparison_operator": "EQUAL", + "notification": { + "notify_on_ok": false, + "retrigger_seconds": 1 + }, + "source": { + "aggregation": "MAX", + "display": "1", + "name": "1" + }, + "threshold": { + "value": { + "double_value": 2 + } + } + }, + "query_text": "select 2", + "schedule": { + "pause_status": "UNPAUSED", + "quartz_cron_schedule": "44 19 */1 * * ?", + "timezone_id": "Europe/Amsterdam" + } +} diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/databricks.yml b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/databricks.yml new file mode 100644 index 0000000000..70925a9ba7 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/databricks.yml @@ -0,0 +1,11 @@ +bundle: + name: alerts-not-allowed-field + +resources: + alerts: + myalert: + warehouse_id: "0123456789012345" + display_name: "My alert" + file_path: ./alert.dbalert.json + # This field is not allowed when file_path is specified + query_text: "select 3" diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/out.test.toml b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt new file mode 100644 index 0000000000..11346e56fa --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt @@ -0,0 +1,17 @@ + +>>> trace [CLI] bundle validate + +>>> [CLI] bundle validate +Error: field query_text is not allowed in the bundle YAML when a .dbalert.json is specified. Please set it in the .dbalert.json file instead. Only allowed fields are: warehouse_id, display_name, file_path + at resources.alerts.query_text.query_text + in databricks.yml:11:19 + +Name: alerts-not-allowed-field +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/alerts-not-allowed-field/default + +Found 1 error + +Exit code: 1 diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/script b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/script new file mode 100755 index 0000000000..b730f91eea --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/script @@ -0,0 +1 @@ +errcode trace trace $CLI bundle validate diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/test.toml b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/test.toml new file mode 100644 index 0000000000..5319b27bfc --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/test.toml @@ -0,0 +1,3 @@ +Local = true +RecordRequests = false +Ignore = [".databricks"] diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/alert.dbalert.json new file mode 100644 index 0000000000..b2b4c7ab59 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/alert.dbalert.json @@ -0,0 +1,26 @@ +{ + "custom_summary": "Alert for ${var.target}", + "evaluation": { + "comparison_operator": "EQUAL", + "notification": { + "notify_on_ok": false, + "retrigger_seconds": 1 + }, + "source": { + "aggregation": "MAX", + "display": "1", + "name": "1" + }, + "threshold": { + "value": { + "double_value": 2 + } + } + }, + "query_text": "select 2", + "schedule": { + "pause_status": "UNPAUSED", + "quartz_cron_schedule": "44 19 */1 * * ?", + "timezone_id": "Europe/Amsterdam" + } +} diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/databricks.yml b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/databricks.yml new file mode 100644 index 0000000000..ce12a6ace3 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/databricks.yml @@ -0,0 +1,9 @@ +bundle: + name: alerts-variable-error + +resources: + alerts: + myalert: + warehouse_id: "0123456789012345" + display_name: "My alert" + file_path: ./alert.dbalert.json diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/out.test.toml b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt new file mode 100644 index 0000000000..046cdf526c --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt @@ -0,0 +1,15 @@ + +>>> [CLI] bundle validate +Error: .dbalert.json file ./alert.dbalert.json contains variable interpolations, which are not supported. Please inline the alert configuration in the bundle YAML to use variables + at resources.alerts.myalert.file_path + in databricks.yml:9:18 + +Name: alerts-variable-error +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/alerts-variable-error/default + +Found 1 error + +Exit code: 1 diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/script b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/script new file mode 100755 index 0000000000..f52b452ee6 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/script @@ -0,0 +1 @@ +errcode trace $CLI bundle validate diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/test.toml b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/test.toml new file mode 100644 index 0000000000..5319b27bfc --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/test.toml @@ -0,0 +1,3 @@ +Local = true +RecordRequests = false +Ignore = [".databricks"] diff --git a/bundle/config/mutator/load_dbalert_files.go b/bundle/config/mutator/load_dbalert_files.go index 1c0794b0a6..fdd67929a3 100644 --- a/bundle/config/mutator/load_dbalert_files.go +++ b/bundle/config/mutator/load_dbalert_files.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/databricks-sdk-go/service/sql" ) @@ -38,7 +39,7 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia // We will only allow these fields to be set in the bundle YAML when an .dbalert.json is // specified. This is done to only have one way to set these fields when a .dbalert.json is // specified. - allowedInYAML := []string{"warehouse_id", "display_name"} + allowedInYAML := []string{"warehouse_id", "display_name", "file_path"} for k, alert := range b.Config.Resources.Alerts { if alert.FilePath == "" { @@ -103,10 +104,70 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia } } - // TODO: Parse that the file does not have any variable interpolations. + // Check that the file does not have any variable interpolations. + if dynvar.ContainsVariableReference(string(content)) { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: fmt.Sprintf(".dbalert.json file %s contains variable interpolations, which are not supported. Please inline the alert configuration in the bundle YAML to use variables", alert.FilePath), + Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", k))}, + Locations: alertV.Get("file_path").Locations(), + }, + } + } - // write values from the file to the alert. - b.Config.Resources.Alerts[k].AlertV2 = dbalertFromFile.AlertV2 + // Handle query_text: prefer query_lines if present, otherwise use query_text directly. + queryText := "" + if len(dbalertFromFile.QueryLines) > 0 { + for _, line := range dbalertFromFile.QueryLines { + queryText += line + "\n" + } + } else { + queryText = dbalertFromFile.QueryText + } + + // Handle custom_description: prefer custom_description_lines if present, otherwise use custom_description directly. + customDescription := "" + if len(dbalertFromFile.CustomDescriptionLines) > 0 { + for _, line := range dbalertFromFile.CustomDescriptionLines { + customDescription += line + "\n" + } + } else { + customDescription = dbalertFromFile.CustomDescription + } + newAlert := sql.AlertV2{ + // Fields with different schema in file vs API. + CustomDescription: customDescription, + QueryText: queryText, + + // API only fields. All these should be present in [allowedInYAML] + DisplayName: alert.DisplayName, + WarehouseId: alert.WarehouseId, + + // Fields with the same schema in file vs API. + CustomSummary: dbalertFromFile.CustomSummary, + Schedule: dbalertFromFile.Schedule, + Evaluation: dbalertFromFile.Evaluation, + EffectiveRunAs: dbalertFromFile.EffectiveRunAs, + RunAs: dbalertFromFile.RunAs, + RunAsUserName: dbalertFromFile.RunAsUserName, + ParentPath: dbalertFromFile.ParentPath, + + // Output only fields. + CreateTime: dbalertFromFile.CreateTime, + OwnerUserName: dbalertFromFile.OwnerUserName, + UpdateTime: dbalertFromFile.UpdateTime, + LifecycleState: dbalertFromFile.LifecycleState, + + // Other fields. + Id: dbalertFromFile.Id, + ForceSendFields: dbalertFromFile.ForceSendFields, + } + + // write values from the file to the alert. + b.Config.Resources.Alerts[k].AlertV2 = newAlert } + + return nil } diff --git a/bundle/config/mutator/load_dbalert_files_test.go b/bundle/config/mutator/load_dbalert_files_test.go index 719fbaa2a0..03d3a4a2fd 100644 --- a/bundle/config/mutator/load_dbalert_files_test.go +++ b/bundle/config/mutator/load_dbalert_files_test.go @@ -12,7 +12,7 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/internal/bundletest" "github.com/databricks/cli/libs/dyn" - "github.com/stretchr/testify/assert" + "github.com/databricks/databricks-sdk-go/service/sql" "github.com/stretchr/testify/require" ) @@ -20,9 +20,10 @@ func TestLoadDBAlertFiles(t *testing.T) { dir := t.TempDir() alertJSON := `{ - "display_name": "Test Alert", - "query_text": "SELECT * FROM table WHERE value > 100", - "warehouse_id": "abc123", + "query_lines": [ + "SELECT * FROM table", + "WHERE value > 100" + ], "schedule": { "quartz_cron_expression": "0 0 12 * * ?", "timezone_id": "UTC" @@ -55,6 +56,10 @@ func TestLoadDBAlertFiles(t *testing.T) { Alerts: map[string]*resources.Alert{ "my_alert": { FilePath: filepath.Join(dir, "alert.dbalert.json"), + AlertV2: sql.AlertV2{ + DisplayName: "Test Alert", + WarehouseId: "abc123", + }, }, }, }, @@ -65,66 +70,10 @@ func TestLoadDBAlertFiles(t *testing.T) { File: filepath.Join(dir, "databricks.yml"), }}) + // Note: This test only verifies that the mutator doesn't error when a file_path is set. + // The full functionality is tested in acceptance tests where the bundle is properly initialized. diags := bundle.Apply(context.Background(), b, mutator.LoadDBAlertFiles()) - require.NoError(t, diags.Error()) - - alert := b.Config.Resources.Alerts["my_alert"] - assert.Equal(t, "Test Alert", alert.DisplayName) - assert.Equal(t, "SELECT * FROM table WHERE value > 100", alert.QueryText) - assert.Equal(t, "abc123", alert.WarehouseId) -} - -func TestLoadDBAlertFilesWithVariableInterpolation(t *testing.T) { - dir := t.TempDir() - - alertJSON := `{ - "display_name": "Test Alert ${var.environment}", - "query_text": "SELECT * FROM table WHERE value > 100", - "warehouse_id": "abc123", - "schedule": { - "quartz_cron_expression": "0 0 12 * * ?", - "timezone_id": "UTC" - }, - "evaluation": { - "execution_condition": "ALL_ROWS_MATCH", - "condition": { - "op": "GREATER_THAN", - "operand": { - "column": { - "name": "value" - } - }, - "threshold": { - "value": { - "double_value": 100.0 - } - } - } - } -}` - err := os.WriteFile(filepath.Join(dir, "alert.dbalert.json"), []byte(alertJSON), 0o644) - require.NoError(t, err) - - b := &bundle.Bundle{ - BundleRootPath: dir, - Config: config.Root{ - Resources: config.Resources{ - Alerts: map[string]*resources.Alert{ - "my_alert": { - FilePath: filepath.Join(dir, "alert.dbalert.json"), - }, - }, - }, - }, - } - - bundletest.SetLocation(b, "resources.alerts.my_alert", []dyn.Location{{ - File: filepath.Join(dir, "databricks.yml"), - }}) - - diags := bundle.Apply(context.Background(), b, mutator.LoadDBAlertFiles()) - require.Error(t, diags.Error()) - assert.Contains(t, diags.Error().Error(), "contains bundle variable interpolations") - assert.Contains(t, diags.Error().Error(), "${var.environment}") + // The mutator should not error out (it will skip processing if Config.Value() is not initialized) + require.NoError(t, diags.Error()) } diff --git a/bundle/config/mutator/resourcemutator/resource_mutator.go b/bundle/config/mutator/resourcemutator/resource_mutator.go index d716de41be..303eb2079a 100644 --- a/bundle/config/mutator/resourcemutator/resource_mutator.go +++ b/bundle/config/mutator/resourcemutator/resource_mutator.go @@ -176,6 +176,10 @@ func applyNormalizeMutators(ctx context.Context, b *bundle.Bundle) { // Drops (dynamic): resources.dashboards.*.file_path ConfigureDashboardSerializedDashboard(), + // Reads (typed): resources.alerts.*.file_path + // Updates (typed): resources.alerts.* (loads alert configuration from .dbalert.json file) + mutator.LoadDBAlertFiles(), + // Reads and updates (typed): resources.jobs.*.** JobClustersFixups(), ClusterFixups(), From fdc778c396fb312712e7195718e59a0582ca297e Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 8 Dec 2025 03:34:12 +0100 Subject: [PATCH 03/15] fixups --- .../alerts/with_file/alert.dbalert.json | 9 ++++- .../resources/alerts/with_file/output.txt | 39 ++++++++++++++++--- .../bundle/resources/alerts/with_file/script | 5 +-- .../alert.dbalert.json | 9 ++++- .../alert.dbalert.json | 9 ++++- .../output.txt | 2 +- bundle/config/mutator/load_dbalert_files.go | 15 +++++-- .../mutator/paths/alert_paths_visitor.go | 18 +++++++++ bundle/config/mutator/paths/visitor.go | 1 + 9 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 bundle/config/mutator/paths/alert_paths_visitor.go diff --git a/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json index 9d88eab236..3af480b9df 100644 --- a/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json +++ b/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json @@ -1,5 +1,10 @@ { "custom_summary": "My alert from file", + "custom_description_lines": [ + "My alert from file", + "with multiple lines", + "and a third line" + ], "evaluation": { "comparison_operator": "EQUAL", "notification": { @@ -17,7 +22,9 @@ } } }, - "query_text": "select 2", + "query_lines": [ + "select 2" + ], "schedule": { "pause_status": "UNPAUSED", "quartz_cron_schedule": "44 19 */1 * * ?", diff --git a/acceptance/bundle/resources/alerts/with_file/output.txt b/acceptance/bundle/resources/alerts/with_file/output.txt index ffcc45609d..1193058754 100644 --- a/acceptance/bundle/resources/alerts/with_file/output.txt +++ b/acceptance/bundle/resources/alerts/with_file/output.txt @@ -1,4 +1,37 @@ +>>> [CLI] bundle validate -o json +{ + "custom_description": "My alert from file\nwith multiple lines\nand a third line\n", + "custom_summary": "My alert from file", + "display_name": "My alert with file", + "evaluation": { + "comparison_operator": "EQUAL", + "notification": { + "notify_on_ok": false, + "retrigger_seconds": 1 + }, + "source": { + "aggregation": "MAX", + "display": "1", + "name": "1" + }, + "threshold": { + "value": { + "double_value": 2 + } + } + }, + "file_path": "alert.dbalert.json", + "parent_path": "/Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default/resources", + "query_text": "select 2\n", + "schedule": { + "pause_status": "UNPAUSED", + "quartz_cron_schedule": "44 19 */1 * * ?", + "timezone_id": "Europe/Amsterdam" + }, + "warehouse_id": "[TEST_DEFAULT_WAREHOUSE_ID]" +} + >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default/files... Deploying resources... @@ -27,7 +60,7 @@ Deployment complete! } } }, - "query_text": "select 2", + "query_text": "select 2\n", "schedule": { "pause_status": "UNPAUSED", "quartz_cron_schedule": "44 19 */1 * * ?", @@ -36,10 +69,6 @@ Deployment complete! "warehouse_id": "[TEST_DEFAULT_WAREHOUSE_ID]" } -=== assert that no permanent drift happens ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged - >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: delete resources.alerts.myalert diff --git a/acceptance/bundle/resources/alerts/with_file/script b/acceptance/bundle/resources/alerts/with_file/script index 21e11b09af..0655b296a2 100755 --- a/acceptance/bundle/resources/alerts/with_file/script +++ b/acceptance/bundle/resources/alerts/with_file/script @@ -1,5 +1,7 @@ envsubst < databricks.yml.tmpl > databricks.yml +trace $CLI bundle validate -o json | jq .resources.alerts.myalert + trace $CLI bundle deploy alert_id=$($CLI bundle summary --output json | jq -r '.resources.alerts.myalert.id') @@ -8,9 +10,6 @@ echo "$alert_id:ALERT_ID" >> ACC_REPLS trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state, custom_summary, evaluation, query_text, schedule, warehouse_id}' -title "assert that no permanent drift happens" -trace $CLI bundle plan - trace $CLI bundle destroy --auto-approve trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state}' diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/alert.dbalert.json index 9d88eab236..ca76920521 100644 --- a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/alert.dbalert.json +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/alert.dbalert.json @@ -17,7 +17,14 @@ } } }, - "query_text": "select 2", + "query_lines": [ + "select 2" + ], + "custom_description_lines": [ + "My alert from file", + "with multiple lines", + "and a third line" + ], "schedule": { "pause_status": "UNPAUSED", "quartz_cron_schedule": "44 19 */1 * * ?", diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/alert.dbalert.json index b2b4c7ab59..5cdcedd778 100644 --- a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/alert.dbalert.json +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/alert.dbalert.json @@ -17,7 +17,14 @@ } } }, - "query_text": "select 2", + "query_lines": [ + "select 2" + ], + "custom_description_lines": [ + "Alert for ${var.target}", + "with multiple lines", + "and a third line" + ], "schedule": { "pause_status": "UNPAUSED", "quartz_cron_schedule": "44 19 */1 * * ?", diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt index 046cdf526c..c31efdbfd5 100644 --- a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt @@ -1,6 +1,6 @@ >>> [CLI] bundle validate -Error: .dbalert.json file ./alert.dbalert.json contains variable interpolations, which are not supported. Please inline the alert configuration in the bundle YAML to use variables +Error: .dbalert.json file alert.dbalert.json contains variable interpolations, which are not supported. Please inline the alert configuration in the bundle YAML to use variables at resources.alerts.myalert.file_path in databricks.yml:9:18 diff --git a/bundle/config/mutator/load_dbalert_files.go b/bundle/config/mutator/load_dbalert_files.go index fdd67929a3..0853a005a3 100644 --- a/bundle/config/mutator/load_dbalert_files.go +++ b/bundle/config/mutator/load_dbalert_files.go @@ -12,6 +12,7 @@ import ( "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/sql" ) @@ -25,15 +26,23 @@ func (m *loadDBAlertFiles) Name() string { return "LoadDBAlertFiles" } -type dbalertFile struct { +type DbalertFile struct { sql.AlertV2 - // query_text and custom_description are split into lines to make it easier to view the diff + // query_text and custom_description can be split into lines to make it easier to view the diff // in a Git editor. QueryLines []string `json:"query_lines,omitempty"` CustomDescriptionLines []string `json:"custom_description_lines,omitempty"` } +func (d *DbalertFile) UnmarshalJSON(data []byte) error { + return marshal.Unmarshal(data, d) +} + +func (d DbalertFile) MarshalJSON() ([]byte, error) { + return marshal.Marshal(d) +} + func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { // Fields that are only settable in the API, and are not allowed in .dbalert.json. // We will only allow these fields to be set in the bundle YAML when an .dbalert.json is @@ -91,7 +100,7 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia } } - var dbalertFromFile dbalertFile + var dbalertFromFile DbalertFile err = json.Unmarshal(content, &dbalertFromFile) if err != nil { return diag.Diagnostics{ diff --git a/bundle/config/mutator/paths/alert_paths_visitor.go b/bundle/config/mutator/paths/alert_paths_visitor.go new file mode 100644 index 0000000000..1f1a010c79 --- /dev/null +++ b/bundle/config/mutator/paths/alert_paths_visitor.go @@ -0,0 +1,18 @@ +package paths + +import ( + "github.com/databricks/cli/libs/dyn" +) + +func VisitAlertPaths(value dyn.Value, fn VisitFunc) (dyn.Value, error) { + pattern := dyn.NewPattern( + dyn.Key("resources"), + dyn.Key("alerts"), + dyn.AnyKey(), + dyn.Key("file_path"), + ) + + return dyn.MapByPattern(value, pattern, func(path dyn.Path, value dyn.Value) (dyn.Value, error) { + return fn(path, TranslateModeLocalRelative, value) + }) +} diff --git a/bundle/config/mutator/paths/visitor.go b/bundle/config/mutator/paths/visitor.go index 3244e415fa..0e3d59d43f 100644 --- a/bundle/config/mutator/paths/visitor.go +++ b/bundle/config/mutator/paths/visitor.go @@ -13,6 +13,7 @@ func VisitPaths(root dyn.Value, fn VisitFunc) (dyn.Value, error) { VisitJobLibrariesPaths, VisitAppPaths, VisitArtifactPaths, + VisitAlertPaths, VisitDashboardPaths, VisitPipelinePaths, VisitPipelineLibrariesPaths, From 387500274f858928501517770b7839a0fbe72ab6 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 8 Dec 2025 03:37:04 +0100 Subject: [PATCH 04/15] only read the lines field --- bundle/config/mutator/load_dbalert_files.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bundle/config/mutator/load_dbalert_files.go b/bundle/config/mutator/load_dbalert_files.go index 0853a005a3..f7fe42eb7f 100644 --- a/bundle/config/mutator/load_dbalert_files.go +++ b/bundle/config/mutator/load_dbalert_files.go @@ -125,24 +125,18 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia } } - // Handle query_text: prefer query_lines if present, otherwise use query_text directly. queryText := "" if len(dbalertFromFile.QueryLines) > 0 { for _, line := range dbalertFromFile.QueryLines { queryText += line + "\n" } - } else { - queryText = dbalertFromFile.QueryText } - // Handle custom_description: prefer custom_description_lines if present, otherwise use custom_description directly. customDescription := "" if len(dbalertFromFile.CustomDescriptionLines) > 0 { for _, line := range dbalertFromFile.CustomDescriptionLines { customDescription += line + "\n" } - } else { - customDescription = dbalertFromFile.CustomDescription } newAlert := sql.AlertV2{ From 8d7d38fe7794a560bbedcde60aab20b985f78c70 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 8 Dec 2025 03:38:52 +0100 Subject: [PATCH 05/15] fix unit test --- bundle/config/mutator/load_dbalert_files_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bundle/config/mutator/load_dbalert_files_test.go b/bundle/config/mutator/load_dbalert_files_test.go index 03d3a4a2fd..9e2c03ee91 100644 --- a/bundle/config/mutator/load_dbalert_files_test.go +++ b/bundle/config/mutator/load_dbalert_files_test.go @@ -13,6 +13,7 @@ import ( "github.com/databricks/cli/bundle/internal/bundletest" "github.com/databricks/cli/libs/dyn" "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -73,7 +74,9 @@ func TestLoadDBAlertFiles(t *testing.T) { // Note: This test only verifies that the mutator doesn't error when a file_path is set. // The full functionality is tested in acceptance tests where the bundle is properly initialized. diags := bundle.Apply(context.Background(), b, mutator.LoadDBAlertFiles()) - - // The mutator should not error out (it will skip processing if Config.Value() is not initialized) require.NoError(t, diags.Error()) + + assert.Equal(t, "Test Alert", b.Config.Resources.Alerts["my_alert"].DisplayName) + assert.Equal(t, "abc123", b.Config.Resources.Alerts["my_alert"].WarehouseId) + assert.Equal(t, "SELECT * FROM table\nWHERE value > 100\n", b.Config.Resources.Alerts["my_alert"].QueryText) } From b74d8b11f087a2b7f8f7555699728fdde8d2c298 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 8 Dec 2025 03:42:44 +0100 Subject: [PATCH 06/15] fix --- bundle/internal/schema/annotations.yml | 3 +++ bundle/schema/jsonschema.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/bundle/internal/schema/annotations.yml b/bundle/internal/schema/annotations.yml index 29b2fd03e6..cc1a7e41c0 100644 --- a/bundle/internal/schema/annotations.yml +++ b/bundle/internal/schema/annotations.yml @@ -461,6 +461,9 @@ github.com/databricks/cli/bundle/config/resources.Alert: "effective_run_as": "description": |- PLACEHOLDER + "file_path": + "description": |- + PLACEHOLDER "id": "description": |- PLACEHOLDER diff --git a/bundle/schema/jsonschema.json b/bundle/schema/jsonschema.json index 023f60407a..47784a257e 100644 --- a/bundle/schema/jsonschema.json +++ b/bundle/schema/jsonschema.json @@ -76,6 +76,9 @@ "evaluation": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/sql.AlertV2Evaluation" }, + "file_path": { + "$ref": "#/$defs/string" + }, "lifecycle": { "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle" }, From 0c9160c4fc6782baa400f3f197fa0d6a94117a32 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 10 Dec 2025 14:12:21 +0100 Subject: [PATCH 07/15] address feedback --- .../output.txt | 2 +- .../output.txt | 4 ++- bundle/config/mutator/load_dbalert_files.go | 31 ++++++++++--------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt index 11346e56fa..23d0f6caa0 100644 --- a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt @@ -3,7 +3,7 @@ >>> [CLI] bundle validate Error: field query_text is not allowed in the bundle YAML when a .dbalert.json is specified. Please set it in the .dbalert.json file instead. Only allowed fields are: warehouse_id, display_name, file_path - at resources.alerts.query_text.query_text + at resources.alerts.myalert.query_text in databricks.yml:11:19 Name: alerts-not-allowed-field diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt index c31efdbfd5..8b63705221 100644 --- a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/output.txt @@ -1,9 +1,11 @@ >>> [CLI] bundle validate -Error: .dbalert.json file alert.dbalert.json contains variable interpolations, which are not supported. Please inline the alert configuration in the bundle YAML to use variables +Error: .alert file alert.dbalert.json must not contain variable interpolations. at resources.alerts.myalert.file_path in databricks.yml:9:18 +Please inline the alert configuration in the bundle configuration to use variables + Name: alerts-variable-error Target: default Workspace: diff --git a/bundle/config/mutator/load_dbalert_files.go b/bundle/config/mutator/load_dbalert_files.go index f7fe42eb7f..2cab3fcd34 100644 --- a/bundle/config/mutator/load_dbalert_files.go +++ b/bundle/config/mutator/load_dbalert_files.go @@ -26,7 +26,7 @@ func (m *loadDBAlertFiles) Name() string { return "LoadDBAlertFiles" } -type DbalertFile struct { +type AlertFile struct { sql.AlertV2 // query_text and custom_description can be split into lines to make it easier to view the diff @@ -35,11 +35,11 @@ type DbalertFile struct { CustomDescriptionLines []string `json:"custom_description_lines,omitempty"` } -func (d *DbalertFile) UnmarshalJSON(data []byte) error { +func (d *AlertFile) UnmarshalJSON(data []byte) error { return marshal.Unmarshal(data, d) } -func (d DbalertFile) MarshalJSON() ([]byte, error) { +func (d AlertFile) MarshalJSON() ([]byte, error) { return marshal.Marshal(d) } @@ -48,14 +48,14 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia // We will only allow these fields to be set in the bundle YAML when an .dbalert.json is // specified. This is done to only have one way to set these fields when a .dbalert.json is // specified. - allowedInYAML := []string{"warehouse_id", "display_name", "file_path"} + allowedInYAML := []string{"warehouse_id", "display_name", "file_path", "permissions", "lifecycle"} - for k, alert := range b.Config.Resources.Alerts { + for alertKey, alert := range b.Config.Resources.Alerts { if alert.FilePath == "" { continue } - alertV, err := dyn.GetByPath(b.Config.Value(), dyn.NewPath(dyn.Key("resources"), dyn.Key("alerts"), dyn.Key(k))) + alertV, err := dyn.GetByPath(b.Config.Value(), dyn.NewPath(dyn.Key("resources"), dyn.Key("alerts"), dyn.Key(alertKey))) if err != nil { return diag.FromErr(err) } @@ -82,7 +82,7 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia { Severity: diag.Error, Summary: fmt.Sprintf("field %s is not allowed in the bundle YAML when a .dbalert.json is specified. Please set it in the .dbalert.json file instead. Only allowed fields are: %s", k, strings.Join(allowedInYAML, ", ")), - Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.%s", k, k))}, + Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.%s", alertKey, k))}, Locations: v.Locations(), }, } @@ -94,20 +94,20 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia { Severity: diag.Error, Summary: fmt.Sprintf("failed to read .dbalert.json file %s: %s", alert.FilePath, err), - Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", k))}, + Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", alertKey))}, Locations: alertV.Get("file_path").Locations(), }, } } - var dbalertFromFile DbalertFile + var dbalertFromFile AlertFile err = json.Unmarshal(content, &dbalertFromFile) if err != nil { return diag.Diagnostics{ { Severity: diag.Error, Summary: fmt.Sprintf("failed to parse .dbalert.json file %s: %s", alert.FilePath, err), - Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", k))}, + Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", alertKey))}, Locations: alertV.Get("file_path").Locations(), }, } @@ -118,8 +118,9 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia return diag.Diagnostics{ { Severity: diag.Error, - Summary: fmt.Sprintf(".dbalert.json file %s contains variable interpolations, which are not supported. Please inline the alert configuration in the bundle YAML to use variables", alert.FilePath), - Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", k))}, + Summary: fmt.Sprintf(".alert file %s must not contain variable interpolations.", alert.FilePath), + Detail: "Please inline the alert configuration in the bundle configuration to use variables", + Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", alertKey))}, Locations: alertV.Get("file_path").Locations(), }, } @@ -158,18 +159,18 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia ParentPath: dbalertFromFile.ParentPath, // Output only fields. - CreateTime: dbalertFromFile.CreateTime, + // CreateTime: dbalertFromFile.CreateTime, OwnerUserName: dbalertFromFile.OwnerUserName, UpdateTime: dbalertFromFile.UpdateTime, LifecycleState: dbalertFromFile.LifecycleState, // Other fields. - Id: dbalertFromFile.Id, + // Id: dbalertFromFile.Id, ForceSendFields: dbalertFromFile.ForceSendFields, } // write values from the file to the alert. - b.Config.Resources.Alerts[k].AlertV2 = newAlert + b.Config.Resources.Alerts[alertKey].AlertV2 = newAlert } return nil From 0f4ffe16acc284edba66cf3bb470979bb5de5c89 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 10 Dec 2025 14:28:09 +0100 Subject: [PATCH 08/15] gpoh --- .golangci.yaml | 2 +- .../output.txt | 4 ++- bundle/config/mutator/load_dbalert_files.go | 27 ++++++++++++++----- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 0df22aa411..1ff2fba52d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -90,7 +90,7 @@ linters: - legacy - std-error-handling rules: - - path-except: bundle/direct/dresources + - path-except: bundle/direct/dresources|bundle/config/mutator/load_dbalert_files.go linters: - exhaustruct - path: bundle/direct/dresources/all_test.go diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt index 23d0f6caa0..2dbe18f5d4 100644 --- a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/output.txt @@ -2,10 +2,12 @@ >>> trace [CLI] bundle validate >>> [CLI] bundle validate -Error: field query_text is not allowed in the bundle YAML when a .dbalert.json is specified. Please set it in the .dbalert.json file instead. Only allowed fields are: warehouse_id, display_name, file_path +Error: field query_text is not allowed in the bundle configuration. at resources.alerts.myalert.query_text in databricks.yml:11:19 +When a .dbalert.json is specified, only the following fields are allowed in the bundle configuration: warehouse_id, display_name, file_path, permissions, lifecycle + Name: alerts-not-allowed-field Target: default Workspace: diff --git a/bundle/config/mutator/load_dbalert_files.go b/bundle/config/mutator/load_dbalert_files.go index 2cab3fcd34..992ce9962d 100644 --- a/bundle/config/mutator/load_dbalert_files.go +++ b/bundle/config/mutator/load_dbalert_files.go @@ -80,8 +80,10 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia return diag.Diagnostics{ { + ID: "", Severity: diag.Error, - Summary: fmt.Sprintf("field %s is not allowed in the bundle YAML when a .dbalert.json is specified. Please set it in the .dbalert.json file instead. Only allowed fields are: %s", k, strings.Join(allowedInYAML, ", ")), + Summary: fmt.Sprintf("field %s is not allowed in the bundle configuration.", k), + Detail: "When a .dbalert.json is specified, only the following fields are allowed in the bundle configuration: " + strings.Join(allowedInYAML, ", "), Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.%s", alertKey, k))}, Locations: v.Locations(), }, @@ -92,8 +94,10 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia if err != nil { return diag.Diagnostics{ { + ID: diag.ID(""), Severity: diag.Error, Summary: fmt.Sprintf("failed to read .dbalert.json file %s: %s", alert.FilePath, err), + Detail: "", Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", alertKey))}, Locations: alertV.Get("file_path").Locations(), }, @@ -105,8 +109,10 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia if err != nil { return diag.Diagnostics{ { + ID: diag.ID(""), Severity: diag.Error, Summary: fmt.Sprintf("failed to parse .dbalert.json file %s: %s", alert.FilePath, err), + Detail: "", Paths: []dyn.Path{dyn.MustPathFromString(fmt.Sprintf("resources.alerts.%s.file_path", alertKey))}, Locations: alertV.Get("file_path").Locations(), }, @@ -117,6 +123,7 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia if dynvar.ContainsVariableReference(string(content)) { return diag.Diagnostics{ { + ID: diag.ID(""), Severity: diag.Error, Summary: fmt.Sprintf(".alert file %s must not contain variable interpolations.", alert.FilePath), Detail: "Please inline the alert configuration in the bundle configuration to use variables", @@ -126,18 +133,24 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia } } - queryText := "" + var queryText string if len(dbalertFromFile.QueryLines) > 0 { + var sb strings.Builder for _, line := range dbalertFromFile.QueryLines { - queryText += line + "\n" + sb.WriteString(line) + sb.WriteString("\n") } + queryText = sb.String() } - customDescription := "" + var customDescription string if len(dbalertFromFile.CustomDescriptionLines) > 0 { + var sb strings.Builder for _, line := range dbalertFromFile.CustomDescriptionLines { - customDescription += line + "\n" + sb.WriteString(line) + sb.WriteString("\n") } + customDescription = sb.String() } newAlert := sql.AlertV2{ @@ -159,13 +172,13 @@ func (m *loadDBAlertFiles) Apply(ctx context.Context, b *bundle.Bundle) diag.Dia ParentPath: dbalertFromFile.ParentPath, // Output only fields. - // CreateTime: dbalertFromFile.CreateTime, + CreateTime: dbalertFromFile.CreateTime, OwnerUserName: dbalertFromFile.OwnerUserName, UpdateTime: dbalertFromFile.UpdateTime, LifecycleState: dbalertFromFile.LifecycleState, // Other fields. - // Id: dbalertFromFile.Id, + Id: dbalertFromFile.Id, ForceSendFields: dbalertFromFile.ForceSendFields, } From 02cba258eaf79d05abe2d823087ab0230d6c3712 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 10 Dec 2025 16:06:39 +0100 Subject: [PATCH 09/15] add export roundback --- .../alerts/with_file/alert.dbalert.json | 35 +++++++++---------- .../alerts/with_file/databricks.yml.tmpl | 2 +- .../alerts/with_file/out.alert.dbalert.json | 32 +++++++++++++++++ .../resources/alerts/with_file/output.txt | 33 +++++++++-------- .../bundle/resources/alerts/with_file/script | 11 +++++- 5 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 acceptance/bundle/resources/alerts/with_file/out.alert.dbalert.json diff --git a/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json index 3af480b9df..d9690fc3c6 100644 --- a/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json +++ b/acceptance/bundle/resources/alerts/with_file/alert.dbalert.json @@ -1,33 +1,32 @@ { "custom_summary": "My alert from file", - "custom_description_lines": [ - "My alert from file", - "with multiple lines", - "and a third line" - ], "evaluation": { - "comparison_operator": "EQUAL", - "notification": { - "notify_on_ok": false, - "retrigger_seconds": 1 - }, "source": { - "aggregation": "MAX", + "name": "1", "display": "1", - "name": "1" + "aggregation": "MAX" }, + "comparison_operator": "EQUAL", "threshold": { "value": { - "double_value": 2 + "double_value": 2.0 } + }, + "notification": { + "retrigger_seconds": 1, + "notify_on_ok": false } }, - "query_lines": [ - "select 2" - ], "schedule": { - "pause_status": "UNPAUSED", "quartz_cron_schedule": "44 19 */1 * * ?", "timezone_id": "Europe/Amsterdam" - } + }, + "query_lines": [ + "select 2" + ], + "custom_description_lines": [ + "My alert from file", + "with multiple lines", + "and a third line" + ] } diff --git a/acceptance/bundle/resources/alerts/with_file/databricks.yml.tmpl b/acceptance/bundle/resources/alerts/with_file/databricks.yml.tmpl index db35cf5a4e..55ff1d1c6d 100644 --- a/acceptance/bundle/resources/alerts/with_file/databricks.yml.tmpl +++ b/acceptance/bundle/resources/alerts/with_file/databricks.yml.tmpl @@ -5,5 +5,5 @@ resources: alerts: myalert: warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID - display_name: "My alert with file" + display_name: "myalert" file_path: ./alert.dbalert.json diff --git a/acceptance/bundle/resources/alerts/with_file/out.alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file/out.alert.dbalert.json new file mode 100644 index 0000000000..d9690fc3c6 --- /dev/null +++ b/acceptance/bundle/resources/alerts/with_file/out.alert.dbalert.json @@ -0,0 +1,32 @@ +{ + "custom_summary": "My alert from file", + "evaluation": { + "source": { + "name": "1", + "display": "1", + "aggregation": "MAX" + }, + "comparison_operator": "EQUAL", + "threshold": { + "value": { + "double_value": 2.0 + } + }, + "notification": { + "retrigger_seconds": 1, + "notify_on_ok": false + } + }, + "schedule": { + "quartz_cron_schedule": "44 19 */1 * * ?", + "timezone_id": "Europe/Amsterdam" + }, + "query_lines": [ + "select 2" + ], + "custom_description_lines": [ + "My alert from file", + "with multiple lines", + "and a third line" + ] +} diff --git a/acceptance/bundle/resources/alerts/with_file/output.txt b/acceptance/bundle/resources/alerts/with_file/output.txt index 1193058754..6b4dae89d5 100644 --- a/acceptance/bundle/resources/alerts/with_file/output.txt +++ b/acceptance/bundle/resources/alerts/with_file/output.txt @@ -3,7 +3,7 @@ { "custom_description": "My alert from file\nwith multiple lines\nand a third line\n", "custom_summary": "My alert from file", - "display_name": "My alert with file", + "display_name": "myalert", "evaluation": { "comparison_operator": "EQUAL", "notification": { @@ -25,7 +25,6 @@ "parent_path": "/Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default/resources", "query_text": "select 2\n", "schedule": { - "pause_status": "UNPAUSED", "quartz_cron_schedule": "44 19 */1 * * ?", "timezone_id": "Europe/Amsterdam" }, @@ -40,7 +39,7 @@ Deployment complete! >>> [CLI] alerts-v2 get-alert [ALERT_ID] { - "display_name": "My alert with file", + "display_name": "myalert", "lifecycle_state": "ACTIVE", "custom_summary": "My alert from file", "evaluation": { @@ -62,26 +61,21 @@ Deployment complete! }, "query_text": "select 2\n", "schedule": { - "pause_status": "UNPAUSED", "quartz_cron_schedule": "44 19 */1 * * ?", "timezone_id": "Europe/Amsterdam" }, "warehouse_id": "[TEST_DEFAULT_WAREHOUSE_ID]" } ->>> [CLI] bundle destroy --auto-approve -The following resources will be deleted: - delete resources.alerts.myalert - -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default +=== assert that the uploaded alert file is the same as the local one +>>> [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default/resources/myalert.dbalert.json -Deleting files... -Destroy complete! +>>> diff out.alert.dbalert.json alert.dbalert.json >>> [CLI] alerts-v2 get-alert [ALERT_ID] { - "display_name": "My alert with file", - "lifecycle_state": "DELETED" + "display_name": "myalert", + "lifecycle_state": "ACTIVE" } >>> [CLI] bundle summary @@ -93,5 +87,14 @@ Workspace: Resources: Alerts: myalert: - Name: My alert with file - URL: (not deployed) + Name: myalert + URL: [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID] + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.alerts.myalert + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/alerts/with_file/script b/acceptance/bundle/resources/alerts/with_file/script index 0655b296a2..78cb271f61 100755 --- a/acceptance/bundle/resources/alerts/with_file/script +++ b/acceptance/bundle/resources/alerts/with_file/script @@ -1,5 +1,11 @@ envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve +} +trap cleanup EXIT + trace $CLI bundle validate -o json | jq .resources.alerts.myalert trace $CLI bundle deploy @@ -10,7 +16,10 @@ echo "$alert_id:ALERT_ID" >> ACC_REPLS trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state, custom_summary, evaluation, query_text, schedule, warehouse_id}' -trace $CLI bundle destroy --auto-approve +alert_path=$($CLI bundle summary --output json | jq -r '.resources.alerts.myalert.parent_path')/myalert.dbalert.json +title "assert that the uploaded alert file is the same as the local one" +trace $CLI workspace export $alert_path > out.alert.dbalert.json +trace diff out.alert.dbalert.json alert.dbalert.json trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state}' From ee1ee53c28933d7b2f9073f3ae38d3125a174112 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 10 Dec 2025 16:08:41 +0100 Subject: [PATCH 10/15] update test --- .../alerts/with_file/out.alert.dbalert.json | 32 ------------------- .../resources/alerts/with_file/output.txt | 2 +- .../bundle/resources/alerts/with_file/script | 5 +-- 3 files changed, 4 insertions(+), 35 deletions(-) delete mode 100644 acceptance/bundle/resources/alerts/with_file/out.alert.dbalert.json diff --git a/acceptance/bundle/resources/alerts/with_file/out.alert.dbalert.json b/acceptance/bundle/resources/alerts/with_file/out.alert.dbalert.json deleted file mode 100644 index d9690fc3c6..0000000000 --- a/acceptance/bundle/resources/alerts/with_file/out.alert.dbalert.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "custom_summary": "My alert from file", - "evaluation": { - "source": { - "name": "1", - "display": "1", - "aggregation": "MAX" - }, - "comparison_operator": "EQUAL", - "threshold": { - "value": { - "double_value": 2.0 - } - }, - "notification": { - "retrigger_seconds": 1, - "notify_on_ok": false - } - }, - "schedule": { - "quartz_cron_schedule": "44 19 */1 * * ?", - "timezone_id": "Europe/Amsterdam" - }, - "query_lines": [ - "select 2" - ], - "custom_description_lines": [ - "My alert from file", - "with multiple lines", - "and a third line" - ] -} diff --git a/acceptance/bundle/resources/alerts/with_file/output.txt b/acceptance/bundle/resources/alerts/with_file/output.txt index 6b4dae89d5..da9c85f4e4 100644 --- a/acceptance/bundle/resources/alerts/with_file/output.txt +++ b/acceptance/bundle/resources/alerts/with_file/output.txt @@ -70,7 +70,7 @@ Deployment complete! === assert that the uploaded alert file is the same as the local one >>> [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default/resources/myalert.dbalert.json ->>> diff out.alert.dbalert.json alert.dbalert.json +>>> diff exported_alert.dbalert.json alert.dbalert.json >>> [CLI] alerts-v2 get-alert [ALERT_ID] { diff --git a/acceptance/bundle/resources/alerts/with_file/script b/acceptance/bundle/resources/alerts/with_file/script index 78cb271f61..34af9ef0bc 100755 --- a/acceptance/bundle/resources/alerts/with_file/script +++ b/acceptance/bundle/resources/alerts/with_file/script @@ -18,8 +18,9 @@ trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state, c alert_path=$($CLI bundle summary --output json | jq -r '.resources.alerts.myalert.parent_path')/myalert.dbalert.json title "assert that the uploaded alert file is the same as the local one" -trace $CLI workspace export $alert_path > out.alert.dbalert.json -trace diff out.alert.dbalert.json alert.dbalert.json +trace $CLI workspace export $alert_path > exported_alert.dbalert.json +trace diff exported_alert.dbalert.json alert.dbalert.json +rm exported_alert.dbalert.json trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state}' From d1e03f8e98b14a4ec265a720bde6ffad6f147b29 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Wed, 10 Dec 2025 16:13:03 +0100 Subject: [PATCH 11/15] disable test on local --- acceptance/bundle/resources/alerts/with_file/test.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/bundle/resources/alerts/with_file/test.toml b/acceptance/bundle/resources/alerts/with_file/test.toml index e6228608dd..a0f2ab9366 100644 --- a/acceptance/bundle/resources/alerts/with_file/test.toml +++ b/acceptance/bundle/resources/alerts/with_file/test.toml @@ -1,4 +1,4 @@ -Local = true +Local = false Cloud = true RecordRequests = false Ignore = [".databricks"] From 15faa0f9951313cdcd46a8ad82f9ee2be6acf89d Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 15 Dec 2025 15:39:37 +0100 Subject: [PATCH 12/15] fix the with_file test --- acceptance/bundle/resources/alerts/with_file/out.test.toml | 2 +- acceptance/bundle/resources/alerts/with_file/output.txt | 2 +- acceptance/bundle/resources/alerts/with_file/test.toml | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/acceptance/bundle/resources/alerts/with_file/out.test.toml b/acceptance/bundle/resources/alerts/with_file/out.test.toml index 01ed6822af..f474b1b917 100644 --- a/acceptance/bundle/resources/alerts/with_file/out.test.toml +++ b/acceptance/bundle/resources/alerts/with_file/out.test.toml @@ -1,4 +1,4 @@ -Local = true +Local = false Cloud = true [EnvMatrix] diff --git a/acceptance/bundle/resources/alerts/with_file/output.txt b/acceptance/bundle/resources/alerts/with_file/output.txt index da9c85f4e4..1431b27d46 100644 --- a/acceptance/bundle/resources/alerts/with_file/output.txt +++ b/acceptance/bundle/resources/alerts/with_file/output.txt @@ -88,7 +88,7 @@ Resources: Alerts: myalert: Name: myalert - URL: [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID] + URL: [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID][NUMID] >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: diff --git a/acceptance/bundle/resources/alerts/with_file/test.toml b/acceptance/bundle/resources/alerts/with_file/test.toml index a0f2ab9366..63750d00f8 100644 --- a/acceptance/bundle/resources/alerts/with_file/test.toml +++ b/acceptance/bundle/resources/alerts/with_file/test.toml @@ -3,5 +3,11 @@ Cloud = true RecordRequests = false Ignore = [".databricks"] +# redact ?o=[NUMID]. Different clouds can have different URL serialization, like [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID] vs [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID]?o=[NUMID]. +[[Repls]] +Old = '\?o\=[0-9]*' +New = '' +Order = 1000 + [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] From 5a7a3524e3388d8f0002a77fbb1216d0609ded6a Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 5 Jan 2026 17:39:32 +0100 Subject: [PATCH 13/15] fix the alerts test --- acceptance/bundle/resources/alerts/with_file/output.txt | 2 +- acceptance/bundle/resources/alerts/with_file/test.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance/bundle/resources/alerts/with_file/output.txt b/acceptance/bundle/resources/alerts/with_file/output.txt index 1431b27d46..da9c85f4e4 100644 --- a/acceptance/bundle/resources/alerts/with_file/output.txt +++ b/acceptance/bundle/resources/alerts/with_file/output.txt @@ -88,7 +88,7 @@ Resources: Alerts: myalert: Name: myalert - URL: [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID][NUMID] + URL: [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID] >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: diff --git a/acceptance/bundle/resources/alerts/with_file/test.toml b/acceptance/bundle/resources/alerts/with_file/test.toml index 63750d00f8..a769e12e63 100644 --- a/acceptance/bundle/resources/alerts/with_file/test.toml +++ b/acceptance/bundle/resources/alerts/with_file/test.toml @@ -5,9 +5,9 @@ Ignore = [".databricks"] # redact ?o=[NUMID]. Different clouds can have different URL serialization, like [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID] vs [DATABRICKS_URL]/sql/alerts-v2/[ALERT_ID]?o=[NUMID]. [[Repls]] -Old = '\?o\=[0-9]*' +Old = '\?o=\d+' New = '' -Order = 1000 +Order = 0 [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] From 9095a33ca2d1460c9a92cc15712c680162162d43 Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 5 Jan 2026 18:17:33 +0100 Subject: [PATCH 14/15] enable msys --- acceptance/bundle/resources/alerts/with_file/test.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/acceptance/bundle/resources/alerts/with_file/test.toml b/acceptance/bundle/resources/alerts/with_file/test.toml index a769e12e63..783eda7a47 100644 --- a/acceptance/bundle/resources/alerts/with_file/test.toml +++ b/acceptance/bundle/resources/alerts/with_file/test.toml @@ -11,3 +11,9 @@ Order = 0 [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] + +[Env] +# MSYS2 automatically converts absolute paths like /Users/$username/$UNIQUE_NAME to +# C:/Program Files/Git/Users/$username/UNIQUE_NAME before passing it to the CLI +# Setting this environment variable prevents that conversion on windows. +MSYS_NO_PATHCONV = "1" From 383c2189a5630b37162eeecfa66cbac71c9ab04f Mon Sep 17 00:00:00 2001 From: Shreyas Goenka Date: Mon, 5 Jan 2026 19:23:03 +0100 Subject: [PATCH 15/15] use the --strip-trailing-cr option --- acceptance/bundle/resources/alerts/with_file/output.txt | 2 +- acceptance/bundle/resources/alerts/with_file/script | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/bundle/resources/alerts/with_file/output.txt b/acceptance/bundle/resources/alerts/with_file/output.txt index da9c85f4e4..2f89acfae8 100644 --- a/acceptance/bundle/resources/alerts/with_file/output.txt +++ b/acceptance/bundle/resources/alerts/with_file/output.txt @@ -70,7 +70,7 @@ Deployment complete! === assert that the uploaded alert file is the same as the local one >>> [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/alerts-with-file-[UNIQUE_NAME]/default/resources/myalert.dbalert.json ->>> diff exported_alert.dbalert.json alert.dbalert.json +>>> diff exported_alert.dbalert.json alert.dbalert.json --strip-trailing-cr >>> [CLI] alerts-v2 get-alert [ALERT_ID] { diff --git a/acceptance/bundle/resources/alerts/with_file/script b/acceptance/bundle/resources/alerts/with_file/script index 34af9ef0bc..69209ba2d5 100755 --- a/acceptance/bundle/resources/alerts/with_file/script +++ b/acceptance/bundle/resources/alerts/with_file/script @@ -19,7 +19,7 @@ trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state, c alert_path=$($CLI bundle summary --output json | jq -r '.resources.alerts.myalert.parent_path')/myalert.dbalert.json title "assert that the uploaded alert file is the same as the local one" trace $CLI workspace export $alert_path > exported_alert.dbalert.json -trace diff exported_alert.dbalert.json alert.dbalert.json +trace diff exported_alert.dbalert.json alert.dbalert.json --strip-trailing-cr rm exported_alert.dbalert.json trace $CLI alerts-v2 get-alert $alert_id | jq '{display_name, lifecycle_state}'