From bc7b16d61afdc8ce957bc3d81c3fb32cd14fae9d Mon Sep 17 00:00:00 2001 From: gotjosh Date: Wed, 31 Mar 2021 08:58:06 +0100 Subject: [PATCH] API: Make conversion functions public (#2530) * API: Make conversion functions public All types are public so it makes sense to make these functions also public for ease of downstream consumption. Signed-off-by: gotjosh * Add the license header to the new file Signed-off-by: gotjosh * Fix tests Signed-off-by: gotjosh * Make two more helper functions public Signed-off-by: gotjosh --- api/v2/api.go | 192 +++---------------------------------------- api/v2/api_test.go | 6 +- api/v2/compat.go | 197 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 182 deletions(-) create mode 100644 api/v2/compat.go diff --git a/api/v2/api.go b/api/v2/api.go index de649584..0f200327 100644 --- a/api/v2/api.go +++ b/api/v2/api.go @@ -270,7 +270,7 @@ func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Re continue } - alert := alertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers) + alert := AlertToOpenAPIAlert(a, api.getAlertStatus(a.Fingerprint()), receivers) res = append(res, alert) } @@ -290,7 +290,7 @@ func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Re func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware.Responder { logger := api.requestLogger(params.HTTPRequest) - alerts := openAPIAlertsToAlerts(params.Alerts) + alerts := OpenAPIAlertsToAlerts(params.Alerts) now := time.Now() api.mtx.RLock() @@ -389,7 +389,7 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams for _, alertGroup := range alertGroups { ag := &open_api_models.AlertGroup{ Receiver: &open_api_models.Receiver{Name: &alertGroup.Receiver}, - Labels: modelLabelSetToAPILabelSet(alertGroup.Labels), + Labels: ModelLabelSetToAPILabelSet(alertGroup.Labels), Alerts: make([]*open_api_models.GettableAlert, 0, len(alertGroup.Alerts)), } @@ -397,7 +397,7 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams fp := alert.Fingerprint() receivers := allReceivers[fp] status := api.getAlertStatus(fp) - apiAlert := alertToOpenAPIAlert(alert, status, receivers) + apiAlert := AlertToOpenAPIAlert(alert, status, receivers) ag.Alerts = append(ag.Alerts, apiAlert) } res = append(res, ag) @@ -434,65 +434,6 @@ func (api *API) alertFilter(matchers []*labels.Matcher, silenced, inhibited, act } } -func alertToOpenAPIAlert(alert *types.Alert, status types.AlertStatus, receivers []string) *open_api_models.GettableAlert { - startsAt := strfmt.DateTime(alert.StartsAt) - updatedAt := strfmt.DateTime(alert.UpdatedAt) - endsAt := strfmt.DateTime(alert.EndsAt) - - apiReceivers := make([]*open_api_models.Receiver, 0, len(receivers)) - for i := range receivers { - apiReceivers = append(apiReceivers, &open_api_models.Receiver{Name: &receivers[i]}) - } - - fp := alert.Fingerprint().String() - state := string(status.State) - aa := &open_api_models.GettableAlert{ - Alert: open_api_models.Alert{ - GeneratorURL: strfmt.URI(alert.GeneratorURL), - Labels: modelLabelSetToAPILabelSet(alert.Labels), - }, - Annotations: modelLabelSetToAPILabelSet(alert.Annotations), - StartsAt: &startsAt, - UpdatedAt: &updatedAt, - EndsAt: &endsAt, - Fingerprint: &fp, - Receivers: apiReceivers, - Status: &open_api_models.AlertStatus{ - State: &state, - SilencedBy: status.SilencedBy, - InhibitedBy: status.InhibitedBy, - }, - } - - if aa.Status.SilencedBy == nil { - aa.Status.SilencedBy = []string{} - } - - if aa.Status.InhibitedBy == nil { - aa.Status.InhibitedBy = []string{} - } - - return aa -} - -func openAPIAlertsToAlerts(apiAlerts open_api_models.PostableAlerts) []*types.Alert { - alerts := []*types.Alert{} - for _, apiAlert := range apiAlerts { - alert := types.Alert{ - Alert: prometheus_model.Alert{ - Labels: apiLabelSetToModelLabelSet(apiAlert.Labels), - Annotations: apiLabelSetToModelLabelSet(apiAlert.Annotations), - StartsAt: time.Time(apiAlert.StartsAt), - EndsAt: time.Time(apiAlert.EndsAt), - GeneratorURL: string(apiAlert.GeneratorURL), - }, - } - alerts = append(alerts, &alert) - } - - return alerts -} - func removeEmptyLabels(ls prometheus_model.LabelSet) { for k, v := range ls { if string(v) == "" { @@ -501,24 +442,6 @@ func removeEmptyLabels(ls prometheus_model.LabelSet) { } } -func modelLabelSetToAPILabelSet(modelLabelSet prometheus_model.LabelSet) open_api_models.LabelSet { - apiLabelSet := open_api_models.LabelSet{} - for key, value := range modelLabelSet { - apiLabelSet[string(key)] = string(value) - } - - return apiLabelSet -} - -func apiLabelSetToModelLabelSet(apiLabelSet open_api_models.LabelSet) prometheus_model.LabelSet { - modelLabelSet := prometheus_model.LabelSet{} - for key, value := range apiLabelSet { - modelLabelSet[prometheus_model.LabelName(key)] = prometheus_model.LabelValue(value) - } - - return modelLabelSet -} - func receiversMatchFilter(receivers []string, filter *regexp.Regexp) bool { for _, r := range receivers { if filter.MatchString(r) { @@ -585,10 +508,10 @@ func (api *API) getSilencesHandler(params silence_ops.GetSilencesParams) middlew sils := open_api_models.GettableSilences{} for _, ps := range psils { - if !checkSilenceMatchesFilterLabels(ps, matchers) { + if !CheckSilenceMatchesFilterLabels(ps, matchers) { continue } - silence, err := gettableSilenceFromProto(ps) + silence, err := GettableSilenceFromProto(ps) if err != nil { level.Error(logger).Log("msg", "Failed to unmarshal silence from proto", "err", err) return silence_ops.NewGetSilencesInternalServerError().WithPayload(err.Error()) @@ -596,7 +519,7 @@ func (api *API) getSilencesHandler(params silence_ops.GetSilencesParams) middlew sils = append(sils, &silence) } - sortSilences(sils) + SortSilences(sils) return silence_ops.NewGetSilencesOK().WithPayload(sils) } @@ -609,12 +532,12 @@ var ( } ) -// sortSilences sorts first according to the state "active, pending, expired" +// SortSilences sorts first according to the state "active, pending, expired" // then by end time or start time depending on the state. // active silences should show the next to expire first // pending silences are ordered based on which one starts next // expired are ordered based on which one expired most recently -func sortSilences(sils open_api_models.GettableSilences) { +func SortSilences(sils open_api_models.GettableSilences) { sort.Slice(sils, func(i, j int) bool { state1 := types.SilenceState(*sils[i].Status.State) state2 := types.SilenceState(*sils[j].Status.State) @@ -639,12 +562,12 @@ func sortSilences(sils open_api_models.GettableSilences) { }) } -// checkSilenceMatchesFilterLabels returns true if +// CheckSilenceMatchesFilterLabels returns true if // a given silence matches a list of matchers. // A silence matches a filter (list of matchers) if // for all matchers in the filter, there exists a matcher in the silence // such that their names, types, and values are equivalent. -func checkSilenceMatchesFilterLabels(s *silencepb.Silence, matchers []*labels.Matcher) bool { +func CheckSilenceMatchesFilterLabels(s *silencepb.Silence, matchers []*labels.Matcher) bool { for _, matcher := range matchers { found := false for _, m := range s.Matchers { @@ -680,7 +603,7 @@ func (api *API) getSilenceHandler(params silence_ops.GetSilenceParams) middlewar return silence_ops.NewGetSilenceNotFound() } - sil, err := gettableSilenceFromProto(sils[0]) + sil, err := GettableSilenceFromProto(sils[0]) if err != nil { level.Error(logger).Log("msg", "Failed to convert unmarshal from proto", "err", err) return silence_ops.NewGetSilenceInternalServerError().WithPayload(err.Error()) @@ -700,62 +623,10 @@ func (api *API) deleteSilenceHandler(params silence_ops.DeleteSilenceParams) mid return silence_ops.NewDeleteSilenceOK() } -func gettableSilenceFromProto(s *silencepb.Silence) (open_api_models.GettableSilence, error) { - start := strfmt.DateTime(s.StartsAt) - end := strfmt.DateTime(s.EndsAt) - updated := strfmt.DateTime(s.UpdatedAt) - state := string(types.CalcSilenceState(s.StartsAt, s.EndsAt)) - sil := open_api_models.GettableSilence{ - Silence: open_api_models.Silence{ - StartsAt: &start, - EndsAt: &end, - Comment: &s.Comment, - CreatedBy: &s.CreatedBy, - }, - ID: &s.Id, - UpdatedAt: &updated, - Status: &open_api_models.SilenceStatus{ - State: &state, - }, - } - - for _, m := range s.Matchers { - matcher := &open_api_models.Matcher{ - Name: &m.Name, - Value: &m.Pattern, - } - f := false - t := true - switch m.Type { - case silencepb.Matcher_EQUAL: - matcher.IsEqual = &t - matcher.IsRegex = &f - case silencepb.Matcher_NOT_EQUAL: - matcher.IsEqual = &f - matcher.IsRegex = &f - case silencepb.Matcher_REGEXP: - matcher.IsEqual = &t - matcher.IsRegex = &t - case silencepb.Matcher_NOT_REGEXP: - matcher.IsEqual = &f - matcher.IsRegex = &t - default: - return sil, fmt.Errorf( - "unknown matcher type for matcher '%v' in silence '%v'", - m.Name, - s.Id, - ) - } - sil.Matchers = append(sil.Matchers, matcher) - } - - return sil, nil -} - func (api *API) postSilencesHandler(params silence_ops.PostSilencesParams) middleware.Responder { logger := api.requestLogger(params.HTTPRequest) - sil, err := postableSilenceToProto(params.Silence) + sil, err := PostableSilenceToProto(params.Silence) if err != nil { level.Error(logger).Log("msg", "Failed to marshal silence to proto", "err", err) return silence_ops.NewPostSilencesBadRequest().WithPayload( @@ -789,43 +660,6 @@ func (api *API) postSilencesHandler(params silence_ops.PostSilencesParams) middl }) } -func postableSilenceToProto(s *open_api_models.PostableSilence) (*silencepb.Silence, error) { - sil := &silencepb.Silence{ - Id: s.ID, - StartsAt: time.Time(*s.StartsAt), - EndsAt: time.Time(*s.EndsAt), - Comment: *s.Comment, - CreatedBy: *s.CreatedBy, - } - for _, m := range s.Matchers { - matcher := &silencepb.Matcher{ - Name: *m.Name, - Pattern: *m.Value, - } - isEqual := true - if m.IsEqual != nil { - isEqual = *m.IsEqual - } - isRegex := false - if m.IsRegex != nil { - isRegex = *m.IsRegex - } - - switch { - case isEqual && !isRegex: - matcher.Type = silencepb.Matcher_EQUAL - case !isEqual && !isRegex: - matcher.Type = silencepb.Matcher_NOT_EQUAL - case isEqual && isRegex: - matcher.Type = silencepb.Matcher_REGEXP - case !isEqual && isRegex: - matcher.Type = silencepb.Matcher_NOT_REGEXP - } - sil.Matchers = append(sil.Matchers, matcher) - } - return sil, nil -} - func parseFilter(filter []string) ([]*labels.Matcher, error) { matchers := make([]*labels.Matcher, 0, len(filter)) for _, matcherString := range filter { diff --git a/api/v2/api_test.go b/api/v2/api_test.go index 7654687c..5ff72fee 100644 --- a/api/v2/api_test.go +++ b/api/v2/api_test.go @@ -124,7 +124,7 @@ func TestGetSilencesHandler(t *testing.T) { gettableSilence("silence-2-active", "active", updateTime, "2019-01-01T12:00:00+00:00", "2019-01-01T14:00:00+00:00"), } - sortSilences(open_api_models.GettableSilences(silences)) + SortSilences(open_api_models.GettableSilences(silences)) for i, sil := range silences { assertEqualStrings(t, "silence-"+strconv.Itoa(i)+"-"+*sil.Status.State, *sil.ID) @@ -227,7 +227,7 @@ func TestCheckSilenceMatchesFilterLabels(t *testing.T) { silence := silencepb.Silence{ Matchers: test.silenceMatchers, } - actual := checkSilenceMatchesFilterLabels(&silence, test.filterMatchers) + actual := CheckSilenceMatchesFilterLabels(&silence, test.filterMatchers) if test.expected != actual { t.Fatal("unexpected match result between silence and filter. expected:", test.expected, ", actual:", actual) } @@ -255,7 +255,7 @@ func TestAlertToOpenAPIAlert(t *testing.T) { UpdatedAt: updated, } ) - openAPIAlert := alertToOpenAPIAlert(alert, types.AlertStatus{State: types.AlertStateActive}, receivers) + openAPIAlert := AlertToOpenAPIAlert(alert, types.AlertStatus{State: types.AlertStateActive}, receivers) require.Equal(t, &open_api_models.GettableAlert{ Annotations: open_api_models.LabelSet{}, Alert: open_api_models.Alert{ diff --git a/api/v2/compat.go b/api/v2/compat.go new file mode 100644 index 00000000..6b948c2f --- /dev/null +++ b/api/v2/compat.go @@ -0,0 +1,197 @@ +// Copyright 2021 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v2 + +import ( + "fmt" + "time" + + "github.com/go-openapi/strfmt" + open_api_models "github.com/prometheus/alertmanager/api/v2/models" + "github.com/prometheus/alertmanager/silence/silencepb" + "github.com/prometheus/alertmanager/types" + prometheus_model "github.com/prometheus/common/model" +) + +// GettableSilenceFromProto converts *silencepb.Silence to open_api_models.GettableSilence. +func GettableSilenceFromProto(s *silencepb.Silence) (open_api_models.GettableSilence, error) { + start := strfmt.DateTime(s.StartsAt) + end := strfmt.DateTime(s.EndsAt) + updated := strfmt.DateTime(s.UpdatedAt) + state := string(types.CalcSilenceState(s.StartsAt, s.EndsAt)) + sil := open_api_models.GettableSilence{ + Silence: open_api_models.Silence{ + StartsAt: &start, + EndsAt: &end, + Comment: &s.Comment, + CreatedBy: &s.CreatedBy, + }, + ID: &s.Id, + UpdatedAt: &updated, + Status: &open_api_models.SilenceStatus{ + State: &state, + }, + } + + for _, m := range s.Matchers { + matcher := &open_api_models.Matcher{ + Name: &m.Name, + Value: &m.Pattern, + } + f := false + t := true + switch m.Type { + case silencepb.Matcher_EQUAL: + matcher.IsEqual = &t + matcher.IsRegex = &f + case silencepb.Matcher_NOT_EQUAL: + matcher.IsEqual = &f + matcher.IsRegex = &f + case silencepb.Matcher_REGEXP: + matcher.IsEqual = &t + matcher.IsRegex = &t + case silencepb.Matcher_NOT_REGEXP: + matcher.IsEqual = &f + matcher.IsRegex = &t + default: + return sil, fmt.Errorf( + "unknown matcher type for matcher '%v' in silence '%v'", + m.Name, + s.Id, + ) + } + sil.Matchers = append(sil.Matchers, matcher) + } + + return sil, nil +} + +// PostableSilenceToProto converts *open_api_models.PostableSilenc to *silencepb.Silence. +func PostableSilenceToProto(s *open_api_models.PostableSilence) (*silencepb.Silence, error) { + sil := &silencepb.Silence{ + Id: s.ID, + StartsAt: time.Time(*s.StartsAt), + EndsAt: time.Time(*s.EndsAt), + Comment: *s.Comment, + CreatedBy: *s.CreatedBy, + } + for _, m := range s.Matchers { + matcher := &silencepb.Matcher{ + Name: *m.Name, + Pattern: *m.Value, + } + isEqual := true + if m.IsEqual != nil { + isEqual = *m.IsEqual + } + isRegex := false + if m.IsRegex != nil { + isRegex = *m.IsRegex + } + + switch { + case isEqual && !isRegex: + matcher.Type = silencepb.Matcher_EQUAL + case !isEqual && !isRegex: + matcher.Type = silencepb.Matcher_NOT_EQUAL + case isEqual && isRegex: + matcher.Type = silencepb.Matcher_REGEXP + case !isEqual && isRegex: + matcher.Type = silencepb.Matcher_NOT_REGEXP + } + sil.Matchers = append(sil.Matchers, matcher) + } + return sil, nil +} + +// AlertToOpenAPIAlert converts internal alerts, alert types, and receivers to *open_api_models.GettableAlert. +func AlertToOpenAPIAlert(alert *types.Alert, status types.AlertStatus, receivers []string) *open_api_models.GettableAlert { + startsAt := strfmt.DateTime(alert.StartsAt) + updatedAt := strfmt.DateTime(alert.UpdatedAt) + endsAt := strfmt.DateTime(alert.EndsAt) + + apiReceivers := make([]*open_api_models.Receiver, 0, len(receivers)) + for i := range receivers { + apiReceivers = append(apiReceivers, &open_api_models.Receiver{Name: &receivers[i]}) + } + + fp := alert.Fingerprint().String() + state := string(status.State) + aa := &open_api_models.GettableAlert{ + Alert: open_api_models.Alert{ + GeneratorURL: strfmt.URI(alert.GeneratorURL), + Labels: ModelLabelSetToAPILabelSet(alert.Labels), + }, + Annotations: ModelLabelSetToAPILabelSet(alert.Annotations), + StartsAt: &startsAt, + UpdatedAt: &updatedAt, + EndsAt: &endsAt, + Fingerprint: &fp, + Receivers: apiReceivers, + Status: &open_api_models.AlertStatus{ + State: &state, + SilencedBy: status.SilencedBy, + InhibitedBy: status.InhibitedBy, + }, + } + + if aa.Status.SilencedBy == nil { + aa.Status.SilencedBy = []string{} + } + + if aa.Status.InhibitedBy == nil { + aa.Status.InhibitedBy = []string{} + } + + return aa +} + +// OpenAPIAlertsToAlerts converts open_api_models.PostableAlerts to []*types.Alert. +func OpenAPIAlertsToAlerts(apiAlerts open_api_models.PostableAlerts) []*types.Alert { + alerts := []*types.Alert{} + for _, apiAlert := range apiAlerts { + alert := types.Alert{ + Alert: prometheus_model.Alert{ + Labels: APILabelSetToModelLabelSet(apiAlert.Labels), + Annotations: APILabelSetToModelLabelSet(apiAlert.Annotations), + StartsAt: time.Time(apiAlert.StartsAt), + EndsAt: time.Time(apiAlert.EndsAt), + GeneratorURL: string(apiAlert.GeneratorURL), + }, + } + alerts = append(alerts, &alert) + } + + return alerts +} + +// ModelLabelSetToAPILabelSet converts prometheus_model.LabelSet to open_api_models.LabelSet. +func ModelLabelSetToAPILabelSet(modelLabelSet prometheus_model.LabelSet) open_api_models.LabelSet { + apiLabelSet := open_api_models.LabelSet{} + for key, value := range modelLabelSet { + apiLabelSet[string(key)] = string(value) + } + + return apiLabelSet +} + +// APILabelSetToModelLabelSet converts open_api_models.LabelSet to prometheus_model.LabelSet. +func APILabelSetToModelLabelSet(apiLabelSet open_api_models.LabelSet) prometheus_model.LabelSet { + modelLabelSet := prometheus_model.LabelSet{} + for key, value := range apiLabelSet { + modelLabelSet[prometheus_model.LabelName(key)] = prometheus_model.LabelValue(value) + } + + return modelLabelSet +}