diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go index 339f2034333..8b3e0a31334 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus.go +++ b/pkg/services/ngalert/api/api_convert_prometheus.go @@ -127,6 +127,7 @@ type ConvertPrometheusSrv struct { } type Alertmanager interface { + DeleteExtraConfiguration(ctx context.Context, org int64, identifier string) error GetAlertmanagerConfiguration(ctx context.Context, org int64, withAutogen bool) (apimodels.GettableUserConfig, error) } @@ -579,7 +580,26 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetAlertmanagerConfig(c * } func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteAlertmanagerConfig(c *contextmodel.ReqContext) response.Response { - return response.Error(http.StatusNotImplemented, "Not Implemented", nil) + if !srv.featureToggles.IsEnabledGlobally(featuremgmt.FlagAlertingImportAlertmanagerAPI) { + return response.Error(http.StatusNotImplemented, "Not Implemented", nil) + } + + logger := srv.logger.FromContext(c.Req.Context()) + + identifier, err := parseConfigIdentifierHeader(c) + if err != nil { + logger.Error("Failed to parse config identifier header", "error", err) + return errorToResponse(err) + } + + err = srv.am.DeleteExtraConfiguration(c.Req.Context(), c.GetOrgID(), identifier) + if err != nil { + logger.Error("Failed to delete alertmanager configuration", "error", err, "identifier", identifier) + return errorToResponse(fmt.Errorf("failed to delete alertmanager configuration: %w", err)) + } + + logger.Info("Successfully deleted extra alertmanager configuration", "identifier", identifier) + return successfulResponse() } // parseBooleanHeader parses a boolean header value, returning an error if the header diff --git a/pkg/services/ngalert/api/api_convert_prometheus_test.go b/pkg/services/ngalert/api/api_convert_prometheus_test.go index ace21a2ea89..d809995d5f2 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus_test.go +++ b/pkg/services/ngalert/api/api_convert_prometheus_test.go @@ -1510,7 +1510,7 @@ func (m *mockAlertmanager) GetAlertmanagerConfiguration(ctx context.Context, org return args.Get(0).(apimodels.GettableUserConfig), args.Error(1) } -func (m *mockAlertmanager) DeleteAndApplyExtraConfiguration(ctx context.Context, org int64, identifier string) error { +func (m *mockAlertmanager) DeleteExtraConfiguration(ctx context.Context, org int64, identifier string) error { args := m.Called(ctx, org, identifier) return args.Error(0) } @@ -1799,3 +1799,67 @@ func TestFormatMergeMatchers(t *testing.T) { require.Equal(t, "env=prod,team=backend", result) }) } + +func TestRouteConvertPrometheusDeleteAlertmanagerConfig(t *testing.T) { + const identifier = "test-config" + const orgID = int64(1) + + mockAM := &mockAlertmanager{} + ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI) + srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft)) + + t.Run("should parse identifier header and call DeleteExtraConfiguration", func(t *testing.T) { + mockAM.On("DeleteExtraConfiguration", mock.Anything, orgID, identifier).Return(nil).Once() + + rc := createRequestCtx() + rc.Req.Header.Set(configIdentifierHeader, identifier) + + response := srv.RouteConvertPrometheusDeleteAlertmanagerConfig(rc) + + require.Equal(t, http.StatusAccepted, response.Status()) + mockAM.AssertExpectations(t) + }) + + t.Run("should return error when identifier header is missing", func(t *testing.T) { + rc := createRequestCtx() + + response := srv.RouteConvertPrometheusDeleteAlertmanagerConfig(rc) + + require.Equal(t, http.StatusBadRequest, response.Status()) + require.Contains(t, string(response.Body()), "identifier cannot be empty") + }) + + t.Run("should return error when DeleteExtraConfiguration fails", func(t *testing.T) { + mockAM.On("DeleteExtraConfiguration", mock.Anything, orgID, identifier).Return(errors.New("delete error")).Once() + + rc := createRequestCtx() + rc.Req.Header.Set(configIdentifierHeader, identifier) + + response := srv.RouteConvertPrometheusDeleteAlertmanagerConfig(rc) + + require.Equal(t, http.StatusInternalServerError, response.Status()) + mockAM.AssertExpectations(t) + }) + + t.Run("should return not implemented when feature toggle is disabled", func(t *testing.T) { + ft := featuremgmt.WithFeatures() + srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft)) + + rc := createRequestCtx() + rc.Req.Header.Set(configIdentifierHeader, identifier) + + response := srv.RouteConvertPrometheusDeleteAlertmanagerConfig(rc) + + require.Equal(t, http.StatusNotImplemented, response.Status()) + }) + + t.Run("should return error for empty identifier header", func(t *testing.T) { + rc := createRequestCtx() + rc.Req.Header.Set(configIdentifierHeader, "") + + response := srv.RouteConvertPrometheusDeleteAlertmanagerConfig(rc) + + require.Equal(t, http.StatusBadRequest, response.Status()) + require.Contains(t, string(response.Body()), "identifier cannot be empty") + }) +} diff --git a/pkg/services/ngalert/notifier/alertmanager_config.go b/pkg/services/ngalert/notifier/alertmanager_config.go index 611aba5e3ed..510f5c25c08 100644 --- a/pkg/services/ngalert/notifier/alertmanager_config.go +++ b/pkg/services/ngalert/notifier/alertmanager_config.go @@ -412,8 +412,8 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyExtraConfiguration(ctx context.Cont return nil } -// DeleteAndApplyExtraConfiguration deletes an ExtraConfiguration by its identifier while preserving the main AlertmanagerConfig. -func (moa *MultiOrgAlertmanager) DeleteAndApplyExtraConfiguration(ctx context.Context, org int64, identifier string) error { +// DeleteExtraConfiguration deletes an ExtraConfiguration by its identifier while preserving the main AlertmanagerConfig. +func (moa *MultiOrgAlertmanager) DeleteExtraConfiguration(ctx context.Context, org int64, identifier string) error { modifyFunc := func(configs []definitions.ExtraConfiguration) ([]definitions.ExtraConfiguration, error) { filtered := make([]definitions.ExtraConfiguration, 0, len(configs)) for _, ec := range configs { diff --git a/pkg/services/ngalert/notifier/alertmanager_config_test.go b/pkg/services/ngalert/notifier/alertmanager_config_test.go index a65074072e1..642ca5e3c04 100644 --- a/pkg/services/ngalert/notifier/alertmanager_config_test.go +++ b/pkg/services/ngalert/notifier/alertmanager_config_test.go @@ -150,7 +150,7 @@ receivers: }) } -func TestMultiOrgAlertmanager_DeleteAndApplyExtraConfiguration(t *testing.T) { +func TestMultiOrgAlertmanager_DeleteExtraConfiguration(t *testing.T) { orgID := int64(1) t.Run("successfully delete existing extra configuration", func(t *testing.T) { @@ -176,7 +176,7 @@ receivers: require.NoError(t, err) require.Len(t, gettableConfig.ExtraConfigs, 1) - err = mam.DeleteAndApplyExtraConfiguration(ctx, orgID, identifier) + err = mam.DeleteExtraConfiguration(ctx, orgID, identifier) require.NoError(t, err) gettableConfig, err = mam.GetAlertmanagerConfiguration(ctx, orgID, false) @@ -189,7 +189,7 @@ receivers: ctx := context.Background() require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx)) - err := mam.DeleteAndApplyExtraConfiguration(ctx, orgID, "non-existent") + err := mam.DeleteExtraConfiguration(ctx, orgID, "non-existent") require.NoError(t, err) }) @@ -197,7 +197,7 @@ receivers: mam := setupMam(t, nil) ctx := context.Background() - err := mam.DeleteAndApplyExtraConfiguration(ctx, 999, "test-config") + err := mam.DeleteExtraConfiguration(ctx, 999, "test-config") require.Error(t, err) require.ErrorContains(t, err, "failed to get current configuration") })