diff --git a/docs/data-sources/notification_destinations.md b/docs/data-sources/notification_destinations.md new file mode 100644 index 000000000..777e41f5c --- /dev/null +++ b/docs/data-sources/notification_destinations.md @@ -0,0 +1,62 @@ +--- +subcategory: "Workspace" +--- +# databricks_notification_destinations Data Source + +This data source allows you to retrieve information about [Notification Destinations](https://docs.databricks.com/api/workspace/notificationdestinations). Notification Destinations are used to send notifications for query alerts and jobs to external systems such as email, Slack, Microsoft Teams, PagerDuty, or generic webhooks. + +## Example Usage + + +```hcl +resource "databricks_notification_destination" "email" { + display_name = "Email Destination" + config { + email { + addresses = ["abc@gmail.com"] + } + } +} + +resource "databricks_notification_destination" "slack" { + display_name = "Slack Destination" + config { + slack { + url = "https://hooks.slack.com/services/..." + } + } +} + +# Lists all notification desitnations +data "databricks_notification_destinations" "this" {} + +# List destinations of specific type and name +data "databricks_notification_destinations" "filtered_notification" { + display_name_contains = "Destination" + type = "EMAIL" +} +``` + + +## Argument Reference + +The following arguments are supported: + +* `display_name_contains` - (Optional) A **case-insensitive** substring to filter Notification Destinations by their display name. +* `type` - (Optional) The type of the Notification Destination to filter by. Valid values are: + * `EMAIL` - Filters Notification Destinations of type Email. + * `MICROSOFT_TEAMS` - Filters Notification Destinations of type Microsoft Teams. + * `PAGERDUTY` - Filters Notification Destinations of type PagerDuty. + * `SLACK` - Filters Notification Destinations of type Slack. + * `WEBHOOK` - Filters Notification Destinations of type Webhook. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `notification_destinations` - A list of Notification Destinations matching the specified criteria. Each element contains the following attributes: + * `id` - The unique ID of the Notification Destination. + * `display_name` - The display name of the Notification Destination. + * `destination_type` - The type of the notification destination. Possible values are `EMAIL`, `MICROSOFT_TEAMS`, `PAGERDUTY`, `SLACK`, or `WEBHOOK`. + +If no matches are found, an empty list will be returned. diff --git a/internal/providers/pluginfw/pluginfw.go b/internal/providers/pluginfw/pluginfw.go index cc5197513..db811d5ae 100644 --- a/internal/providers/pluginfw/pluginfw.go +++ b/internal/providers/pluginfw/pluginfw.go @@ -18,6 +18,7 @@ import ( providercommon "github.com/databricks/terraform-provider-databricks/internal/providers/common" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/resources/cluster" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/resources/library" + "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/resources/notificationdestinations" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/resources/qualitymonitor" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/resources/registered_model" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/resources/volume" @@ -54,6 +55,7 @@ func (p *DatabricksProviderPluginFramework) DataSources(ctx context.Context) []f cluster.DataSourceCluster, volume.DataSourceVolumes, registered_model.DataSourceRegisteredModel, + notificationdestinations.DataSourceNotificationDestinations, } } diff --git a/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations.go b/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations.go new file mode 100755 index 000000000..441877fdd --- /dev/null +++ b/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations.go @@ -0,0 +1,116 @@ +package notificationdestinations + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/databricks/databricks-sdk-go/service/settings" + "github.com/databricks/terraform-provider-databricks/common" + pluginfwcommon "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/common" + "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/converters" + "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/tfschema" + "github.com/databricks/terraform-provider-databricks/internal/service/settings_tf" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func DataSourceNotificationDestinations() datasource.DataSource { + return &NotificationDestinationsDataSource{} +} + +var _ datasource.DataSourceWithConfigure = &NotificationDestinationsDataSource{} + +type NotificationDestinationsDataSource struct { + Client *common.DatabricksClient +} + +type NotificationDestinationsInfo struct { + DisplayNameContains types.String `tfsdk:"display_name_contains" tf:"optional"` + Type types.String `tfsdk:"type" tf:"optional"` + NotificationDestinations []settings_tf.ListNotificationDestinationsResult `tfsdk:"notification_destinations" tf:"computed"` +} + +func (d *NotificationDestinationsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "databricks_notification_destinations" +} + +func (d *NotificationDestinationsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + attrs, blocks := tfschema.DataSourceStructToSchemaMap(NotificationDestinationsInfo{}, nil) + resp.Schema = schema.Schema{ + Attributes: attrs, + Blocks: blocks, + } +} + +func (d *NotificationDestinationsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if d.Client == nil { + d.Client = pluginfwcommon.ConfigureDataSource(req, resp) + } +} + +func validateType(notificationType string) diag.Diagnostics { + validTypes := []string{ + string(settings.DestinationTypeEmail), + string(settings.DestinationTypeMicrosoftTeams), + string(settings.DestinationTypePagerduty), + string(settings.DestinationTypeSlack), + string(settings.DestinationTypeWebhook), + } + + if !slices.Contains(validTypes, notificationType) { + return diag.Diagnostics{diag.NewErrorDiagnostic(fmt.Sprintf("Invalid type '%s'; valid types are %s.", notificationType, strings.Join(validTypes, ", ")), "")} + } + return nil +} + +func AppendDiagAndCheckErrors(resp *datasource.ReadResponse, diags diag.Diagnostics) bool { + resp.Diagnostics.Append(diags...) + return resp.Diagnostics.HasError() +} + +func (d *NotificationDestinationsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + w, diags := d.Client.GetWorkspaceClient() + if AppendDiagAndCheckErrors(resp, diags) { + return + } + + var notificationInfo NotificationDestinationsInfo + if AppendDiagAndCheckErrors(resp, req.Config.Get(ctx, ¬ificationInfo)) { + return + } + + notificationType := notificationInfo.Type.ValueString() + notificationDisplayName := strings.ToLower(notificationInfo.DisplayNameContains.ValueString()) + + if notificationType != "" && AppendDiagAndCheckErrors(resp, validateType(notificationType)) { + return + } + + notificationsGoSdk, err := w.NotificationDestinations.ListAll(ctx, settings.ListNotificationDestinationsRequest{}) + if err != nil { + resp.Diagnostics.AddError("Failed to fetch Notification Destinations", err.Error()) + return + } + + var notificationsTfSdk []settings_tf.ListNotificationDestinationsResult + for _, notification := range notificationsGoSdk { + if (notificationType != "" && notification.DestinationType.String() != notificationType) || + (notificationDisplayName != "" && !strings.Contains(strings.ToLower(notification.DisplayName), notificationDisplayName)) { + continue + } + + var notificationDestination settings_tf.ListNotificationDestinationsResult + if AppendDiagAndCheckErrors(resp, converters.GoSdkToTfSdkStruct(ctx, notification, ¬ificationDestination)) { + return + } + notificationsTfSdk = append(notificationsTfSdk, notificationDestination) + } + + notificationInfo.NotificationDestinations = notificationsTfSdk + resp.Diagnostics.Append(resp.State.Set(ctx, notificationInfo)...) + +} diff --git a/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations_acc_test.go b/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations_acc_test.go new file mode 100644 index 000000000..cf72b0da2 --- /dev/null +++ b/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations_acc_test.go @@ -0,0 +1,63 @@ +package notificationdestinations_test + +import ( + "testing" + + "github.com/databricks/terraform-provider-databricks/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func CheckDataSourceNotificationsPopulated(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + ds, ok := s.Modules[0].Resources["data.databricks_notification_destinations.this"] + require.True(t, ok, "data.databricks_notification_destinations.this has to be there") + + notificationCount := ds.Primary.Attributes["notification_destinations.#"] + require.Equal(t, "2", notificationCount, "expected two notifications") + + notificationIds := []string{ + ds.Primary.Attributes["notification_destinations.0.id"], + ds.Primary.Attributes["notification_destinations.1.id"], + } + + expectedNotificationIds := []string{ + s.Modules[0].Resources["databricks_notification_destination.email_notification"].Primary.ID, + s.Modules[0].Resources["databricks_notification_destination.slack_notification"].Primary.ID, + } + + assert.ElementsMatch(t, expectedNotificationIds, notificationIds, "expected notification_destination ids to match") + + return nil + } +} + +func TestAccNotificationsCreation(t *testing.T) { + acceptance.WorkspaceLevel(t, acceptance.Step{ + Template: ` + resource "databricks_notification_destination" "email_notification" { + display_name = "email notification destination" + config { + email { + addresses = ["abc@gmail.com"] + } + } + } + + resource "databricks_notification_destination" "slack_notification" { + display_name = "slack notification destination" + config { + slack { + url = "https://hooks.slack.com/services/..." + } + } + } + + data "databricks_notification_destinations" "this" { + depends_on = [databricks_notification_destination.email_notification, databricks_notification_destination.slack_notification] + } + `, + Check: CheckDataSourceNotificationsPopulated(t), + }) +} diff --git a/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations_test.go b/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations_test.go new file mode 100755 index 000000000..45f9625bd --- /dev/null +++ b/internal/providers/pluginfw/resources/notificationdestinations/data_notification_destinations_test.go @@ -0,0 +1,15 @@ +package notificationdestinations + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stretchr/testify/assert" +) + +func TestValidateType_InvalidType(t *testing.T) { + actualDiagnostics := validateType("INVALID") + expectedDiagnostics := diag.Diagnostics{diag.NewErrorDiagnostic("Invalid type 'INVALID'; valid types are EMAIL, MICROSOFT_TEAMS, PAGERDUTY, SLACK, WEBHOOK.", "")} + assert.True(t, actualDiagnostics.HasError()) + assert.Equal(t, expectedDiagnostics, actualDiagnostics) +}