diff --git a/dashboards.go b/dashboards.go index 8195c68b..f8371392 100644 --- a/dashboards.go +++ b/dashboards.go @@ -108,7 +108,7 @@ func (client *Client) UpsertDashboard(ctx context.Context, folder *Folder, build } for _, ref := range alertRefs { if err := client.DeleteAlertGroup(ctx, ref.Namespace, ref.RuleGroup); err != nil { - return nil, fmt.Errorf("could not delete of previous alerts for dashboard: %w", err) + return nil, fmt.Errorf("could not delete previous alerts for dashboard: %w", err) } } @@ -174,6 +174,18 @@ func (client *Client) persistDashboard(ctx context.Context, folder *Folder, buil // DeleteDashboard deletes a dashboard given its UID. func (client *Client) DeleteDashboard(ctx context.Context, uid string) error { + // first: delete existing alerts associated to that dashboard + alertRefs, err := client.listAlertsForDashboard(ctx, uid) + if err != nil { + return fmt.Errorf("could not prepare deletion of alerts for dashboard: %w", err) + } + for _, ref := range alertRefs { + if err := client.DeleteAlertGroup(ctx, ref.Namespace, ref.RuleGroup); err != nil { + return fmt.Errorf("could not delete alerts for dashboard: %w", err) + } + } + + // then: delete the dashboard itself resp, err := client.delete(ctx, "/api/dashboards/uid/"+uid) if err != nil { return err diff --git a/dashboards_test.go b/dashboards_test.go index 1c5ef2b8..d7738094 100644 --- a/dashboards_test.go +++ b/dashboards_test.go @@ -145,19 +145,85 @@ func TestFetchingAnUnknownDashboardByUIDFailsCleanly(t *testing.T) { req.ErrorIs(err, ErrDashboardNotFound) } -func TestDeleteDashboard(t *testing.T) { +func TestDeleteDashboardWithNoAlerts(t *testing.T) { req := require.New(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprintln(w, `{"title": "Production Overview"}`) + // Potential existing alerts retrieval + if r.Method == http.MethodGet && r.URL.String() == "/api/ruler/grafana/api/v1/rules?dashboard_uid=some-uid" { + _, _ = fmt.Fprintln(w, `{}`) + return + } + + // Dashboard deletion + if r.Method == http.MethodDelete && r.URL.String() == "/api/dashboards/uid/some-uid" { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintln(w, `{"title": "Production Overview"}`) + return + } + + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintf(w, `{"message": "oh noes, we should not get here", "method": "%s", "path": "%s"}\n`, r.Method, r.URL.String()) })) defer ts.Close() client := NewClient(http.DefaultClient, ts.URL) - err := client.DeleteDashboard(context.TODO(), "some uid") + err := client.DeleteDashboard(context.TODO(), "some-uid") + + req.NoError(err) +} + +func TestDeleteDashboardWithAlerts(t *testing.T) { + req := require.New(t) + firstAlertDeleted := false + secondAlertDeleted := false + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Potential existing alerts retrieval + if r.Method == http.MethodGet && r.URL.String() == "/api/ruler/grafana/api/v1/rules?dashboard_uid=some-uid" { + _, _ = fmt.Fprintln(w, `{ + "test ns 1": [ + {"name": "alert 1"} + ], + "test ns 2": [ + {"name": "alert 2"} + ] +}`) + return + } + + // First alert deletion + if r.Method == http.MethodDelete && r.URL.String() == "/api/ruler/grafana/api/v1/rules/test%20ns%201/alert%201" { + firstAlertDeleted = true + w.WriteHeader(http.StatusAccepted) + return + } + + // Second alert deletion + if r.Method == http.MethodDelete && r.URL.String() == "/api/ruler/grafana/api/v1/rules/test%20ns%202/alert%202" { + secondAlertDeleted = true + w.WriteHeader(http.StatusAccepted) + return + } + + // Dashboard deletion + if r.Method == http.MethodDelete && r.URL.String() == "/api/dashboards/uid/some-uid" { + w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintln(w, `{"title": "Production Overview"}`) + return + } + + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintf(w, `{"message": "oh noes, we should not get here", "method": "%s", "path": "%s"}\n`, r.Method, r.URL.String()) + })) + defer ts.Close() + + client := NewClient(http.DefaultClient, ts.URL) + + err := client.DeleteDashboard(context.TODO(), "some-uid") req.NoError(err) + req.True(firstAlertDeleted) + req.True(secondAlertDeleted) } func TestDeleteDashboardCanFail(t *testing.T) { @@ -178,14 +244,27 @@ func TestDeleteDashboardCanFail(t *testing.T) { func TestDeletingANonExistingDashboardReturnsSpecificError(t *testing.T) { req := require.New(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = fmt.Fprintln(w, `{"message": "oh noes, does not exist"}`) + // Potential existing alerts retrieval + if r.Method == http.MethodGet && r.URL.String() == "/api/ruler/grafana/api/v1/rules?dashboard_uid=some-uid" { + _, _ = fmt.Fprintln(w, `{}`) + return + } + + // Dashboard deletion + if r.Method == http.MethodDelete && r.URL.String() == "/api/dashboards/uid/some-uid" { + w.WriteHeader(http.StatusNotFound) + _, _ = fmt.Fprintln(w, `{"message": "oh noes, does not exist"}`) + return + } + + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintf(w, `{"message": "oh noes, we should not get here", "method": "%s", "path": "%s"}\n`, r.Method, r.URL.String()) })) defer ts.Close() client := NewClient(http.DefaultClient, ts.URL) - err := client.DeleteDashboard(context.TODO(), "some uid") + err := client.DeleteDashboard(context.TODO(), "some-uid") req.Equal(ErrDashboardNotFound, err) }