Skip to content
Open
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
21 changes: 21 additions & 0 deletions acceptance/bundle/generate/alert_yaml/alert.json.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"display_name": "test alert",
"parent_path": "/Workspace/test-$UNIQUE_NAME",
"query_text": "SELECT 1\n as value",
"warehouse_id": "$TEST_DEFAULT_WAREHOUSE_ID",
"evaluation": {
"comparison_operator": "GREATER_THAN",
"source": {
"name": "value"
},
"threshold": {
"value": {
"double_value": 0.0
}
}
},
"schedule": {
"quartz_cron_schedule": "0 0 * * * ?",
"timezone_id": "UTC"
}
}
2 changes: 2 additions & 0 deletions acceptance/bundle/generate/alert_yaml/databricks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bundle:
name: alert-generate
5 changes: 5 additions & 0 deletions acceptance/bundle/generate/alert_yaml/out.test.toml

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

4 changes: 4 additions & 0 deletions acceptance/bundle/generate/alert_yaml/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
>>> [CLI] workspace mkdirs /Workspace/test-[UNIQUE_NAME]

>>> [CLI] bundle generate alert --existing-id [NUMID] --yaml --config-dir out/resource
Alert configuration successfully saved to [TEST_TMP_DIR]/out/resource/test_alert.alert.yml
9 changes: 9 additions & 0 deletions acceptance/bundle/generate/alert_yaml/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
trace $CLI workspace mkdirs /Workspace/test-$UNIQUE_NAME

# create an alert to import
envsubst < alert.json.tmpl > alert.json
alert_id=$($CLI alerts-v2 create-alert --json @alert.json | jq -r '.id')
rm alert.json

# Generate alert with --yaml flagGenerate alert with --yaml flag
trace $CLI bundle generate alert --existing-id $alert_id --yaml --config-dir out/resource
13 changes: 13 additions & 0 deletions acceptance/bundle/generate/alert_yaml/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Cloud = false
Local = false

# Alert tests timeout during bundle deploy (hang at file upload for 50+ minutes).
# Use aggressive 5-minute timeout until the issue is resolved.
# See: https://github.com/databricks/cli/issues/4221
TimeoutCloud = "5m"

[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"
26 changes: 26 additions & 0 deletions bundle/generate/alert.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package generate

import (
"encoding/json"

"github.com/databricks/cli/libs/dyn"
"github.com/databricks/cli/libs/dyn/convert"
"github.com/databricks/databricks-sdk-go/service/sql"
)

Expand All @@ -16,3 +19,26 @@ func ConvertAlertToValue(alert *sql.AlertV2, filePath string) (dyn.Value, error)

return dyn.V(dv), nil
}

func ConvertAlertToValueWithDefinition(alert *sql.AlertV2, alertJSON []byte) (dyn.Value, error) {
// Parse the alert JSON into a generic map for inline embedding
var alertDef map[string]any
if err := json.Unmarshal(alertJSON, &alertDef); err != nil {
return dyn.Value{}, err
}

// Convert the parsed JSON to a dyn.Value
defValue, err := convert.FromTyped(alertDef, dyn.NilValue)
if err != nil {
return dyn.Value{}, err
}

// Create the configuration with embedded definition
dv := map[string]dyn.Value{
"display_name": dyn.NewValue(alert.DisplayName, []dyn.Location{{Line: 1}}),
"warehouse_id": dyn.NewValue(alert.WarehouseId, []dyn.Location{{Line: 2}}),
"definition": defValue,
}

return dyn.V(dv), nil
}
83 changes: 53 additions & 30 deletions cmd/bundle/generate/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewGenerateAlertCommand() *cobra.Command {
var configDir string
var sourceDir string
var force bool
var useYAML bool

cmd := &cobra.Command{
Use: "alert",
Expand All @@ -40,13 +41,21 @@ Examples:
# Generate alert configuration by ID
databricks bundle generate alert --existing-id abc123

# Generate with inline YAML definition (no separate .dbalert.json file)
databricks bundle generate alert --existing-id abc123 --yaml

# Specify custom directories for organization
databricks bundle generate alert --existing-id abc123 \
--key my_alert --config-dir resources --source-dir src

What gets generated:
- Alert configuration YAML file with settings and a reference to the alert definition
- Alert definition (.dbalert.json) file with the complete alert specification
- Alert definition (.dbalert.json) file with the complete alert specification (unless --yaml is used)

When using --yaml flag:
- The alert definition is embedded directly in the YAML configuration
- No separate .dbalert.json file is created
- All alert settings are in a single file for easier management

After generation, you can deploy this alert to other targets using:
databricks bundle deploy --target staging
Expand All @@ -64,6 +73,7 @@ After generation, you can deploy this alert to other targets using:
cmd.Flags().StringVarP(&configDir, "config-dir", "d", "resources", `directory to write the configuration to`)
cmd.Flags().StringVarP(&sourceDir, "source-dir", "s", "src", `directory to write the alert definition to`)
cmd.Flags().BoolVarP(&force, "force", "f", false, `force overwrite existing files in the output directory`)
cmd.Flags().BoolVar(&useYAML, "yaml", false, `embed alert definition in YAML configuration instead of separate file`)

cmd.RunE = func(cmd *cobra.Command, args []string) error {
ctx := logdiag.InitContext(cmd.Context())
Expand Down Expand Up @@ -101,18 +111,7 @@ After generation, you can deploy this alert to other targets using:
sourceDir = filepath.Join(b.BundleRootPath, sourceDir)
}

// Calculate relative path from config dir to source dir
relativeSourceDir, err := filepath.Rel(configDir, sourceDir)
if err != nil {
return err
}
relativeSourceDir = filepath.ToSlash(relativeSourceDir)

// Save alert definition to source directory
alertBasename := alertKey + ".dbalert.json"
alertPath := filepath.Join(sourceDir, alertBasename)

// remote alert path
// Fetch alert definition from workspace
remoteAlertPath := path.Join(alert.ParentPath, alert.DisplayName+".dbalert.json")
resp, err := w.Workspace.Export(ctx, workspace.ExportRequest{
Path: remoteAlertPath,
Expand All @@ -125,25 +124,47 @@ After generation, you can deploy this alert to other targets using:
return err
}

// Create source directory if needed
if err := os.MkdirAll(sourceDir, 0o755); err != nil {
return err
}
var v dyn.Value
var alertPath string

// Check if file exists and force flag
if _, err := os.Stat(alertPath); err == nil && !force {
return fmt.Errorf("%s already exists. Use --force to overwrite", alertPath)
}
if useYAML {
// Embed definition directly in YAML
v, err = generate.ConvertAlertToValueWithDefinition(alert, alertJSON)
if err != nil {
return err
}
} else {
// Save alert definition to separate file
// Calculate relative path from config dir to source dir
relativeSourceDir, err := filepath.Rel(configDir, sourceDir)
if err != nil {
return err
}
relativeSourceDir = filepath.ToSlash(relativeSourceDir)

// Write alert definition file
if err := os.WriteFile(alertPath, alertJSON, 0o644); err != nil {
return err
}
alertBasename := alertKey + ".dbalert.json"
alertPath = filepath.Join(sourceDir, alertBasename)

// Convert alert to bundle configuration
v, err := generate.ConvertAlertToValue(alert, path.Join(relativeSourceDir, alertBasename))
if err != nil {
return err
// Create source directory if needed
if err := os.MkdirAll(sourceDir, 0o755); err != nil {
return err
}

// Check if file exists and force flag
if _, err := os.Stat(alertPath); err == nil && !force {
return fmt.Errorf("%s already exists. Use --force to overwrite", alertPath)
}

// Write alert definition file
if err := os.WriteFile(alertPath, alertJSON, 0o644); err != nil {
return err
}

// Convert alert to bundle configuration with file reference
v, err = generate.ConvertAlertToValue(alert, path.Join(relativeSourceDir, alertBasename))
if err != nil {
return err
}
}

result := map[string]dyn.Value{
Expand Down Expand Up @@ -171,7 +192,9 @@ After generation, you can deploy this alert to other targets using:
}

cmdio.LogString(ctx, "Alert configuration successfully saved to "+configPath)
cmdio.LogString(ctx, "Serialized alert definition to "+alertPath)
if !useYAML && alertPath != "" {
cmdio.LogString(ctx, "Serialized alert definition to "+alertPath)
}

return nil
}
Expand Down