diff --git a/.golangci.yml b/.golangci.yml index 00000ecff..860bebb3c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -62,7 +62,6 @@ linters-settings: disabled: true - name: unnecessary-stmt disabled: true - mnd: checks: - argument @@ -86,6 +85,8 @@ linters-settings: - '0o666' - '0o644' - '0o755' + wsl: + strict-append: false linters: disable-all: true @@ -144,6 +145,7 @@ linters: - usestdlibvars - wastedassign - whitespace + - wsl issues: exclude-dirs: @@ -151,6 +153,9 @@ issues: exclude-files: - notifier/registrator.go exclude-rules: + - path-except: 'api/*/*' + linters: + - wsl - path: _test\.go linters: - contextcheck @@ -159,6 +164,7 @@ issues: - errcheck - goconst - revive + - wsl run: timeout: 5m diff --git a/api/authorization.go b/api/authorization.go index 81e068617..3b1660099 100644 --- a/api/authorization.go +++ b/api/authorization.go @@ -17,7 +17,9 @@ func (auth *Authorization) IsAdmin(login string) bool { if !auth.IsEnabled() { return false } + _, ok := auth.AdminList[login] + return ok } @@ -35,8 +37,10 @@ func (auth *Authorization) GetRole(login string) Role { if !auth.IsEnabled() { return RoleUndefined } + if auth.IsAdmin(login) { return RoleAdmin } + return RoleUser } diff --git a/api/controller/contact.go b/api/controller/contact.go index ae523e879..f735f6958 100644 --- a/api/controller/contact.go +++ b/api/controller/contact.go @@ -24,9 +24,11 @@ func GetAllContacts(database moira.Database) (*dto.ContactList, *api.ErrorRespon if err != nil { return nil, api.ErrorInternalServer(err) } + contactsList := dto.ContactList{ List: contacts, } + return &contactsList, nil } @@ -83,12 +85,14 @@ func CreateContact( if err != nil { return api.ErrorInternalServer(err) } + contactData.ID = uuid4.String() } else { exists, err := isContactExists(dataBase, contactData.ID) if err != nil { return api.ErrorInternalServer(err) } + if exists { return api.ErrorInvalidRequest(fmt.Errorf("contact with this ID already exists")) } @@ -97,9 +101,11 @@ func CreateContact( if err := dataBase.SaveContact(&contactData); err != nil { return api.ErrorInternalServer(err) } + contact.User = contactData.User contact.ID = contactData.ID contact.TeamID = contactData.Team + return nil } @@ -117,23 +123,28 @@ func UpdateContact( contactData.Type = contactDTO.Type contactData.Value = contactDTO.Value contactData.Name = contactDTO.Name + if err := dataBase.SaveContact(&contactData); err != nil { return contactDTO, api.ErrorInternalServer(err) } + contactDTO.User = contactData.User contactDTO.TeamID = contactData.Team contactDTO.ID = contactData.ID + return contactDTO, nil } // RemoveContact deletes notification contact for current user and remove contactID from all subscriptions. func RemoveContact(database moira.Database, contactID string, userLogin string, teamID string) *api.ErrorResponse { //nolint:gocyclo subscriptionIDs := make([]string, 0) + if userLogin != "" { userSubscriptionIDs, err := database.GetUserSubscriptionIDs(userLogin) if err != nil { return api.ErrorInternalServer(err) } + subscriptionIDs = append(subscriptionIDs, userSubscriptionIDs...) } @@ -142,6 +153,7 @@ func RemoveContact(database moira.Database, contactID string, userLogin string, if err != nil { return api.ErrorInternalServer(err) } + subscriptionIDs = append(subscriptionIDs, teamSubscriptionIDs...) } @@ -156,6 +168,7 @@ func RemoveContact(database moira.Database, contactID string, userLogin string, if subscription == nil { continue } + for i, contact := range subscription.Contacts { if contact == contactID { subscription.Contacts = append(subscription.Contacts[:i], subscription.Contacts[i+1:]...) @@ -171,17 +184,22 @@ func RemoveContact(database moira.Database, contactID string, userLogin string, for subInd, subscription := range subscriptionsWithDeletingContact { errBuffer.WriteString(subscription.ID) errBuffer.WriteString(" (tags: ") + for tagInd := range subscription.Tags { errBuffer.WriteString(subscription.Tags[tagInd]) + if tagInd != len(subscription.Tags)-1 { errBuffer.WriteString(", ") } } + errBuffer.WriteString(")") + if subInd != len(subscriptionsWithDeletingContact)-1 { errBuffer.WriteString(", ") } } + return api.ErrorInvalidRequest(fmt.Errorf(errBuffer.String())) } @@ -205,6 +223,7 @@ func SendTestContactNotification(dataBase moira.Database, contactID string) *api if err := dataBase.PushNotificationEvent(eventData, false); err != nil { return api.ErrorInternalServer(err) } + return nil } @@ -220,23 +239,29 @@ func CheckUserPermissionsForContact( if errors.Is(err, database.ErrNil) { return moira.ContactData{}, api.ErrorNotFound(fmt.Sprintf("contact with ID '%s' does not exists", contactID)) } + return moira.ContactData{}, api.ErrorInternalServer(err) } + if auth.IsAdmin(userLogin) { return contactData, nil } + if contactData.Team != "" { teamContainsUser, err := dataBase.IsTeamContainUser(contactData.Team, userLogin) if err != nil { return moira.ContactData{}, api.ErrorInternalServer(err) } + if teamContainsUser { return contactData, nil } } + if contactData.User == userLogin { return contactData, nil } + return moira.ContactData{}, api.ErrorForbidden("you are not permitted") } @@ -245,9 +270,11 @@ func isContactExists(dataBase moira.Database, contactID string) (bool, error) { if errors.Is(err, database.ErrNil) { return false, nil } + if err != nil { return false, err } + return true, nil } diff --git a/api/controller/contact_events.go b/api/controller/contact_events.go index 2f051d69a..87ae2070b 100644 --- a/api/controller/contact_events.go +++ b/api/controller/contact_events.go @@ -18,6 +18,7 @@ func GetContactEventsByIDWithLimit(database moira.Database, contactID string, fr eventsList := dto.ContactEventItemList{ List: make([]dto.ContactEventItem, 0), } + for _, i := range events { contactEventItem := &dto.ContactEventItem{ TimeStamp: i.TimeStamp, diff --git a/api/controller/events.go b/api/controller/events.go index 218c8ecfe..4a517959c 100644 --- a/api/controller/events.go +++ b/api/controller/events.go @@ -12,6 +12,7 @@ func GetTriggerEvents(database moira.Database, triggerID string, page int64, siz if err != nil { return nil, api.ErrorInternalServer(err) } + eventCount := database.GetNotificationEventCount(triggerID, -1) eventsList := &dto.EventsList{ @@ -20,11 +21,13 @@ func GetTriggerEvents(database moira.Database, triggerID string, page int64, siz Total: eventCount, List: make([]moira.NotificationEvent, 0), } + for _, event := range events { if event != nil { eventsList.List = append(eventsList.List, *event) } } + return eventsList, nil } @@ -33,5 +36,6 @@ func DeleteAllEvents(database moira.Database) *api.ErrorResponse { if err := database.RemoveAllNotificationEvents(); err != nil { return api.ErrorInternalServer(err) } + return nil } diff --git a/api/controller/health.go b/api/controller/health.go index 0452ea649..443d99b91 100644 --- a/api/controller/health.go +++ b/api/controller/health.go @@ -27,5 +27,6 @@ func UpdateNotifierState(database moira.Database, state *dto.NotifierState) *api if err != nil { return api.ErrorInternalServer(err) } + return nil } diff --git a/api/controller/notification.go b/api/controller/notification.go index 2e75fb6e3..f54b95511 100644 --- a/api/controller/notification.go +++ b/api/controller/notification.go @@ -12,10 +12,12 @@ func GetNotifications(database moira.Database, start int64, end int64) (*dto.Not if err != nil { return nil, api.ErrorInternalServer(err) } + notificationsList := dto.NotificationsList{ List: notifications, Total: total, } + return ¬ificationsList, nil } @@ -25,6 +27,7 @@ func DeleteNotification(database moira.Database, notificationKey string) (*dto.N if err != nil { return nil, api.ErrorInternalServer(err) } + return &dto.NotificationDeleteResponse{Result: result}, nil } @@ -33,5 +36,6 @@ func DeleteAllNotifications(database moira.Database) *api.ErrorResponse { if err := database.RemoveAllNotifications(); err != nil { return api.ErrorInternalServer(err) } + return nil } diff --git a/api/controller/pattern.go b/api/controller/pattern.go index 22742624b..5dff3591e 100644 --- a/api/controller/pattern.go +++ b/api/controller/pattern.go @@ -12,6 +12,7 @@ func GetAllPatterns(database moira.Database, logger moira.Logger) (*dto.PatternL if err != nil { return nil, api.ErrorInternalServer(err) } + pattersList := dto.PatternList{ List: make([]dto.PatternData, 0, len(patterns)), } @@ -27,6 +28,7 @@ func GetAllPatterns(database moira.Database, logger moira.Logger) (*dto.PatternL Msg("Failed to get pattern trigger IDs") rch <- nil } + triggers, err := database.GetTriggers(triggerIDs) if err != nil { logger.Error(). @@ -34,6 +36,7 @@ func GetAllPatterns(database moira.Database, logger moira.Logger) (*dto.PatternL Msg("Failed to get trigger") rch <- nil } + metrics, err := database.GetPatternMetrics(pattern) if err != nil { logger.Error(). @@ -41,11 +44,13 @@ func GetAllPatterns(database moira.Database, logger moira.Logger) (*dto.PatternL Msg("Failed to get pattern metrics") rch <- nil } + patternData := dto.PatternData{ Pattern: pattern, Triggers: make([]dto.TriggerModel, 0), Metrics: metrics, } + for _, trigger := range triggers { if trigger != nil { patternData.Triggers = append(patternData.Triggers, dto.CreateTriggerModel(trigger)) @@ -69,5 +74,6 @@ func DeletePattern(database moira.Database, pattern string) *api.ErrorResponse { if err := database.RemovePattern(pattern); err != nil { return api.ErrorInternalServer(err) } + return nil } diff --git a/api/controller/subscription.go b/api/controller/subscription.go index be6754307..6f7b4eae6 100644 --- a/api/controller/subscription.go +++ b/api/controller/subscription.go @@ -20,18 +20,22 @@ func GetUserSubscriptions(database moira.Database, userLogin string) (*dto.Subsc if err != nil { return nil, api.ErrorInternalServer(err) } + subscriptions, err := database.GetSubscriptions(subscriptionIDs) if err != nil { return nil, api.ErrorInternalServer(err) } + subscriptionsList := &dto.SubscriptionList{ List: make([]moira.SubscriptionData, 0), } + for _, subscription := range subscriptions { if subscription != nil { subscriptionsList.List = append(subscriptionsList.List, *subscription) } } + return subscriptionsList, nil } @@ -40,17 +44,20 @@ func CreateSubscription(dataBase moira.Database, auth *api.Authorization, userLo if userLogin != "" && teamID != "" { return api.ErrorInternalServer(fmt.Errorf("CreateSubscription: cannot create subscription when both userLogin and teamID specified")) } + if subscription.ID == "" { uuid4, err := uuid.NewV4() if err != nil { return api.ErrorInternalServer(err) } + subscription.ID = uuid4.String() } else { exists, err := isSubscriptionExists(dataBase, subscription.ID) if err != nil { return api.ErrorInternalServer(err) } + if exists { return api.ErrorInvalidRequest(fmt.Errorf("subscription with this ID already exists")) } @@ -60,11 +67,15 @@ func CreateSubscription(dataBase moira.Database, auth *api.Authorization, userLo if !auth.IsAdmin(userLogin) || subscription.User == "" { subscription.User = userLogin } + subscription.TeamID = teamID + data := moira.SubscriptionData(*subscription) + if err := dataBase.SaveSubscription(&data); err != nil { return api.ErrorInternalServer(err) } + return nil } @@ -74,7 +85,9 @@ func GetSubscription(dataBase moira.Database, subscriptionID string) (*dto.Subsc if err != nil { return nil, api.ErrorInternalServer(err) } + dto := dto.Subscription(subscription) + return &dto, nil } @@ -84,10 +97,13 @@ func UpdateSubscription(dataBase moira.Database, subscriptionID string, userLogi if subscription.TeamID == "" { subscription.User = userLogin } + data := moira.SubscriptionData(*subscription) + if err := dataBase.SaveSubscription(&data); err != nil { return api.ErrorInternalServer(err) } + return nil } @@ -96,6 +112,7 @@ func RemoveSubscription(database moira.Database, subscriptionID string) *api.Err if err := database.RemoveSubscription(subscriptionID); err != nil { return api.ErrorInternalServer(err) } + return nil } @@ -129,23 +146,29 @@ func CheckUserPermissionsForSubscription( if errors.Is(err, database.ErrNil) { return moira.SubscriptionData{}, api.ErrorNotFound(fmt.Sprintf("subscription with ID '%s' does not exists", subscriptionID)) } + return moira.SubscriptionData{}, api.ErrorInternalServer(err) } + if auth.IsAdmin(userLogin) { return subscription, nil } + if subscription.TeamID != "" { teamContainsUser, err := dataBase.IsTeamContainUser(subscription.TeamID, userLogin) if err != nil { return moira.SubscriptionData{}, api.ErrorInternalServer(err) } + if teamContainsUser { return subscription, nil } } + if subscription.User == userLogin { return subscription, nil } + return moira.SubscriptionData{}, api.ErrorForbidden("you are not permitted") } @@ -154,8 +177,10 @@ func isSubscriptionExists(dataBase moira.Database, subscriptionID string) (bool, if errors.Is(err, database.ErrNil) { return false, nil } + if err != nil { return false, err } + return true, nil } diff --git a/api/controller/tag.go b/api/controller/tag.go index 3c950368d..c2382f759 100644 --- a/api/controller/tag.go +++ b/api/controller/tag.go @@ -28,6 +28,7 @@ func GetAllTagsAndSubscriptions(database moira.Database, logger moira.Logger) (* Subscriptions: make([]moira.SubscriptionData, 0), } tagStat.TagName = tagName + subscriptions, err := database.GetTagsSubscriptions([]string{tagName}) if err != nil { logger.Error(). @@ -35,11 +36,13 @@ func GetAllTagsAndSubscriptions(database moira.Database, logger moira.Logger) (* Msg("Failed to get tag's subscriptions") rch <- nil } + for _, subscription := range subscriptions { if subscription != nil { tagStat.Subscriptions = append(tagStat.Subscriptions, *subscription) } } + tagStat.Triggers, err = database.GetTagTriggerIDs(tagName) if err != nil { logger.Error(). @@ -56,6 +59,7 @@ func GetAllTagsAndSubscriptions(database moira.Database, logger moira.Logger) (* tagsStatistics.List = append(tagsStatistics.List, *r) } } + return &tagsStatistics, nil } @@ -78,7 +82,9 @@ func getTagNamesSorted(database moira.Database) ([]string, error) { if err != nil { return nil, err } + sort.SliceStable(tagsNames, func(i, j int) bool { return strings.ToLower(tagsNames[i]) < strings.ToLower(tagsNames[j]) }) + return tagsNames, nil } @@ -116,5 +122,6 @@ func RemoveTag(database moira.Database, tagName string) (*dto.MessageResponse, * if err = database.RemoveTag(tagName); err != nil { return nil, api.ErrorInternalServer(err) } + return &dto.MessageResponse{Message: "tag deleted"}, nil } diff --git a/api/controller/team.go b/api/controller/team.go index bbf16bcfa..1dbc41d4e 100644 --- a/api/controller/team.go +++ b/api/controller/team.go @@ -21,45 +21,52 @@ func CreateTeam(dataBase moira.Database, team dto.TeamModel, userID string) (dto var teamID string if team.ID != "" { // if teamID is specified in request data then check that team with this id is not exist teamID = team.ID + _, err := dataBase.GetTeam(teamID) if err == nil { return dto.SaveTeamResponse{}, api.ErrorInvalidRequest(fmt.Errorf("team with ID you specified already exists %s", teamID)) } + if err != nil && !errors.Is(err, database.ErrNil) { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot check id for team: %w", err)) } } else { // on the other hand try to create an UUID for teamID createdSuccessfully := false + for i := 0; i < teamIDCreateRetries; i++ { // trying three times to create an UUID and check if it exists generatedUUID, err := uuid.NewV4() if err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot generate id for team: %w", err)) } + teamID = generatedUUID.String() + _, err = dataBase.GetTeam(teamID) if errors.Is(err, database.ErrNil) { createdSuccessfully = true break } + if err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot check id for team: %w", err)) } } + if !createdSuccessfully { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot generate unique id for team")) } } - err := dataBase.SaveTeam(teamID, team.ToMoiraTeam()) - if err != nil { + + if err := dataBase.SaveTeam(teamID, team.ToMoiraTeam()); err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot save team: %w", err)) } teamsMap, apiErr := addTeamsForNewUsers(dataBase, teamID, map[string]bool{userID: true}, map[string][]string{}) - if err != nil { + if apiErr != nil { return dto.SaveTeamResponse{}, apiErr } - err = dataBase.SaveTeamsAndUsers(teamID, []string{userID}, teamsMap) + err := dataBase.SaveTeamsAndUsers(teamID, []string{userID}, teamsMap) if err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot save team users: %w", err)) } @@ -74,10 +81,12 @@ func GetTeam(dataBase moira.Database, teamID string) (dto.TeamModel, *api.ErrorR if errors.Is(err, database.ErrNil) { return dto.TeamModel{}, api.ErrorNotFound(fmt.Sprintf("cannot find team: %s", teamID)) } + return dto.TeamModel{}, api.ErrorInternalServer(fmt.Errorf("cannot get team from database: %w", err)) } teamModel := dto.NewTeamModel(team) + return teamModel, nil } @@ -86,6 +95,7 @@ func GetUserTeams(dataBase moira.Database, userID string) (dto.UserTeams, *api.E teams, err := dataBase.GetUserTeams(userID) result := []dto.TeamModel{} + if err != nil && !errors.Is(err, database.ErrNil) { return dto.UserTeams{}, api.ErrorInternalServer(fmt.Errorf("cannot get user teams from database: %w", err)) } @@ -95,6 +105,7 @@ func GetUserTeams(dataBase moira.Database, userID string) (dto.UserTeams, *api.E if err != nil { return dto.UserTeams{}, api.ErrorInternalServer(fmt.Errorf("cannot retrieve team from database: %w", err)) } + teamModel := dto.NewTeamModel(team) result = append(result, teamModel) } @@ -109,24 +120,29 @@ func GetTeamUsers(dataBase moira.Database, teamID string) (dto.TeamMembers, *api if errors.Is(err, database.ErrNil) { return dto.TeamMembers{}, api.ErrorNotFound(fmt.Sprintf("cannot find team users: %s", teamID)) } + return dto.TeamMembers{}, api.ErrorInternalServer(fmt.Errorf("cannot get team users from database: %w", err)) } result := dto.TeamMembers{ Usernames: users, } + return result, nil } func fillCurrentUsersTeamsMap(dataBase moira.Database, existingUsers []string) (map[string][]string, *api.ErrorResponse) { result := map[string][]string{} + for _, userID := range existingUsers { fetchedUserTeams, err := dataBase.GetUserTeams(userID) if err != nil { return nil, api.ErrorInternalServer(fmt.Errorf("cannot get team users from database: %w", err)) } + result[userID] = fetchedUserTeams } + return result, nil } @@ -135,12 +151,15 @@ func removeDeletedUsers(teamID string, existingUsers []string, newUsers map[stri if newUsers[userID] { continue } + userRemovedTeams, err := removeUserTeam(teamsMap[userID], teamID) if err != nil { return nil, api.ErrorInternalServer(fmt.Errorf("cannot remove team from user: %w", err)) } + teamsMap[userID] = userRemovedTeams } + return teamsMap, nil } @@ -150,13 +169,16 @@ func addTeamsForNewUsers(dataBase moira.Database, teamID string, newUsers map[st if _, ok := teamsMap[userID]; ok { continue } + fetchedUserTeams, err := dataBase.GetUserTeams(userID) if err != nil && !errors.Is(err, database.ErrNil) { return nil, api.ErrorInternalServer(fmt.Errorf("cannot get team users from database: %w", err)) } + fetchedUserTeams = append(fetchedUserTeams, teamID) teamsMap[userID] = fetchedUserTeams } + return teamsMap, nil } @@ -167,6 +189,7 @@ func SetTeamUsers(dataBase moira.Database, teamID string, allUsers []string) (dt if errors.Is(err, database.ErrNil) { return dto.TeamMembers{}, api.ErrorNotFound(fmt.Sprintf("cannot find team users: %s", teamID)) } + return dto.TeamMembers{}, api.ErrorInternalServer(fmt.Errorf("cannot get team users from database: %w", err)) } /* @@ -223,6 +246,7 @@ func SetTeamUsers(dataBase moira.Database, teamID string, allUsers []string) (dt result := dto.TeamMembers{ Usernames: allUsers, } + return result, nil } @@ -232,7 +256,9 @@ func addUserTeam(teamID string, teams []string) ([]string, error) { return []string{}, fmt.Errorf("team already exist in user teams: %s", teamID) } } + teams = append(teams, teamID) + return teams, nil } @@ -243,6 +269,7 @@ func AddTeamUsers(dataBase moira.Database, teamID string, newUsers []string) (dt if errors.Is(err, database.ErrNil) { return dto.TeamMembers{}, api.ErrorNotFound(fmt.Sprintf("cannot find team users: %s", teamID)) } + return dto.TeamMembers{}, api.ErrorInternalServer(fmt.Errorf("cannot get team users from database: %w", err)) } @@ -255,8 +282,10 @@ func AddTeamUsers(dataBase moira.Database, teamID string, newUsers []string) (dt if errors.Is(err, database.ErrNil) { return dto.TeamMembers{}, api.ErrorNotFound(fmt.Sprintf("cannot find user teams: %s", userID)) } + return dto.TeamMembers{}, api.ErrorInternalServer(fmt.Errorf("cannot get user teams from database: %w", err)) } + teamsMap[userID] = userTeams finalUsers = append(finalUsers, userID) } @@ -288,6 +317,7 @@ func AddTeamUsers(dataBase moira.Database, teamID string, newUsers []string) (dt result := dto.TeamMembers{ Usernames: finalUsers, } + return result, nil } @@ -297,6 +327,7 @@ func UpdateTeam(dataBase moira.Database, teamID string, team dto.TeamModel) (dto if err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot save team: %w", err)) } + return dto.SaveTeamResponse{ID: teamID}, nil } @@ -306,27 +337,34 @@ func DeleteTeam(dataBase moira.Database, teamID, userLogin string) (dto.SaveTeam if err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot get team users: %w", err)) } + if len(teamUsers) > 1 { return dto.SaveTeamResponse{}, api.ErrorInvalidRequest(fmt.Errorf("cannot delete team: team have users: %s", strings.Join(teamUsers, ", "))) } + teamContacts, err := dataBase.GetTeamContactIDs(teamID) if err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot get team contacts: %w", err)) } + if len(teamContacts) > 0 { return dto.SaveTeamResponse{}, api.ErrorInvalidRequest(fmt.Errorf("cannot delete team: team have contacts: %s", strings.Join(teamContacts, ", "))) } + teamSubscriptions, err := dataBase.GetTeamSubscriptionIDs(teamID) if err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot get team subscriptions: %w", err)) } + if len(teamSubscriptions) > 0 { return dto.SaveTeamResponse{}, api.ErrorInvalidRequest(fmt.Errorf("cannot delete team: team have subscriptions: %s", strings.Join(teamSubscriptions, ", "))) } + err = dataBase.DeleteTeam(teamID, userLogin) if err != nil { return dto.SaveTeamResponse{}, api.ErrorInternalServer(fmt.Errorf("cannot delete team: %w", err)) } + return dto.SaveTeamResponse{ID: teamID}, nil } @@ -337,6 +375,7 @@ func DeleteTeamUser(dataBase moira.Database, teamID string, removeUserID string) if errors.Is(err, database.ErrNil) { return dto.TeamMembers{}, api.ErrorNotFound(fmt.Sprintf("cannot find team users: %s", teamID)) } + return dto.TeamMembers{}, api.ErrorInternalServer(fmt.Errorf("cannot get team users from database: %w", err)) } @@ -345,11 +384,13 @@ func DeleteTeamUser(dataBase moira.Database, teamID string, removeUserID string) } userFound := false + for _, userID := range existingUsers { if userID == removeUserID { userFound = true } } + if !userFound { return dto.TeamMembers{}, api.ErrorNotFound(fmt.Sprintf("user that you specified not found in this team: %s", removeUserID)) } @@ -363,8 +404,10 @@ func DeleteTeamUser(dataBase moira.Database, teamID string, removeUserID string) if errors.Is(err, database.ErrNil) { return dto.TeamMembers{}, api.ErrorNotFound(fmt.Sprintf("cannot find user teams: %s", userID)) } + return dto.TeamMembers{}, api.ErrorInternalServer(fmt.Errorf("cannot get user teams from database: %w", err)) } + if userID == removeUserID { userTeams, err = removeUserTeam(userTeams, teamID) if err != nil { @@ -373,6 +416,7 @@ func DeleteTeamUser(dataBase moira.Database, teamID string, removeUserID string) } else { finalUsers = append(finalUsers, userID) } + teamsMap[userID] = userTeams } @@ -384,17 +428,20 @@ func DeleteTeamUser(dataBase moira.Database, teamID string, removeUserID string) result := dto.TeamMembers{ Usernames: finalUsers, } + return result, nil } func removeUserTeam(teams []string, teamID string) ([]string, error) { for i, currentTeamID := range teams { if teamID == currentTeamID { - teams[i] = teams[len(teams)-1] // Copy last element to index i. - teams[len(teams)-1] = "" // Erase last element (write zero value). + teams[i] = teams[len(teams)-1] // Copy last element to index i. + teams[len(teams)-1] = "" // Erase last element (write zero value). + return teams[:len(teams)-1], nil // Truncate slice. } } + return []string{}, fmt.Errorf("cannot find team in user teams: %s", teamID) } @@ -406,11 +453,13 @@ func CheckUserPermissionsForTeam( if auth.IsAdmin(userID) { return nil } + _, err := dataBase.GetTeam(teamID) if err != nil { if errors.Is(err, database.ErrNil) { return api.ErrorNotFound(fmt.Sprintf("team with ID '%s' does not exists", teamID)) } + return api.ErrorInternalServer(err) } @@ -418,9 +467,11 @@ func CheckUserPermissionsForTeam( if err != nil { return api.ErrorInternalServer(err) } + if !userIsTeamMember { return api.ErrorForbidden("you are not permitted to manipulate with this team") } + return nil } @@ -441,11 +492,13 @@ func GetTeamSettings(dataBase moira.Database, teamID string) (dto.TeamSettings, if err != nil { return dto.TeamSettings{}, api.ErrorInternalServer(err) } + for _, subscription := range subscriptions { if subscription != nil { teamSettings.Subscriptions = append(teamSettings.Subscriptions, *subscription) } } + contactIDs, err := dataBase.GetTeamContactIDs(teamID) if err != nil { return dto.TeamSettings{}, api.ErrorInternalServer(err) @@ -455,10 +508,12 @@ func GetTeamSettings(dataBase moira.Database, teamID string) (dto.TeamSettings, if err != nil { return dto.TeamSettings{}, api.ErrorInternalServer(err) } + for _, contact := range contacts { if contact != nil { teamSettings.Contacts = append(teamSettings.Contacts, *contact) } } + return teamSettings, nil } diff --git a/api/controller/trigger.go b/api/controller/trigger.go index c523126de..284c17b35 100644 --- a/api/controller/trigger.go +++ b/api/controller/trigger.go @@ -21,8 +21,10 @@ func UpdateTrigger(dataBase moira.Database, trigger *dto.TriggerModel, triggerID if errors.Is(err, database.ErrNil) { return nil, api.ErrorNotFound(fmt.Sprintf("trigger with ID = '%s' does not exists", triggerID)) } + return nil, api.ErrorInternalServer(err) } + return saveTrigger(dataBase, trigger.ToMoiraTrigger(), triggerID, timeSeriesNames) } @@ -33,6 +35,7 @@ func saveTrigger(dataBase moira.Database, trigger *moira.Trigger, triggerID stri } defer dataBase.DeleteTriggerCheckLock(triggerID) //nolint lastCheck, err := dataBase.GetTriggerLastCheck(triggerID) + if err != nil && !errors.Is(err, database.ErrNil) { return nil, api.ErrorInternalServer(err) } @@ -43,16 +46,19 @@ func saveTrigger(dataBase moira.Database, trigger *moira.Trigger, triggerID stri lastCheck.RemoveMetricState(metric) } } + lastCheck.RemoveMetricsToTargetRelation() } else { triggerState := moira.StateNODATA if trigger.TTLState != nil { triggerState = trigger.TTLState.ToTriggerState() } + lastCheck = moira.CheckData{ Metrics: make(map[string]moira.MetricState), State: triggerState, } + lastCheck.UpdateScore() } @@ -68,6 +74,7 @@ func saveTrigger(dataBase moira.Database, trigger *moira.Trigger, triggerID stri ID: triggerID, Message: "trigger updated", } + return &resp, nil } @@ -78,8 +85,10 @@ func GetTrigger(dataBase moira.Database, triggerID string) (*dto.Trigger, *api.E if errors.Is(err, database.ErrNil) { return nil, api.ErrorNotFound("trigger not found") } + return nil, api.ErrorInternalServer(err) } + throttling, _ := dataBase.GetTriggerThrottling(triggerID) throttlingUnix := throttling.Unix() @@ -100,6 +109,7 @@ func RemoveTrigger(database moira.Database, triggerID string) *api.ErrorResponse if err := database.RemoveTrigger(triggerID); err != nil { return api.ErrorInternalServer(err) } + return nil } @@ -107,15 +117,18 @@ func RemoveTrigger(database moira.Database, triggerID string) *api.ErrorResponse func GetTriggerThrottling(database moira.Database, triggerID string) (*dto.ThrottlingResponse, *api.ErrorResponse) { throttling, _ := database.GetTriggerThrottling(triggerID) throttlingUnix := throttling.Unix() + if throttlingUnix < time.Now().Unix() { throttlingUnix = 0 } + return &dto.ThrottlingResponse{Throttling: throttlingUnix}, nil } // GetTriggerLastCheck gets trigger last check data. func GetTriggerLastCheck(dataBase moira.Database, triggerID string) (*dto.TriggerCheck, *api.ErrorResponse) { lastCheck := &moira.CheckData{} + var err error *lastCheck, err = dataBase.GetTriggerLastCheck(triggerID) @@ -123,6 +136,7 @@ func GetTriggerLastCheck(dataBase moira.Database, triggerID string) (*dto.Trigge if !errors.Is(err, database.ErrNil) { return nil, api.ErrorInternalServer(err) } + lastCheck = nil } @@ -145,19 +159,24 @@ func DeleteTriggerThrottling(database moira.Database, triggerID string) *api.Err } now := time.Now().Unix() + notifications, _, err := database.GetNotifications(0, -1) if err != nil { return api.ErrorInternalServer(err) } + notificationsForRewrite := make([]*moira.ScheduledNotification, 0) + for _, notification := range notifications { if notification != nil && notification.Event.TriggerID == triggerID { notificationsForRewrite = append(notificationsForRewrite, notification) } } + if err = database.AddNotifications(notificationsForRewrite, now); err != nil { return api.ErrorInternalServer(err) } + return nil } @@ -167,9 +186,11 @@ func SetTriggerMaintenance(database moira.Database, triggerID string, triggerMai return api.ErrorInternalServer(err) } defer database.ReleaseTriggerCheckLock(triggerID) + if err := database.SetTriggerCheckMaintenance(triggerID, triggerMaintenance.Metrics, triggerMaintenance.Trigger, userLogin, timeCallMaintenance); err != nil { return api.ErrorInternalServer(err) } + return nil } diff --git a/api/controller/trigger_metrics.go b/api/controller/trigger_metrics.go index 06ba2e517..598a058a3 100644 --- a/api/controller/trigger_metrics.go +++ b/api/controller/trigger_metrics.go @@ -18,22 +18,28 @@ func GetTriggerEvaluationResult(dataBase moira.Database, metricSourceProvider *m if err != nil { return nil, nil, err } + triggerMetrics := make(map[string][]metricSource.MetricData) + metricsSource, err := metricSourceProvider.GetTriggerMetricSource(&trigger) if err != nil { return nil, &trigger, err } + for i, target := range trigger.Targets { i++ // Increase counter to have trigger names start from t1 + fetchResult, err := metricsSource.Fetch(target, from, to, fetchRealtimeData) if err != nil { return nil, &trigger, err } + metricData := fetchResult.GetMetricsData() targetName := fmt.Sprintf("t%d", i) triggerMetrics[targetName] = metricData } + return triggerMetrics, &trigger, nil } @@ -55,25 +61,33 @@ func GetTriggerMetrics(dataBase moira.Database, metricSourceProvider *metricSour if errors.Is(err, database.ErrNil) { return nil, api.ErrorInvalidRequest(fmt.Errorf("trigger not found")) } + return nil, api.ErrorInternalServer(err) } + triggerMetrics := make(dto.TriggerMetrics) for targetName, target := range tts { targetMetrics := make(map[string][]moira.MetricValue) + for _, timeSeries := range target { values := make([]moira.MetricValue, 0) + for i, l := 0, len(timeSeries.Values); i < l; i++ { timestamp := timeSeries.StartTime + int64(i)*timeSeries.StepTime + value := timeSeries.GetTimestampValue(timestamp) if moira.IsFiniteNumber(value) { values = append(values, moira.MetricValue{Value: value, Timestamp: timestamp}) } } + targetMetrics[timeSeries.Name] = values } + triggerMetrics[targetName] = targetMetrics } + return &triggerMetrics, nil } @@ -83,6 +97,7 @@ func deleteTriggerMetrics(dataBase moira.Database, metricName string, triggerID if errors.Is(err, database.ErrNil) { return api.ErrorInvalidRequest(fmt.Errorf("trigger not found")) } + return api.ErrorInternalServer(err) } @@ -96,8 +111,10 @@ func deleteTriggerMetrics(dataBase moira.Database, metricName string, triggerID if errors.Is(err, database.ErrNil) { return api.ErrorInvalidRequest(fmt.Errorf("trigger check not found")) } + return api.ErrorInternalServer(err) } + if removeAllNodataMetrics { for metricName, metricState := range lastCheck.Metrics { if metricState.State == moira.StateNODATA { @@ -112,6 +129,7 @@ func deleteTriggerMetrics(dataBase moira.Database, metricName string, triggerID } lastCheck.UpdateScore() + if err = dataBase.RemovePatternsMetrics(trigger.Patterns); err != nil { return api.ErrorInternalServer(err) } diff --git a/api/controller/triggers.go b/api/controller/triggers.go index 4c6ec7075..575c420f8 100644 --- a/api/controller/triggers.go +++ b/api/controller/triggers.go @@ -25,23 +25,28 @@ func CreateTrigger(dataBase moira.Database, trigger *dto.TriggerModel, timeSerie if err != nil { return nil, api.ErrorInternalServer(err) } + trigger.ID = uuid4.String() } else { if !idValidationPattern.MatchString(trigger.ID) { return nil, api.ErrorInvalidRequest(fmt.Errorf("trigger ID contains invalid characters (allowed: 0-9, a-z, A-Z, -, ~, _, .)")) } + exists, err := triggerExists(dataBase, trigger.ID) if err != nil { return nil, api.ErrorInternalServer(err) } + if exists { return nil, api.ErrorInvalidRequest(fmt.Errorf("trigger with this ID already exists")) } } + resp, err := saveTrigger(dataBase, trigger.ToMoiraTrigger(), trigger.ID, timeSeriesNames) if resp != nil { resp.Message = "trigger created" } + return resp, err } @@ -56,6 +61,7 @@ func GetAllTriggers(database moira.Database) (*dto.TriggersList, *api.ErrorRespo if err != nil { return nil, api.ErrorInternalServer(err) } + triggersList := &dto.TriggersList{ List: triggerChecks, } @@ -65,27 +71,35 @@ func GetAllTriggers(database moira.Database) (*dto.TriggersList, *api.ErrorRespo // SearchTriggers gets trigger page and filter trigger by tags and search request terms. func SearchTriggers(database moira.Database, searcher moira.Searcher, options moira.SearchOptions) (*dto.TriggersList, *api.ErrorResponse) { //nolint - var searchResults []*moira.SearchResult - var total int64 + var ( + searchResults []*moira.SearchResult + total int64 + ) + pagerShouldExist := options.PagerID != "" if pagerShouldExist && (options.SearchString != "" || len(options.Tags) > 0) { return nil, api.ErrorInvalidRequest(fmt.Errorf("cannot handle request with search string or tags and pager ID set")) } + if pagerShouldExist { var err error + searchResults, total, err = database.GetTriggersSearchResults(options.PagerID, options.Page, options.Size) if err != nil { return nil, api.ErrorInternalServer(err) } + if searchResults == nil { return nil, api.ErrorNotFound("Pager not found") } } else { var err error + if options.CreatePager { options.Size = pageSizeUnlimited } + searchResults, total, err = searcher.SearchTriggers(options) if err != nil { return nil, api.ErrorInternalServer(err) @@ -97,7 +111,9 @@ func SearchTriggers(database moira.Database, searcher moira.Searcher, options mo if err != nil { return nil, api.ErrorInternalServer(err) } + options.PagerID = uuid4.String() + err = database.SaveTriggersSearchResults(options.PagerID, searchResults) if err != nil { return nil, api.ErrorInternalServer(err) @@ -110,6 +126,7 @@ func SearchTriggers(database moira.Database, searcher moira.Searcher, options mo from = int64(math.Min(float64(options.Page*options.Size), float64(len(searchResults)))) to = int64(math.Min(float64(from+options.Size), float64(len(searchResults)))) } + searchResults = searchResults[from:to] } @@ -159,13 +176,16 @@ func DeleteTriggersPager(database moira.Database, pagerID string) (dto.TriggersS if err != nil { return dto.TriggersSearchResultDeleteResponse{}, api.ErrorInternalServer(err) } + if !exists { return dto.TriggersSearchResultDeleteResponse{}, api.ErrorNotFound(fmt.Sprintf("pager with id %s not found", pagerID)) } + err = database.DeleteTriggersSearchResults(pagerID) if err != nil { return dto.TriggersSearchResultDeleteResponse{}, api.ErrorInternalServer(err) } + return dto.TriggersSearchResultDeleteResponse{PagerID: pagerID}, nil } @@ -180,6 +200,7 @@ func GetUnusedTriggerIDs(database moira.Database) (*dto.TriggersList, *api.Error if err != nil { return nil, api.ErrorInternalServer(err) } + triggersList := &dto.TriggersList{ List: triggerChecks, } @@ -192,7 +213,9 @@ func getTriggerChecks(database moira.Database, triggerIDs []string) ([]moira.Tri if err != nil { return nil, err } + list := make([]moira.TriggerCheck, 0, len(triggerChecks)) + for _, triggerCheck := range triggerChecks { if triggerCheck != nil { triggerCheck.LastCheck.RemoveDeadMetrics() @@ -208,8 +231,10 @@ func triggerExists(database moira.Database, triggerID string) (bool, error) { if errors.Is(err, db.ErrNil) { return false, nil } + if err != nil { return false, err } + return true, nil } diff --git a/api/controller/user.go b/api/controller/user.go index 0b3d969d6..d46dfd163 100644 --- a/api/controller/user.go +++ b/api/controller/user.go @@ -27,11 +27,13 @@ func GetUserSettings(database moira.Database, userLogin string, auth *api.Author if err != nil { return nil, api.ErrorInternalServer(err) } + for _, subscription := range subscriptions { if subscription != nil { userSettings.Subscriptions = append(userSettings.Subscriptions, *subscription) } } + contactIDs, err := database.GetUserContactIDs(userLogin) if err != nil { return nil, api.ErrorInternalServer(err) @@ -41,10 +43,12 @@ func GetUserSettings(database moira.Database, userLogin string, auth *api.Author if err != nil { return nil, api.ErrorInternalServer(err) } + for _, contact := range contacts { if contact != nil { userSettings.Contacts = append(userSettings.Contacts, *contact) } } + return userSettings, nil } diff --git a/api/dto/contact.go b/api/dto/contact.go index 84ffe2f57..3b4f07fa9 100644 --- a/api/dto/contact.go +++ b/api/dto/contact.go @@ -33,8 +33,10 @@ func (contact *Contact) Bind(r *http.Request) error { if contact.Type == "" { return fmt.Errorf("contact type can not be empty") } + if contact.Value == "" { return fmt.Errorf("contact value of type %s can not be empty", contact.Type) } + return nil } diff --git a/api/dto/target.go b/api/dto/target.go index 0d877185e..3605fe63f 100644 --- a/api/dto/target.go +++ b/api/dto/target.go @@ -152,12 +152,16 @@ func graphiteTargetVerification(targets []string, ttl time.Duration, triggerSour if err != nil { functionsOfTarget.SyntaxOk = false functionsOfTargets = append(functionsOfTargets, functionsOfTarget) + continue } + isSpaceInMetricName := nestedExpr != "" + if isSpaceInMetricName { functionsOfTarget.SyntaxOk = false functionsOfTargets = append(functionsOfTargets, functionsOfTarget) + continue } @@ -237,6 +241,7 @@ func checkFunction(funcName string, triggerSource moira.TriggerSource) *ProblemO Description: "Incorrect seriesByTag syntax.", } } + if !valid { return &ProblemOfTarget{ Argument: funcName, @@ -244,6 +249,7 @@ func checkFunction(funcName string, triggerSource moira.TriggerSource) *ProblemO Description: "seriesByTag function requires at least one argument with strict equality.", } } + funcName = "seriesByTag" } @@ -320,8 +326,10 @@ func hasWildcard(target string) bool { } func positiveDuration(argument parser.Expr) (string, time.Duration) { - var secondTimeDuration time.Duration - var value string + var ( + secondTimeDuration time.Duration + value string + ) switch argument.Type() { case parser.EtConst: diff --git a/api/dto/team.go b/api/dto/team.go index 14e5d8546..b92b86277 100644 --- a/api/dto/team.go +++ b/api/dto/team.go @@ -34,12 +34,15 @@ func (t TeamModel) Bind(*http.Request) error { if t.Name == "" { return fmt.Errorf("team name cannot be empty") } + if utf8.RuneCountInString(t.Name) > teamNameLimit { return fmt.Errorf("team name cannot be longer than %d characters", teamNameLimit) } + if utf8.RuneCountInString(t.Description) > teamDescriptionLimit { return fmt.Errorf("team description cannot be longer than %d characters", teamNameLimit) } + return nil } @@ -87,6 +90,7 @@ func (m TeamMembers) Bind(*http.Request) error { if len(m.Usernames) == 0 { return fmt.Errorf("at least one user should be specified") } + return nil } diff --git a/api/handler/config.go b/api/handler/config.go index 780d3509a..9875faf61 100644 --- a/api/handler/config.go +++ b/api/handler/config.go @@ -17,10 +17,10 @@ import ( // @failure 422 {object} api.ErrorRenderExample "Render error" // @router /config [get] func getWebConfig(webConfig *api.WebConfig) http.HandlerFunc { - return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + return func(writer http.ResponseWriter, request *http.Request) { if err := render.Render(writer, request, webConfig); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint return } - }) + } } diff --git a/api/handler/contact.go b/api/handler/contact.go index 82af5a548..462f1fef6 100644 --- a/api/handler/contact.go +++ b/api/handler/contact.go @@ -119,11 +119,13 @@ func contactFilter(next http.Handler) http.Handler { contactID := middleware.GetContactID(request) userLogin := middleware.GetLogin(request) auth := middleware.GetAuth(request) + contactData, err := controller.CheckUserPermissionsForContact(database, contactID, userLogin, auth) if err != nil { render.Render(writer, request, err) //nolint return } + ctx := context.WithValue(request.Context(), contactKey, contactData) next.ServeHTTP(writer, request.WithContext(ctx)) }) @@ -183,6 +185,7 @@ func updateContact(writer http.ResponseWriter, request *http.Request) { // @router /contact/{contactID} [delete] func removeContact(writer http.ResponseWriter, request *http.Request) { contactData := request.Context().Value(contactKey).(moira.ContactData) + err := controller.RemoveContact(database, contactData.ID, contactData.User, contactData.Team) if err != nil { render.Render(writer, request, err) //nolint @@ -204,6 +207,7 @@ func removeContact(writer http.ResponseWriter, request *http.Request) { // @tags contact func sendTestContactNotification(writer http.ResponseWriter, request *http.Request) { contactID := middleware.GetContactID(request) + err := controller.SendTestContactNotification(database, contactID) if err != nil { render.Render(writer, request, err) //nolint diff --git a/api/handler/contact_events.go b/api/handler/contact_events.go index 6325e0a5c..38b3d1d37 100644 --- a/api/handler/contact_events.go +++ b/api/handler/contact_events.go @@ -44,20 +44,24 @@ func getContactByIDWithEvents(writer http.ResponseWriter, request *http.Request) contactData := request.Context().Value(contactKey).(moira.ContactData) fromStr := middleware.GetFromStr(request) toStr := middleware.GetToStr(request) + from := date.DateParamToEpoch(fromStr, "UTC", 0, time.UTC) if from == 0 { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("can not parse from: %s", fromStr))) //nolint return } + to := date.DateParamToEpoch(toStr, "UTC", 0, time.UTC) if to == 0 { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("can not parse to: %v", to))) //nolint return } + contactWithEvents, err := controller.GetContactEventsByIDWithLimit(database, contactData.ID, from, to) if err != nil { render.Render(writer, request, err) //nolint } + if err := render.Render(writer, request, contactWithEvents); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint return diff --git a/api/handler/event.go b/api/handler/event.go index c5d8602d5..c6c00b6b1 100644 --- a/api/handler/event.go +++ b/api/handler/event.go @@ -34,11 +34,13 @@ func getEventsList(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) size := middleware.GetSize(request) page := middleware.GetPage(request) + eventsList, err := controller.GetTriggerEvents(database, triggerID, page, size) if err != nil { render.Render(writer, request, err) //nolint return } + if err := render.Render(writer, request, eventsList); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint } diff --git a/api/handler/handler.go b/api/handler/handler.go index 27647a8c3..37f8423f2 100644 --- a/api/handler/handler.go +++ b/api/handler/handler.go @@ -124,6 +124,7 @@ func NewHandler( if apiConfig.EnableCORS { return cors.AllowAll().Handler(router) } + return router } diff --git a/api/handler/pattern.go b/api/handler/pattern.go index 149ccfe0d..910a71bb6 100644 --- a/api/handler/pattern.go +++ b/api/handler/pattern.go @@ -31,11 +31,13 @@ func pattern(router chi.Router) { // @router /pattern [get] func getAllPatterns(writer http.ResponseWriter, request *http.Request) { logger := middleware.GetLoggerEntry(request) + patternsList, err := controller.GetAllPatterns(database, logger) if err != nil { render.Render(writer, request, err) //nolint return } + if err := render.Render(writer, request, patternsList); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint } @@ -59,6 +61,7 @@ func deletePattern(writer http.ResponseWriter, request *http.Request) { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("pattern must be set"))) //nolint return } + err := controller.DeletePattern(database, pattern) if err != nil { render.Render(writer, request, err) //nolint diff --git a/api/handler/subscription.go b/api/handler/subscription.go index efca95735..4c256b7ad 100644 --- a/api/handler/subscription.go +++ b/api/handler/subscription.go @@ -39,6 +39,7 @@ func subscription(router chi.Router) { // @router /subscription [get] func getUserSubscriptions(writer http.ResponseWriter, request *http.Request) { userLogin := middleware.GetLogin(request) + subscriptions, err := controller.GetUserSubscriptions(database, userLogin) if err != nil { render.Render(writer, request, err) //nolint @@ -70,6 +71,7 @@ func createSubscription(writer http.ResponseWriter, request *http.Request) { render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint return } + userLogin := middleware.GetLogin(request) auth := middleware.GetAuth(request) @@ -77,12 +79,15 @@ func createSubscription(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(http.StatusBadRequest) render.Render(writer, request, api.ErrorInvalidRequest( //nolint errors.New("if any_tags is true, then the tags must be empty"))) + return } + if err := controller.CreateSubscription(database, auth, userLogin, "", subscription); err != nil { render.Render(writer, request, err) //nolint return } + if err := render.Render(writer, request, subscription); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint return @@ -95,11 +100,13 @@ func subscriptionFilter(next http.Handler) http.Handler { subscriptionID := middleware.GetSubscriptionID(request) userLogin := middleware.GetLogin(request) auth := middleware.GetAuth(request) + subscriptionData, err := controller.CheckUserPermissionsForSubscription(database, subscriptionID, userLogin, auth) if err != nil { render.Render(writer, request, err) //nolint return } + ctx := context.WithValue(request.Context(), subscriptionKey, subscriptionData) next.ServeHTTP(writer, request.WithContext(ctx)) }) @@ -119,11 +126,13 @@ func subscriptionFilter(next http.Handler) http.Handler { // @router /subscription/{subscriptionID} [get] func getSubscription(writer http.ResponseWriter, request *http.Request) { subscriptionID := middleware.GetSubscriptionID(request) + subscription, err := controller.GetSubscription(database, subscriptionID) if err != nil { render.Render(writer, request, err) //nolint return } + if err := render.Render(writer, request, subscription); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint return @@ -155,6 +164,7 @@ func updateSubscription(writer http.ResponseWriter, request *http.Request) { default: render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint } + return } @@ -162,6 +172,7 @@ func updateSubscription(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(http.StatusBadRequest) render.Render(writer, request, api.ErrorInvalidRequest( //nolint errors.New("if any_tags is true, then the tags must be empty"))) + return } @@ -169,10 +180,13 @@ func updateSubscription(writer http.ResponseWriter, request *http.Request) { if err := controller.UpdateSubscription(database, subscriptionData.ID, subscriptionData.User, subscription); err != nil { render.Render(writer, request, err) //nolint + return } + if err := render.Render(writer, request, subscription); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint + return } } diff --git a/api/handler/tag.go b/api/handler/tag.go index a2703cfb0..a1bce9532 100644 --- a/api/handler/tag.go +++ b/api/handler/tag.go @@ -82,11 +82,13 @@ func createTags(writer http.ResponseWriter, request *http.Request) { // @router /tag/stats [get] func getAllTagsAndSubscriptions(writer http.ResponseWriter, request *http.Request) { logger := middleware.GetLoggerEntry(request) + data, err := controller.GetAllTagsAndSubscriptions(database, logger) if err != nil { render.Render(writer, request, err) //nolint return } + if err := render.Render(writer, request, data); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint return @@ -107,11 +109,13 @@ func getAllTagsAndSubscriptions(writer http.ResponseWriter, request *http.Reques // @router /tag/{tag} [delete] func removeTag(writer http.ResponseWriter, request *http.Request) { tagName := middleware.GetTag(request) + response, err := controller.RemoveTag(database, tagName) if err != nil { render.Render(writer, request, err) //nolint return } + if err := render.Render(writer, request, response); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint return diff --git a/api/handler/team.go b/api/handler/team.go index 294c09817..e175ad8a6 100644 --- a/api/handler/team.go +++ b/api/handler/team.go @@ -38,11 +38,13 @@ func usersFilterForTeams(next http.Handler) http.Handler { userLogin := middleware.GetLogin(request) teamID := middleware.GetTeamID(request) auth := middleware.GetAuth(request) + err := controller.CheckUserPermissionsForTeam(database, teamID, userLogin, auth) if err != nil { render.Render(writer, request, err) //nolint return } + next.ServeHTTP(writer, request) }) } @@ -63,16 +65,19 @@ func usersFilterForTeams(next http.Handler) http.Handler { func createTeam(writer http.ResponseWriter, request *http.Request) { user := middleware.GetLogin(request) team := dto.TeamModel{} + err := render.Bind(request, &team) if err != nil { render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint:errcheck return } + response, apiErr := controller.CreateTeam(database, team, user) if apiErr != nil { render.Render(writer, request, apiErr) //nolint:errcheck return } + if err := render.Render(writer, request, response); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint:errcheck return @@ -91,6 +96,7 @@ func createTeam(writer http.ResponseWriter, request *http.Request) { // @router /teams [get] func getAllTeams(writer http.ResponseWriter, request *http.Request) { user := middleware.GetLogin(request) + response, err := controller.GetUserTeams(database, user) if err != nil { render.Render(writer, request, err) //nolint:errcheck @@ -149,6 +155,7 @@ func getTeam(writer http.ResponseWriter, request *http.Request) { // @router /teams/{teamID} [patch] func updateTeam(writer http.ResponseWriter, request *http.Request) { team := dto.TeamModel{} + err := render.Bind(request, &team) if err != nil { render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint:errcheck @@ -162,6 +169,7 @@ func updateTeam(writer http.ResponseWriter, request *http.Request) { render.Render(writer, request, apiErr) //nolint:errcheck return } + if err := render.Render(writer, request, response); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint:errcheck return @@ -191,6 +199,7 @@ func deleteTeam(writer http.ResponseWriter, request *http.Request) { render.Render(writer, request, apiErr) //nolint:errcheck return } + if err := render.Render(writer, request, response); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint:errcheck return @@ -243,6 +252,7 @@ func getTeamUsers(writer http.ResponseWriter, request *http.Request) { // @router /teams/{teamID}/users [put] func setTeamUsers(writer http.ResponseWriter, request *http.Request) { members := dto.TeamMembers{} + err := render.Bind(request, &members) if err != nil { render.Render(writer, request, api.ErrorInvalidRequest(err)) // nolint:errcheck @@ -281,11 +291,13 @@ func setTeamUsers(writer http.ResponseWriter, request *http.Request) { // @router /teams/{teamID}/users [post] func addTeamUsers(writer http.ResponseWriter, request *http.Request) { members := dto.TeamMembers{} + err := render.Bind(request, &members) if err != nil { render.Render(writer, request, api.ErrorInvalidRequest(err)) // nolint:errcheck return } + teamID := middleware.GetTeamID(request) response, apiErr := controller.AddTeamUsers(database, teamID, members.Usernames) @@ -346,6 +358,7 @@ func deleteTeamUser(writer http.ResponseWriter, request *http.Request) { // @router /teams/{teamID}/settings [get] func getTeamSettings(writer http.ResponseWriter, request *http.Request) { teamID := middleware.GetTeamID(request) + teamSettings, err := controller.GetTeamSettings(database, teamID) if err != nil { render.Render(writer, request, err) //nolint:errcheck diff --git a/api/handler/team_subscription.go b/api/handler/team_subscription.go index 99f6c6666..a616ea04b 100644 --- a/api/handler/team_subscription.go +++ b/api/handler/team_subscription.go @@ -38,6 +38,7 @@ func createTeamSubscription(writer http.ResponseWriter, request *http.Request) { render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint:errcheck return } + teamID := middleware.GetTeamID(request) auth := middleware.GetAuth(request) @@ -45,12 +46,15 @@ func createTeamSubscription(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(http.StatusBadRequest) render.Render(writer, request, api.ErrorInvalidRequest( //nolint:errcheck errors.New("if any_tags is true, then the tags must be empty"))) + return } + if err := controller.CreateSubscription(database, auth, "", teamID, subscription); err != nil { render.Render(writer, request, err) //nolint:errcheck return } + if err := render.Render(writer, request, subscription); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint:errcheck return diff --git a/api/handler/trigger.go b/api/handler/trigger.go index d17b09cc9..2490979b8 100644 --- a/api/handler/trigger.go +++ b/api/handler/trigger.go @@ -70,6 +70,7 @@ func updateTrigger(writer http.ResponseWriter, request *http.Request) { } timeSeriesNames := middleware.GetTimeSeriesNames(request) + response, err := controller.UpdateTrigger(database, &trigger.TriggerModel, triggerID, timeSeriesNames) if err != nil { render.Render(writer, request, err) //nolint @@ -115,6 +116,7 @@ func validateTargets(request *http.Request, trigger *dto.Trigger) ([]dto.TreeOfP func writeErrorSaveResponse(writer http.ResponseWriter, request *http.Request, treesOfProblems []dto.TreeOfProblems) { render.Status(request, http.StatusBadRequest) + response := dto.SaveTriggerResponse{ CheckResult: dto.TriggerCheckResponse{ Targets: treesOfProblems, @@ -135,6 +137,7 @@ func writeErrorSaveResponse(writer http.ResponseWriter, request *http.Request, t // @router /trigger/{triggerID} [delete] func removeTrigger(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) + err := controller.RemoveTrigger(database, triggerID) if err != nil { render.Render(writer, request, err) //nolint @@ -205,11 +208,13 @@ func checkingTemplateFilling(request *http.Request, trigger dto.Trigger) *api.Er // @router /trigger/{triggerID}/state [get] func getTriggerState(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) + triggerState, err := controller.GetTriggerLastCheck(database, triggerID) if err != nil { render.Render(writer, request, err) //nolint return } + if err := render.Render(writer, request, triggerState); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint } @@ -228,11 +233,13 @@ func getTriggerState(writer http.ResponseWriter, request *http.Request) { // @router /trigger/{triggerID}/throttling [get] func getTriggerThrottling(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) + triggerState, err := controller.GetTriggerThrottling(database, triggerID) if err != nil { render.Render(writer, request, err) //nolint return } + if err := render.Render(writer, request, triggerState); err != nil { render.Render(writer, request, api.ErrorRender(err)) //nolint } @@ -250,6 +257,7 @@ func getTriggerThrottling(writer http.ResponseWriter, request *http.Request) { // @router /trigger/{triggerID}/throttling [delete] func deleteThrottling(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) + err := controller.DeleteTriggerThrottling(database, triggerID) if err != nil { render.Render(writer, request, err) //nolint @@ -272,10 +280,12 @@ func deleteThrottling(writer http.ResponseWriter, request *http.Request) { func setTriggerMaintenance(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) triggerMaintenance := dto.TriggerMaintenance{} + if err := render.Bind(request, &triggerMaintenance); err != nil { render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint return } + userLogin := middleware.GetLogin(request) timeCallMaintenance := time.Now().Unix() @@ -310,5 +320,6 @@ func prepareTriggerContext(request *http.Request) (triggerID string, log moira.L logger := middleware.GetLoggerEntry(request) triggerID = middleware.GetTriggerID(request) log = logger.Clone().String(moira.LogFieldNameTriggerID, triggerID) + return triggerID, log } diff --git a/api/handler/trigger_metrics.go b/api/handler/trigger_metrics.go index a1f95fa4a..c097037d2 100644 --- a/api/handler/trigger_metrics.go +++ b/api/handler/trigger_metrics.go @@ -41,6 +41,7 @@ func getTriggerMetrics(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) fromStr := middleware.GetFromStr(request) toStr := middleware.GetToStr(request) + from := date.DateParamToEpoch(fromStr, "UTC", 0, time.UTC) if from == 0 { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("can not parse from: %s", fromStr))) //nolint diff --git a/api/handler/trigger_render.go b/api/handler/trigger_render.go index 02794c759..ea7848730 100644 --- a/api/handler/trigger_render.go +++ b/api/handler/trigger_render.go @@ -42,6 +42,7 @@ func renderTrigger(writer http.ResponseWriter, request *http.Request) { render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint return } + metricsData, trigger, err := evaluateTargetMetrics(sourceProvider, from, to, triggerID, fetchRealtimeData) if err != nil { if trigger == nil { @@ -49,6 +50,7 @@ func renderTrigger(writer http.ResponseWriter, request *http.Request) { } else { render.Render(writer, request, api.ErrorInternalServer(err)) //nolint } + return } @@ -62,7 +64,9 @@ func renderTrigger(writer http.ResponseWriter, request *http.Request) { render.Render(writer, request, api.ErrorInternalServer(err)) //nolint return } + writer.Header().Set("Content-Type", "image/png") + err = renderable.Render(chart.PNG, writer) if err != nil { render.Render(writer, request, api.ErrorInternalServer(fmt.Errorf("can not render plot %s", err.Error()))) //nolint @@ -76,6 +80,7 @@ func getEvaluationParameters(request *http.Request) (sourceProvider *metricSourc fromStr := middleware.GetFromStr(request) toStr := middleware.GetToStr(request) from = date.DateParamToEpoch(fromStr, "UTC", 0, time.UTC) + urlValues, err := url.ParseQuery(request.URL.RawQuery) if err != nil { return sourceProvider, "", 0, 0, "", false, fmt.Errorf("failed to parse query string: %w", err) @@ -120,12 +125,14 @@ func buildRenderable(request *http.Request, trigger *moira.Trigger, metricsData } timezone := urlValues.Get("timezone") + location, err := time.LoadLocation(timezone) if err != nil { return nil, fmt.Errorf("failed to load %s timezone: %s", timezone, err.Error()) } plotTheme := urlValues.Get("theme") + plotTemplate, err := plotting.GetPlotTemplate(plotTheme, location) if err != nil { return nil, fmt.Errorf("can not initialize plot theme %s", err.Error()) diff --git a/api/handler/triggers.go b/api/handler/triggers.go index 50eaf00cb..0010f12a8 100644 --- a/api/handler/triggers.go +++ b/api/handler/triggers.go @@ -81,6 +81,7 @@ func getUnusedTriggers(writer http.ResponseWriter, request *http.Request) { if err != nil { return } //nolint + return } @@ -169,6 +170,7 @@ func getTriggerFromRequest(request *http.Request) (*dto.Trigger, *api.ErrorRespo String("status", response.StatusText). Error(err). Msg("Remote server unavailable") + return nil, response case *json.UnmarshalTypeError: return nil, api.ErrorInvalidRequest(fmt.Errorf("invalid payload: %s", err.Error())) @@ -176,6 +178,7 @@ func getTriggerFromRequest(request *http.Request) (*dto.Trigger, *api.ErrorRespo return nil, api.ErrorInternalServer(err) } } + trigger.UpdatedBy = middleware.GetLogin(request) return trigger, nil @@ -229,6 +232,7 @@ func triggerCheck(writer http.ResponseWriter, request *http.Request) { if len(trigger.Targets) > 0 { var err error + response.Targets, err = dto.TargetVerification(trigger.Targets, ttl, trigger.TriggerSource) if err != nil { render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint @@ -317,7 +321,9 @@ func deletePager(writer http.ResponseWriter, request *http.Request) { func getRequestTags(request *http.Request) []string { var filterTags []string + i := 0 + for { tag := request.FormValue(fmt.Sprintf("tags[%v]", i)) if tag == "" { @@ -326,6 +332,7 @@ func getRequestTags(request *http.Request) []string { filterTags = append(filterTags, tag) i++ } + return filterTags } @@ -335,6 +342,7 @@ func getOnlyProblemsFlag(request *http.Request) bool { onlyProblems, _ := strconv.ParseBool(onlyProblemsStr) return onlyProblems } + return false } @@ -345,6 +353,7 @@ func getTriggerCreatedBy(request *http.Request) (string, bool) { if createdBy, ok := request.Form["createdBy"]; ok { return createdBy[0], true } + return "", false } @@ -352,5 +361,6 @@ func getSearchRequestString(request *http.Request) string { searchText := request.FormValue("text") searchText = strings.ToLower(searchText) searchText, _ = url.PathUnescape(searchText) + return searchText } diff --git a/api/handler/user.go b/api/handler/user.go index b9bf11ea4..aee06fae5 100644 --- a/api/handler/user.go +++ b/api/handler/user.go @@ -28,6 +28,7 @@ func user(router chi.Router) { func getUserName(writer http.ResponseWriter, request *http.Request) { userLogin := middleware.GetLogin(request) auth := middleware.GetAuth(request) + if err := render.Render(writer, request, &dto.User{ Login: userLogin, Role: auth.GetRole(userLogin), @@ -51,6 +52,7 @@ func getUserName(writer http.ResponseWriter, request *http.Request) { func getUserSettings(writer http.ResponseWriter, request *http.Request) { userLogin := middleware.GetLogin(request) auth := middleware.GetAuth(request) + userSettings, err := controller.GetUserSettings(database, userLogin, auth) if err != nil { render.Render(writer, request, err) //nolint diff --git a/api/middleware/authorization.go b/api/middleware/authorization.go index ac282741a..4b6c1fb7b 100644 --- a/api/middleware/authorization.go +++ b/api/middleware/authorization.go @@ -18,8 +18,10 @@ func AdminOnlyMiddleware() func(next http.Handler) http.Handler { render.Render(w, r, api.ErrorForbidden("Only administrators can use this")) //nolint:errcheck return } + next.ServeHTTP(w, r) } + return http.HandlerFunc(fn) } } diff --git a/api/middleware/context.go b/api/middleware/context.go index 5dbc88e34..243a2a346 100644 --- a/api/middleware/context.go +++ b/api/middleware/context.go @@ -52,6 +52,7 @@ func TriggerContext(next http.Handler) http.Handler { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("triggerID must be set"))) //nolint return } + ctx := context.WithValue(request.Context(), triggerIDKey, triggerID) next.ServeHTTP(writer, request.WithContext(ctx)) }) @@ -65,6 +66,7 @@ func ContactContext(next http.Handler) http.Handler { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("contactID must be set"))) //nolint return } + ctx := context.WithValue(request.Context(), contactIDKey, contactID) next.ServeHTTP(writer, request.WithContext(ctx)) }) @@ -78,6 +80,7 @@ func TagContext(next http.Handler) http.Handler { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("tag must be set"))) //nolint return } + ctx := context.WithValue(request.Context(), tagKey, tag) next.ServeHTTP(writer, request.WithContext(ctx)) }) @@ -91,6 +94,7 @@ func SubscriptionContext(next http.Handler) http.Handler { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("subscriptionId must be set"))) //nolint return } + ctx := context.WithValue(request.Context(), subscriptionIDKey, subscriptionID) next.ServeHTTP(writer, request.WithContext(ctx)) }) @@ -250,6 +254,7 @@ func TeamContext(next http.Handler) http.Handler { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("teamId must be set"))) //nolint:errcheck return } + ctx := context.WithValue(request.Context(), teamIDKey, teamID) next.ServeHTTP(writer, request.WithContext(ctx)) }) @@ -263,6 +268,7 @@ func TeamUserIDContext(next http.Handler) http.Handler { render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("userId must be set"))) //nolint:errcheck return } + ctx := context.WithValue(request.Context(), teamUserIDKey, userID) next.ServeHTTP(writer, request.WithContext(ctx)) }) diff --git a/api/middleware/logger.go b/api/middleware/logger.go index dd93d585c..68c303008 100644 --- a/api/middleware/logger.go +++ b/api/middleware/logger.go @@ -44,8 +44,10 @@ func RequestLogger(logger moira.Logger) func(next http.Handler) http.Handler { wrapWriter := middleware.NewWrapResponseWriter(&responseWriterWithBody{ResponseWriter: writer}, request.ProtoMajor) t1 := time.Now() + defer func() { rvr := recover() + entry.fillMsg(request) if rvr != nil { @@ -58,6 +60,7 @@ func RequestLogger(logger moira.Logger) func(next http.Handler) http.Handler { next.ServeHTTP(wrapWriter, WithLogEntry(request, entry)) } + return http.HandlerFunc(fn) } } @@ -65,6 +68,7 @@ func RequestLogger(logger moira.Logger) func(next http.Handler) http.Handler { func getErrorResponseIfItHas(writer http.ResponseWriter) *api.ErrorResponse { writerWithBody := writer.(*responseWriterWithBody) errResp := &api.ErrorResponse{} + if err := json.NewDecoder(&writerWithBody.body).Decode(errResp); err != nil { return &api.ErrorResponse{ HTTPStatusCode: http.StatusInternalServerError, @@ -121,6 +125,7 @@ func (entry *apiLoggerEntry) write(status, bytes int, elapsed time.Duration, res if status == 0 { status = http.StatusOK } + if status >= http.StatusInternalServerError { event = entry.logger.Error() @@ -158,8 +163,10 @@ type responseWriterWithBody struct { func (w *responseWriterWithBody) Write(buf []byte) (int, error) { n, err := w.ResponseWriter.Write(buf) _, err2 := w.body.Write(buf[:n]) + if err == nil { err = err2 } + return n, err } diff --git a/api/middleware/middleware.go b/api/middleware/middleware.go index 7c9c01bc5..918126605 100644 --- a/api/middleware/middleware.go +++ b/api/middleware/middleware.go @@ -145,6 +145,7 @@ func GetTeamID(request *http.Request) string { if teamID == nil { return "" } + return teamID.(string) } diff --git a/api/middleware/readonly_mode.go b/api/middleware/readonly_mode.go index 86dfdcb93..9f51d41e8 100644 --- a/api/middleware/readonly_mode.go +++ b/api/middleware/readonly_mode.go @@ -15,8 +15,10 @@ func ReadOnlyMiddleware(config *api.Config) func(next http.Handler) http.Handler render.Render(w, r, api.ErrorForbidden("Moira is currently in read-only mode")) //nolint:errcheck return } + next.ServeHTTP(w, r) } + return http.HandlerFunc(fn) } } diff --git a/checker/worker/trigger_handler.go b/checker/worker/trigger_handler.go index 8f5f61999..e7a845c3b 100644 --- a/checker/worker/trigger_handler.go +++ b/checker/worker/trigger_handler.go @@ -41,7 +41,9 @@ func (manager *Manager) handleTrigger(triggerID string, metrics *metrics.CheckMe err = fmt.Errorf("panic: '%s' stack: %s", r, debug.Stack()) } }() + err = manager.handleTriggerInLock(triggerID, metrics) + return err } @@ -87,8 +89,10 @@ func (manager *Manager) checkTrigger(triggerID string) error { if errors.Is(err, checker.ErrTriggerNotExists) { return nil } + if err != nil { return err } + return triggerChecker.Check() } diff --git a/cmd/api/main.go b/cmd/api/main.go index 9f87d2fd9..03b299278 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -38,6 +38,7 @@ var ( func main() { flag.Parse() + if *printVersion { fmt.Println("Moira Api") fmt.Println("Version:", MoiraVersion) @@ -148,11 +149,13 @@ func main() { go func() { server.Serve(listener) //nolint }() + defer Stop(logger, server) logger.Info(). String("moira_version", MoiraVersion). Msg("Moira Api Started") + ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) @@ -166,6 +169,7 @@ func main() { func Stop(logger moira.Logger, server *http.Server) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) //nolint defer cancel() + if err := server.Shutdown(ctx); err != nil { logger.Error(). Error(err). diff --git a/filter/connection/handler.go b/filter/connection/handler.go index 2b32d9d3a..827a9a609 100644 --- a/filter/connection/handler.go +++ b/filter/connection/handler.go @@ -27,6 +27,7 @@ func NewConnectionsHandler(logger moira.Logger) *Handler { // HandleConnection convert every line from connection to metric and send it to lineChan channel. func (handler *Handler) HandleConnection(connection net.Conn, lineChan chan<- []byte) { handler.wg.Add(1) + go func() { defer handler.wg.Done() handler.handle(connection, lineChan) @@ -36,6 +37,7 @@ func (handler *Handler) HandleConnection(connection net.Conn, lineChan chan<- [] func (handler *Handler) handle(connection net.Conn, lineChan chan<- []byte) { buffer := bufio.NewReader(connection) closeConnection := make(chan struct{}) + go func(conn net.Conn) { select { case <-handler.terminate: @@ -48,14 +50,18 @@ func (handler *Handler) handle(connection net.Conn, lineChan chan<- []byte) { bytes, err := buffer.ReadBytes('\n') if err != nil { connection.Close() + if err != io.EOF { handler.logger.Error(). Error(err). Msg("Fail to read from metric connection") } + close(closeConnection) + return } + bytesWithoutCRLF := dropCRLF(bytes) if len(bytesWithoutCRLF) > 0 { lineChan <- bytesWithoutCRLF @@ -74,8 +80,10 @@ func dropCRLF(bytes []byte) []byte { if bytesLength > 0 && bytes[bytesLength-1] == '\n' { bytesLength-- } + if bytesLength > 0 && bytes[bytesLength-1] == '\r' { bytesLength-- } + return bytes[:bytesLength] } diff --git a/senders/victorops/api/alert.go b/senders/victorops/api/alert.go index c396ae017..cc068630f 100644 --- a/senders/victorops/api/alert.go +++ b/senders/victorops/api/alert.go @@ -45,6 +45,7 @@ func (client *Client) CreateAlert(routingKey string, alert CreateAlertRequest) e if err != nil { return fmt.Errorf("error while encoding json: %w", err) } + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, fmt.Sprintf("%s/%s", client.routingURL, routingKey), bytes.NewReader(body)) if err != nil { return err @@ -57,9 +58,12 @@ func (client *Client) CreateAlert(routingKey string, alert CreateAlertRequest) e if err != nil { return fmt.Errorf("error while making the request to victorops: %w", err) } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("victorops API request resulted in error with status %v: %v", resp.StatusCode, string(body)) } diff --git a/senders/victorops/api/client.go b/senders/victorops/api/client.go index 709e3a496..8bcb373a5 100644 --- a/senders/victorops/api/client.go +++ b/senders/victorops/api/client.go @@ -14,6 +14,7 @@ func NewClient(routingURL string, httpClient *http.Client) *Client { if httpClient == nil { httpClient = http.DefaultClient } + return &Client{ httpClient: httpClient, routingURL: routingURL,