diff --git a/Dockerfile.api b/Dockerfile.api index 520944ec0..f34cbc4e8 100644 --- a/Dockerfile.api +++ b/Dockerfile.api @@ -3,9 +3,12 @@ FROM golang:1.18 as builder COPY go.mod go.sum /go/src/github.com/moira-alert/moira/ WORKDIR /go/src/github.com/moira-alert/moira RUN go mod download +RUN go install github.com/swaggo/swag/cmd/swag@v1.8.12 COPY . /go/src/github.com/moira-alert/moira/ +RUN /go/bin/swag init -g api/handler/handler.go + ARG GO_VERSION="GoVersion" ARG GIT_COMMIT="git_Commit" ARG MoiraVersion="MoiraVersion" diff --git a/Makefile b/Makefile index 74e3a0e35..97df83ae3 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,20 @@ lint: mock: . ./generate_mocks.sh +.PHONY: install-swag +install-swag: + go install github.com/swaggo/swag/cmd/swag@v1.8.12 + +.PHONY: spec +spec: + echo "Generating Swagger documentation" + swag init -g api/handler/handler.go + swag fmt + +.PHONY: validate-spec +validate-spec: + openapi-generator-cli validate -i docs/swagger.yaml + .PHONY: test test: echo 'mode: atomic' > coverage.txt && go list ./... | xargs -n1 -I{} sh -c 'go test -race -v -bench=. -covermode=atomic -coverprofile=coverage.tmp {} && tail -n +2 coverage.tmp >> coverage.txt' && rm coverage.tmp diff --git a/api/dto/contact.go b/api/dto/contact.go index 4f28adf2b..12e9fd1da 100644 --- a/api/dto/contact.go +++ b/api/dto/contact.go @@ -17,10 +17,10 @@ func (*ContactList) Render(w http.ResponseWriter, r *http.Request) error { } type Contact struct { - Type string `json:"type"` - Value string `json:"value"` - ID string `json:"id,omitempty"` - User string `json:"user,omitempty"` + Type string `json:"type" example:"mail"` + Value string `json:"value" example:"devops@example.com"` + ID string `json:"id,omitempty" example:"1dd38765-c5be-418d-81fa-7a5f879c2315"` + User string `json:"user,omitempty" example:""` TeamID string `json:"team_id,omitempty"` } diff --git a/api/dto/events.go b/api/dto/events.go index e24fddde3..2f704d337 100644 --- a/api/dto/events.go +++ b/api/dto/events.go @@ -8,9 +8,9 @@ import ( ) type EventsList struct { - Page int64 `json:"page"` - Size int64 `json:"size"` - Total int64 `json:"total"` + Page int64 `json:"page" example:"0"` + Size int64 `json:"size" example:"100"` + Total int64 `json:"total" example:"10"` List []moira.NotificationEvent `json:"list"` } diff --git a/api/dto/health.go b/api/dto/health.go index a9fd7eb8d..db8adbfc9 100644 --- a/api/dto/health.go +++ b/api/dto/health.go @@ -13,8 +13,8 @@ const ( ) type NotifierState struct { - State string `json:"state"` - Message string `json:"message,omitempty"` + State string `json:"state" example:"ERROR"` + Message string `json:"message,omitempty" example:"Moira has been turned off for maintenance"` } func (*NotifierState) Render(w http.ResponseWriter, r *http.Request) error { diff --git a/api/dto/notification.go b/api/dto/notification.go index 0c685df32..4a6fa6ef1 100644 --- a/api/dto/notification.go +++ b/api/dto/notification.go @@ -8,7 +8,7 @@ import ( ) type NotificationsList struct { - Total int64 `json:"total"` + Total int64 `json:"total" example:"0"` List []*moira.ScheduledNotification `json:"list"` } @@ -17,7 +17,7 @@ func (*NotificationsList) Render(w http.ResponseWriter, r *http.Request) error { } type NotificationDeleteResponse struct { - Result int64 `json:"result"` + Result int64 `json:"result" example:"0"` } func (*NotificationDeleteResponse) Render(w http.ResponseWriter, r *http.Request) error { diff --git a/api/dto/pattern.go b/api/dto/pattern.go index 6df448512..3fdd4710b 100644 --- a/api/dto/pattern.go +++ b/api/dto/pattern.go @@ -14,7 +14,7 @@ func (*PatternList) Render(w http.ResponseWriter, r *http.Request) error { } type PatternData struct { - Metrics []string `json:"metrics"` - Pattern string `json:"pattern"` + Metrics []string `json:"metrics" example:"DevOps.my_server.hdd.freespace_mbytes, DevOps.my_server.hdd.freespace_mbytes, DevOps.my_server.db.*"` + Pattern string `json:"pattern" example:"Devops.my_server.*"` Triggers []TriggerModel `json:"triggers"` } diff --git a/api/dto/tag.go b/api/dto/tag.go index a6f3ae497..9fd435bf4 100644 --- a/api/dto/tag.go +++ b/api/dto/tag.go @@ -8,7 +8,7 @@ import ( ) type TagsData struct { - TagNames []string `json:"list"` + TagNames []string `json:"list" example:"cpu"` } func (*TagsData) Render(w http.ResponseWriter, r *http.Request) error { @@ -16,7 +16,7 @@ func (*TagsData) Render(w http.ResponseWriter, r *http.Request) error { } type MessageResponse struct { - Message string `json:"message"` + Message string `json:"message" example:"tag deleted"` } func (*MessageResponse) Render(w http.ResponseWriter, r *http.Request) error { @@ -28,8 +28,8 @@ type TagsStatistics struct { } type TagStatistics struct { - TagName string `json:"name"` - Triggers []string `json:"triggers"` + TagName string `json:"name" example:"cpu"` + Triggers []string `json:"triggers" example:"bcba82f5-48cf-44c0-b7d6-e1d32c64a88c"` Subscriptions []moira.SubscriptionData `json:"subscriptions"` } diff --git a/api/dto/target.go b/api/dto/target.go index 135e3b52f..2c6f11afb 100644 --- a/api/dto/target.go +++ b/api/dto/target.go @@ -101,10 +101,10 @@ var ( ) type ProblemOfTarget struct { - Argument string `json:"argument"` - Type typeOfProblem `json:"type,omitempty"` - Description string `json:"description,omitempty"` - Position int `json:"position"` + Argument string `json:"argument" example:"consolidateBy"` + Type typeOfProblem `json:"type,omitempty" example:"warn"` + Description string `json:"description,omitempty" example:"This function affects only visual graph representation. It is meaningless in Moira"` + Position int `json:"position" example:"0"` Problems []ProblemOfTarget `json:"problems,omitempty"` } @@ -123,7 +123,7 @@ func (p *ProblemOfTarget) hasError() bool { } type TreeOfProblems struct { - SyntaxOk bool `json:"syntax_ok"` + SyntaxOk bool `json:"syntax_ok" example:"true"` TreeOfProblems *ProblemOfTarget `json:"tree_of_problems,omitempty"` } diff --git a/api/dto/team.go b/api/dto/team.go index 1bb07c7bb..53b129967 100644 --- a/api/dto/team.go +++ b/api/dto/team.go @@ -15,9 +15,9 @@ const ( // TeamModel is a structure that represents team entity in HTTP transfer type TeamModel struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` + ID string `json:"id" example:"d5d98eb3-ee18-4f75-9364-244f67e23b54"` + Name string `json:"name" example:"Infrastructure Team"` + Description string `json:"description" example:"Team that holds all members of infrastructure division"` } // NewTeamModel is a constructor function that creates a new TeamModel using moira.Team @@ -59,7 +59,7 @@ func (t TeamModel) ToMoiraTeam() moira.Team { // SaveTeamResponse is a structure to return team creation result in HTTP response type SaveTeamResponse struct { - ID string `json:"id"` + ID string `json:"id" example:"d5d98eb3-ee18-4f75-9364-244f67e23b54"` } // Render is a function that implements chi Renderer interface for SaveTeamResponse @@ -79,7 +79,7 @@ func (UserTeams) Render(w http.ResponseWriter, r *http.Request) error { // TeamMembers is a structure that represents a team members in HTTP transfer type TeamMembers struct { - Usernames []string `json:"usernames"` + Usernames []string `json:"usernames" example:"anonymous"` } // Bind is a method that implements Binder interface from chi and checks that validity of data in request @@ -96,7 +96,7 @@ func (TeamMembers) Render(w http.ResponseWriter, r *http.Request) error { } type TeamSettings struct { - TeamID string `json:"team_id"` + TeamID string `json:"team_id" example:"d5d98eb3-ee18-4f75-9364-244f67e23b54"` Contacts []moira.ContactData `json:"contacts"` Subscriptions []moira.SubscriptionData `json:"subscriptions"` } diff --git a/api/dto/triggers.go b/api/dto/triggers.go index 300df70af..c573e1659 100644 --- a/api/dto/triggers.go +++ b/api/dto/triggers.go @@ -36,43 +36,43 @@ func (*TriggersList) Render(http.ResponseWriter, *http.Request) error { type Trigger struct { TriggerModel - Throttling int64 `json:"throttling"` + Throttling int64 `json:"throttling" example:"0"` } // TriggerModel is moira.Trigger api representation type TriggerModel struct { // Trigger unique ID - ID string `json:"id"` + ID string `json:"id" example:"292516ed-4924-4154-a62c-ebe312431fce"` // Trigger name - Name string `json:"name"` + Name string `json:"name" example:"Not enough disk space left"` // Description string - Desc *string `json:"desc,omitempty"` + Desc *string `json:"desc,omitempty" example:"check the size of /var/log"` // Graphite-like targets: t1, t2, ... - Targets []string `json:"targets"` + Targets []string `json:"targets" example:"devOps.my_server.hdd.freespace_mbytes"` // WARN threshold - WarnValue *float64 `json:"warn_value"` + WarnValue *float64 `json:"warn_value" example:"500"` // ERROR threshold - ErrorValue *float64 `json:"error_value"` + ErrorValue *float64 `json:"error_value" example:"1000"` // Could be: rising, falling, expression - TriggerType string `json:"trigger_type"` + TriggerType string `json:"trigger_type" example:"rising"` // Set of tags to manipulate subscriptions - Tags []string `json:"tags"` + Tags []string `json:"tags" example:"server,disk"` // When there are no metrics for trigger, Moira will switch metric to TTLState state after TTL seconds - TTLState *moira.TTLState `json:"ttl_state,omitempty"` + TTLState *moira.TTLState `json:"ttl_state,omitempty" example:"NODATA"` // When there are no metrics for trigger, Moira will switch metric to TTLState state after TTL seconds - TTL int64 `json:"ttl,omitempty"` + TTL int64 `json:"ttl,omitempty" example:"600"` // Determines when Moira should monitor trigger Schedule *moira.ScheduleData `json:"sched,omitempty"` // Used if you need more complex logic than provided by WARN/ERROR values - Expression string `json:"expression"` + Expression string `json:"expression" example:""` // Graphite patterns for trigger - Patterns []string `json:"patterns"` + Patterns []string `json:"patterns" example:""` // Shows if trigger is remote (graphite-backend) based or stored inside Moira-Redis DB - IsRemote bool `json:"is_remote"` + IsRemote bool `json:"is_remote" example:"false"` // If true, first event NODATA → OK will be omitted - MuteNewMetrics bool `json:"mute_new_metrics"` + MuteNewMetrics bool `json:"mute_new_metrics" example:"false"` // A list of targets that have only alone metrics - AloneMetrics map[string]bool `json:"alone_metrics"` + AloneMetrics map[string]bool `json:"alone_metrics" example:"t1:true"` // Datetime when the trigger was created CreatedAt *time.Time `json:"created_at"` // Datetime when the trigger was updated @@ -366,7 +366,7 @@ type TriggerCheckResponse struct { type TriggerCheck struct { *moira.CheckData - TriggerID string `json:"trigger_id"` + TriggerID string `json:"trigger_id" example:"trigger_id"` } func (*TriggerCheck) Render(http.ResponseWriter, *http.Request) error { @@ -380,7 +380,7 @@ func (*MetricsMaintenance) Bind(*http.Request) error { } type TriggerMaintenance struct { - Trigger *int64 `json:"trigger"` + Trigger *int64 `json:"trigger" example:"1594225165"` Metrics map[string]int64 `json:"metrics"` } @@ -389,7 +389,7 @@ func (*TriggerMaintenance) Bind(*http.Request) error { } type ThrottlingResponse struct { - Throttling int64 `json:"throttling"` + Throttling int64 `json:"throttling" example:"0"` } func (*ThrottlingResponse) Render(http.ResponseWriter, *http.Request) error { @@ -397,8 +397,8 @@ func (*ThrottlingResponse) Render(http.ResponseWriter, *http.Request) error { } type SaveTriggerResponse struct { - ID string `json:"id"` - Message string `json:"message"` + ID string `json:"id" example:"trigger_id"` + Message string `json:"message" example:"trigger created"` CheckResult TriggerCheckResponse `json:"checkResult,omitempty"` } @@ -426,7 +426,7 @@ type TriggerDump struct { } type TriggersSearchResultDeleteResponse struct { - PagerID string `json:"pager_id"` + PagerID string `json:"pager_id" example:"292516ed-4924-4154-a62c-ebe312431fce"` } func (TriggersSearchResultDeleteResponse) Render(http.ResponseWriter, *http.Request) error { diff --git a/api/dto/user.go b/api/dto/user.go index c03b94bd4..68d45eca2 100644 --- a/api/dto/user.go +++ b/api/dto/user.go @@ -18,7 +18,7 @@ func (*UserSettings) Render(w http.ResponseWriter, r *http.Request) error { } type User struct { - Login string `json:"login"` + Login string `json:"login" example:"john"` } func (*User) Render(w http.ResponseWriter, r *http.Request) error { diff --git a/api/error_response.go b/api/error_response.go index fb4d40658..08dfec426 100644 --- a/api/error_response.go +++ b/api/error_response.go @@ -85,3 +85,35 @@ var ErrNotFound = &ErrorResponse{HTTPStatusCode: http.StatusNotFound, StatusText // ErrMethodNotAllowed is default 405 router method not allowed var ErrMethodNotAllowed = &ErrorResponse{HTTPStatusCode: http.StatusMethodNotAllowed, StatusText: "Method not allowed."} + +// Examples for `swaggo`: + +type ErrorInternalServerExample struct { + StatusText string `json:"status" example:"Internal Server Error"` + ErrorText string `json:"error" example:"server error during request handling"` +} + +type ErrorInvalidRequestExample struct { + StatusText string `json:"status" example:"Invalid request"` + ErrorText string `json:"error" example:"resource with the ID does not exist"` +} + +type ErrorRenderExample struct { + StatusText string `json:"status" example:"Error rendering response"` + ErrorText string `json:"error" example:"rendering error"` +} + +type ErrorNotFoundExample struct { + StatusText string `json:"status" example:"Resource not found"` + ErrorText string `json:"error" example:"resource with ID '66741a8c-c2ba-4357-a2c9-ee78e0e7' does not exist"` +} + +type ErrorForbiddenExample struct { + StatusText string `json:"status" example:"Forbidden"` + ErrorText string `json:"error" example:"you cannot access this resource"` +} + +type ErrorRemoteServerUnavailableExample struct { + StatusText string `json:"status" example:"Remote server unavailable"` + ErrorText string `json:"error" example:"Remote server error, please contact administrator"` +} diff --git a/api/handler/config.go b/api/handler/config.go index c758f2145..ea62edc2b 100644 --- a/api/handler/config.go +++ b/api/handler/config.go @@ -2,6 +2,24 @@ package handler import "net/http" +type ContactExample struct { + Type string `json:"type" example:"telegram"` + Label string `json:"label" example:"Telegram"` +} + +type ConfigurationResponse struct { + RemoteAllowed bool `json:"remoteAllowed" example:"false"` + Contacts []ContactExample `json:"contacts"` +} + +// nolint: gofmt,goimports +// +// @summary Get available configuration +// @id get-web-config +// @tags config +// @produce json +// @success 200 {object} ConfigurationResponse "Configuration fetched successfully" +// @router /config [get] func getWebConfig(configContent []byte) http.HandlerFunc { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "application/json") diff --git a/api/handler/contact.go b/api/handler/contact.go index b824a9037..9c4f338b5 100644 --- a/api/handler/contact.go +++ b/api/handler/contact.go @@ -27,6 +27,16 @@ func contact(router chi.Router) { }) } +// nolint: gofmt,goimports +// +// @summary Gets all Moira contacts +// @id get-all-contacts +// @tags contact +// @produce json +// @success 200 {object} dto.ContactList "Contacts fetched successfully" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /contact [get] func getAllContacts(writer http.ResponseWriter, request *http.Request) { contacts, err := controller.GetAllContacts(database) if err != nil { @@ -40,6 +50,19 @@ func getAllContacts(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get contact by ID +// @id get-contact-by-id +// @tags contact +// @produce json +// @param contactID path string true "Contact ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.Contact "Successfully received contact" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /contact/{contactID} [get] func getContactById(writer http.ResponseWriter, request *http.Request) { contactData := request.Context().Value(contactKey).(moira.ContactData) @@ -56,6 +79,19 @@ func getContactById(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Creates a new contact notification for the current user +// @id create-new-contact +// @tags contact +// @accept json +// @produce json +// @param contact body dto.Contact true "Contact data" +// @success 200 {object} dto.Contact "Contact created successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /contact [put] func createNewContact(writer http.ResponseWriter, request *http.Request) { contact := &dto.Contact{} if err := render.Bind(request, contact); err != nil { @@ -90,6 +126,22 @@ func contactFilter(next http.Handler) http.Handler { }) } +// nolint: gofmt,goimports +// +// @summary Updates an existing notification contact to the values passed in the request body +// @id update-contact +// @accept json +// @produce json +// @param contactID path string true "ID of the contact to update" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param contact body dto.Contact true "Updated contact data" +// @success 200 {object} dto.Contact "Updated contact" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /contact/{contactID} [put] +// @tags contact func updateContact(writer http.ResponseWriter, request *http.Request) { contactDTO := dto.Contact{} if err := render.Bind(request, &contactDTO); err != nil { @@ -108,6 +160,20 @@ func updateContact(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Deletes notification contact for the current user and remove the contact ID from all subscriptions +// @id remove-contact +// @accept json +// @produce json +// @tags contact +// @param contactID path string true "ID of the contact to remove" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 "Contact has been deleted" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @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, "") @@ -116,6 +182,19 @@ func removeContact(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Push a test notification to verify that the contact is properly set up +// @id send-test-contact-notification +// @accept json +// @produce json +// @param contactID path string true "The ID of the target contact" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 "Test successful" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /contact/{contactID}/test [post] +// @tags contact func sendTestContactNotification(writer http.ResponseWriter, request *http.Request) { contactID := middleware.GetContactID(request) err := controller.SendTestContactNotification(database, contactID) diff --git a/api/handler/contact_events.go b/api/handler/contact_events.go index e1192a254..7c57b409e 100644 --- a/api/handler/contact_events.go +++ b/api/handler/contact_events.go @@ -24,6 +24,22 @@ func contactEvents(router chi.Router) { }) } +// nolint: gofmt,goimports +// +// @summary Get contact events by ID with time range +// @id get-contact-events-by-id +// @tags contact +// @produce json +// @param contactID path string true "Contact ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param from query string false "Start time of the time range" default(-3hour) +// @param to query string false "End time of the time range" default(now) +// @success 200 {object} dto.ContactEventItemList "Successfully received contact events" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /contact/{contactID}/events [get] func getContactByIdWithEvents(writer http.ResponseWriter, request *http.Request) { contactData := request.Context().Value(contactKey).(moira.ContactData) fromStr := middleware.GetFromStr(request) diff --git a/api/handler/event.go b/api/handler/event.go index 10f162e5c..398ad96a8 100644 --- a/api/handler/event.go +++ b/api/handler/event.go @@ -15,6 +15,21 @@ func event(router chi.Router) { router.Delete("/all", deleteAllEvents) } +// nolint: gofmt,goimports +// +// @summary Gets all trigger events for current page and their count +// @id get-events-list +// @tags event +// @produce json +// @param triggerID path string true "The ID of updated trigger" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param size query int false "Number of items to be displayed on one page" default(100) +// @param p query int false "Defines the number of the displayed page. E.g, p=2 would display the 2nd page" default(0) +// @success 200 {object} dto.EventsList "Events fetched successfully" +// @Failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @Failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @Failure 422 {object} api.ErrorRenderExample "Render error" +// @Failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /event/{triggerID} [get] func getEventsList(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) size := middleware.GetSize(request) @@ -29,6 +44,15 @@ func getEventsList(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Deletes all notification events +// @id delete-all-events +// @tags event +// @produce json +// @success 200 "Events removed successfully" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /event/all [delete] func deleteAllEvents(writer http.ResponseWriter, request *http.Request) { if errorResponse := controller.DeleteAllEvents(database); errorResponse != nil { render.Render(writer, request, errorResponse) //nolint diff --git a/api/handler/handler.go b/api/handler/handler.go index cad6f07c9..54d7e3254 100644 --- a/api/handler/handler.go +++ b/api/handler/handler.go @@ -8,10 +8,13 @@ import ( "github.com/go-chi/render" metricSource "github.com/moira-alert/moira/metric_source" "github.com/rs/cors" + httpSwagger "github.com/swaggo/http-swagger" "github.com/moira-alert/moira" "github.com/moira-alert/moira/api" moiramiddle "github.com/moira-alert/moira/api/middleware" + + _ "github.com/moira-alert/moira/docs" // docs is generated by Swag CLI, you have to import it. ) var database moira.Database @@ -33,6 +36,53 @@ func NewHandler(db moira.Database, log moira.Logger, index moira.Searcher, confi router.NotFound(notFoundHandler) router.MethodNotAllowed(methodNotAllowedHandler) + // @title Moira Alert + // @version master + // @description This is an API description for [Moira Alert API](https://moira.readthedocs.io/en/latest/overview.html) + // @description Check us out on [Github](https://github.com/moira-alert) or look up our [guide on getting started with Moira](https://moira.readthedocs.io) + // @contact.name Contact Moira Team + // @contact.email opensource@skbkontur.com + // @license.name MIT + // @BasePath /api + // + // @tag.name contact + // @tag.description APIs for working with Moira contacts. For more details, see + // + // @tag.name config + // @tag.description View Moira's runtime configuration. For more details, see + // + // @tag.name event + // @tag.description APIs for interacting with notification events. See for details + // + // @tag.name health + // @tag.description interact with Moira states/health status. See for details + // + // @tag.name notification + // @tag.description manage notifications that are currently in queue. See + // + // @tag.name pattern + // @tag.description APIs for interacting with graphite patterns in Moira. See + // + // @tag.name subscription + // @tag.description APIs for managing a user's subscription(s). See to learn about Moira subscriptions + // + // @tag.name tag + // @tag.description APIs for managing tags (a grouping of tags and subscriptions). See + // + // @tag.name trigger + // @tag.description APIs for interacting with Moira triggers. See to learn about Triggers + // + // @tag.name team + // @tag.description APIs for interacting with Moira teams + // + // @tag.name teamSubscription + // @tag.description APIs for interacting with Moira subscriptions owned by certain team + // + // @tag.name teamContact + // @tag.description APIs for interacting with Moira contacts owned by certain team + // + // @tag.name user + // @tag.description APIs for interacting with Moira users router.Route("/api", func(router chi.Router) { router.Use(moiramiddle.DatabaseContext(database)) router.Get("/config", getWebConfig(webConfigContent)) @@ -45,12 +95,15 @@ func NewHandler(db moira.Database, log moira.Logger, index moira.Searcher, confi router.Route("/notification", notification) router.Route("/health", health) router.Route("/teams", teams) - router.Route("/contact", func(router chi.Router) { contact(router) contactEvents(router) }) + router.Get("/swagger/*", httpSwagger.Handler( + httpSwagger.URL("/api/swagger/doc.json"), + )) }) + if config.EnableCORS { return cors.AllowAll().Handler(router) } diff --git a/api/handler/health.go b/api/handler/health.go index 30eb5c1f2..e8affaa7f 100644 --- a/api/handler/health.go +++ b/api/handler/health.go @@ -15,6 +15,16 @@ func health(router chi.Router) { router.Put("/notifier", setNotifierState) } +// nolint: gofmt,goimports +// +// @summary Get notifier state +// @id get-notifier-state +// @tags health +// @produce json +// @success 200 {object} dto.NotifierState "Notifier state retrieved" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /health/notifier [get] func getNotifierState(writer http.ResponseWriter, request *http.Request) { state, err := controller.GetNotifierState(database) if err != nil { diff --git a/api/handler/notification.go b/api/handler/notification.go index 1ab60441f..92d879f00 100644 --- a/api/handler/notification.go +++ b/api/handler/notification.go @@ -18,6 +18,19 @@ func notification(router chi.Router) { router.Delete("/all", deleteAllNotifications) } +// nolint: gofmt,goimports +// +// @summary Gets a paginated list of notifications, all notifications are fetched if end = -1 and start = 0 +// @id get-notifications +// @tags notification +// @produce json +// @param start query int false "Default Value: 0" default(0) +// @param end query int false "Default Value: -1" default(-1) +// @success 200 {object} dto.NotificationsList "Notifications fetched successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /notification [get] func getNotification(writer http.ResponseWriter, request *http.Request) { urlValues, err := url.ParseQuery(request.URL.RawQuery) if err != nil { @@ -46,6 +59,18 @@ func getNotification(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Delete a notification by id +// @id delete-notification +// @tags notification +// @param id query string true "The ID of deleted notification" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @produce json +// @success 200 {object} dto.NotificationDeleteResponse "Notification have been deleted" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /notification [delete] func deleteNotification(writer http.ResponseWriter, request *http.Request) { urlValues, err := url.ParseQuery(request.URL.RawQuery) if err != nil { diff --git a/api/handler/pattern.go b/api/handler/pattern.go index ac2c20327..148c6b98f 100644 --- a/api/handler/pattern.go +++ b/api/handler/pattern.go @@ -16,6 +16,16 @@ func pattern(router chi.Router) { router.Delete("/{pattern}", deletePattern) } +// nolint: gofmt,goimports +// +// @summary Get all patterns +// @id get-all-patterns +// @tags pattern +// @produce json +// @success 200 {object} dto.PatternList "Patterns fetched successfully" +// @Failure 422 {object} api.ErrorRenderExample "Render error" +// @Failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /pattern [get] func getAllPatterns(writer http.ResponseWriter, request *http.Request) { logger := middleware.GetLoggerEntry(request) patternsList, err := controller.GetAllPatterns(database, logger) @@ -28,6 +38,17 @@ func getAllPatterns(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Deletes a Moira pattern +// @id delete-pattern +// @tags pattern +// @produce json +// @param pattern path string true "Trigger pattern to operate on" default(DevOps.my_server.hdd.freespace_mbytes) +// @success 200 "Pattern deleted successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /pattern/{pattern} [delete] func deletePattern(writer http.ResponseWriter, request *http.Request) { pattern := chi.URLParam(request, "pattern") if pattern == "" { diff --git a/api/handler/subscription.go b/api/handler/subscription.go index 1a958e513..833abbfb5 100644 --- a/api/handler/subscription.go +++ b/api/handler/subscription.go @@ -26,6 +26,16 @@ func subscription(router chi.Router) { }) } +// nolint: gofmt,goimports +// +// @summary Get all subscriptions +// @id get-user-subscriptions +// @tags subscription +// @produce json +// @success 200 {object} dto.SubscriptionList "Subscriptions fetched successfully" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /subscription [get] func getUserSubscriptions(writer http.ResponseWriter, request *http.Request) { userLogin := middleware.GetLogin(request) contacts, err := controller.GetUserSubscriptions(database, userLogin) @@ -39,6 +49,19 @@ func getUserSubscriptions(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Create a new subscription +// @id create-subscription +// @tags subscription +// @accept json +// @produce json +// @param subscription body dto.Subscription true "Subscription data" +// @success 200 {object} dto.Subscription "Subscription created successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /subscription [put] func createSubscription(writer http.ResponseWriter, request *http.Request) { subscription := &dto.Subscription{} if err := render.Bind(request, subscription); err != nil { @@ -78,6 +101,22 @@ func subscriptionFilter(next http.Handler) http.Handler { }) } +// nolint: gofmt,goimports +// +// @summary Update a subscription +// @id update-subscription +// @tags subscription +// @accept json +// @produce json +// @param subscriptionID path string true "ID of the subscription to update" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param subscription body dto.Subscription true "Updated subscription data" +// @success 200 {object} dto.Subscription "Subscription updated successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /subscription/{subscriptionID} [put] func updateSubscription(writer http.ResponseWriter, request *http.Request) { subscription := &dto.Subscription{} if err := render.Bind(request, subscription); err != nil { @@ -109,6 +148,18 @@ func updateSubscription(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Delete a subscription +// @id remove-subscription +// @tags subscription +// @produce json +// @param subscriptionID path string true "ID of the subscription to remove" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 "Subscription deleted" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /subscription/{subscriptionID} [delete] func removeSubscription(writer http.ResponseWriter, request *http.Request) { subscriptionID := middleware.GetSubscriptionID(request) if err := controller.RemoveSubscription(database, subscriptionID); err != nil { @@ -116,6 +167,18 @@ func removeSubscription(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Send a test notification for a subscription +// @id send-test-notification +// @tags subscription +// @produce json +// @param subscriptionID path string true "ID of the subscription to send the test notification" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 "Test notification sent successfully" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /subscription/{subscriptionID}/test [put] func sendTestNotification(writer http.ResponseWriter, request *http.Request) { subscriptionID := middleware.GetSubscriptionID(request) if err := controller.SendTestNotification(database, subscriptionID); err != nil { diff --git a/api/handler/tag.go b/api/handler/tag.go index 40068dee8..6faaea6ae 100644 --- a/api/handler/tag.go +++ b/api/handler/tag.go @@ -19,6 +19,16 @@ func tag(router chi.Router) { }) } +// nolint: gofmt,goimports +// +// @summary Get all tags +// @id get-all-tags +// @tags tag +// @produce json +// @success 200 {object} dto.TagsData "Tags fetched successfully" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /tag [get] func getAllTags(writer http.ResponseWriter, request *http.Request) { tagData, err := controller.GetAllTags(database) if err != nil { @@ -32,6 +42,16 @@ func getAllTags(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get all tags and their subscriptions +// @id get-all-tags-and-subscriptions +// @tags tag +// @produce json +// @success 200 {object} dto.TagsStatistics "Successful" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /tag/stats [get] func getAllTagsAndSubscriptions(writer http.ResponseWriter, request *http.Request) { logger := middleware.GetLoggerEntry(request) data, err := controller.GetAllTagsAndSubscriptions(database, logger) @@ -45,6 +65,18 @@ func getAllTagsAndSubscriptions(writer http.ResponseWriter, request *http.Reques } } +// nolint: gofmt,goimports +// +// @summary Remove a tag +// @id remove-tag +// @tags tag +// @produce json +// @param tag path string true "Name of the tag to remove" default(cpu) +// @success 200 {object} dto.MessageResponse "Tag removed successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /tag/{tag} [delete] func removeTag(writer http.ResponseWriter, request *http.Request) { tagName := middleware.GetTag(request) response, err := controller.RemoveTag(database, tagName) diff --git a/api/handler/team.go b/api/handler/team.go index 44c644903..bc079e6b4 100644 --- a/api/handler/team.go +++ b/api/handler/team.go @@ -46,6 +46,19 @@ func usersFilterForTeams(next http.Handler) http.Handler { }) } +// nolint: gofmt,goimports +// +// @summary Create a new team +// @id create-team +// @tags team +// @accept json +// @produce json +// @param team body dto.TeamModel true "Team data" +// @success 200 {object} dto.SaveTeamResponse "Team created successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams [post] func createTeam(writer http.ResponseWriter, request *http.Request) { user := middleware.GetLogin(request) team := dto.TeamModel{} @@ -65,6 +78,16 @@ func createTeam(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get all teams +// @id get-all-teams +// @tags team +// @produce json +// @success 200 {object} dto.UserTeams "Teams fetched successfully" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams [get] func getAllTeams(writer http.ResponseWriter, request *http.Request) { user := middleware.GetLogin(request) response, err := controller.GetUserTeams(database, user) @@ -79,6 +102,19 @@ func getAllTeams(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get a team by ID +// @id get-team +// @tags team +// @produce json +// @param teamID path string true "ID of the team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.TeamModel "Team updated successfully" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID} [get] func getTeam(writer http.ResponseWriter, request *http.Request) { teamID := middleware.GetTeamID(request) @@ -94,6 +130,22 @@ func getTeam(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Update existing team +// @id update-team +// @tags team +// @accept json +// @produce json +// @param teamID path string true "ID of the team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param team body dto.TeamModel true "Updated team data" +// @success 200 {object} dto.SaveTeamResponse "Team updated successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID} [patch] func updateTeam(writer http.ResponseWriter, request *http.Request) { team := dto.TeamModel{} err := render.Bind(request, &team) @@ -115,6 +167,20 @@ func updateTeam(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Delete a team +// @id delete-team +// @tags team +// @produce json +// @param teamID path string true "ID of the team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.SaveTeamResponse "Team has been successfully deleted" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID} [delete] func deleteTeam(writer http.ResponseWriter, request *http.Request) { userLogin := middleware.GetLogin(request) teamID := middleware.GetTeamID(request) @@ -130,6 +196,19 @@ func deleteTeam(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get users of a team +// @id get-team-users +// @tags team +// @produce json +// @param teamID path string true "ID of the team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.TeamMembers "Users fetched successfully" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID}/users [get] func getTeamUsers(writer http.ResponseWriter, request *http.Request) { teamID := middleware.GetTeamID(request) @@ -145,6 +224,22 @@ func getTeamUsers(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Set users of a team +// @id set-team-users +// @tags team +// @accept json +// @produce json +// @param teamID path string true "ID of the team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param usernames body dto.TeamMembers true "Usernames to set as team members" +// @success 200 {object} dto.TeamMembers "Team updated successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID}/users [put] func setTeamUsers(writer http.ResponseWriter, request *http.Request) { members := dto.TeamMembers{} err := render.Bind(request, &members) @@ -167,6 +262,22 @@ func setTeamUsers(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Add users to a team +// @id add-team-users +// @tags team +// @accept json +// @produce json +// @param teamID path string true "ID of the team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param usernames body dto.TeamMembers true "Usernames to add to the team" +// @success 200 {object} dto.TeamMembers "Team updated successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID}/users [post] func addTeamUsers(writer http.ResponseWriter, request *http.Request) { members := dto.TeamMembers{} err := render.Bind(request, &members) @@ -188,6 +299,21 @@ func addTeamUsers(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Delete a user from a team +// @id delete-team-user +// @tags team +// @produce json +// @param teamID path string true "ID of the team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param teamUserID path string true "User login in methods related to teams manipulation" default(anonymous) +// @success 200 {object} dto.TeamMembers "Team updated successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID}/users/{teamUserID} [delete] func deleteTeamUser(writer http.ResponseWriter, request *http.Request) { teamID := middleware.GetTeamID(request) userID := middleware.GetTeamUserID(request) @@ -204,6 +330,19 @@ func deleteTeamUser(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get team settings +// @id get-team-settings +// @tags team +// @produce json +// @param teamID path string true "ID of the team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.TeamSettings "Team settings" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID}/settings [get] func getTeamSettings(writer http.ResponseWriter, request *http.Request) { teamID := middleware.GetTeamID(request) teamSettings, err := controller.GetTeamSettings(database, teamID) diff --git a/api/handler/team_contact.go b/api/handler/team_contact.go index 70f094db2..1ed5874ab 100644 --- a/api/handler/team_contact.go +++ b/api/handler/team_contact.go @@ -15,6 +15,22 @@ func teamContact(router chi.Router) { router.Post("/", createNewTeamContact) } +// nolint: gofmt,goimports +// +// @summary Create a new team contact +// @id create-new-team-contact +// @tags teamContact +// @accept json +// @produce json +// @param teamID path string true "The ID of team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param contact body dto.Contact true "Team contact data" +// @success 200 {object} dto.Contact "Team contact created successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID}/contacts [post] func createNewTeamContact(writer http.ResponseWriter, request *http.Request) { contact := &dto.Contact{} if err := render.Bind(request, contact); err != nil { diff --git a/api/handler/team_subscription.go b/api/handler/team_subscription.go index 4d7d29939..c913fff79 100644 --- a/api/handler/team_subscription.go +++ b/api/handler/team_subscription.go @@ -16,6 +16,22 @@ func teamSubscription(router chi.Router) { router.Post("/", createTeamSubscription) } +// nolint: gofmt,goimports +// +// @summary Create a new team subscription +// @id create-new-team-subscription +// @tags teamSubscription +// @accept json +// @produce json +// @param teamID path string true "The ID of team" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param subscription body dto.Subscription true "Team subscription data" +// @success 200 {object} dto.Subscription "Team subscription created successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 403 {object} api.ErrorForbiddenExample "Forbidden" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /teams/{teamID}/subscriptions [post] func createTeamSubscription(writer http.ResponseWriter, request *http.Request) { subscription := &dto.Subscription{} if err := render.Bind(request, subscription); err != nil { diff --git a/api/handler/trigger.go b/api/handler/trigger.go index 924f338dd..73d9f574e 100644 --- a/api/handler/trigger.go +++ b/api/handler/trigger.go @@ -30,6 +30,22 @@ func trigger(router chi.Router) { router.Get("/dump", triggerDump) } +// nolint: gofmt,goimports +// +// @summary Update existing trigger +// @id update-trigger +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param validate query bool false "For validating targets" +// @param body body dto.Trigger true "Trigger data" +// @success 200 {object} dto.SaveTriggerResponse "Updated trigger" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @failure 503 {object} api.ErrorRemoteServerUnavailableExample "Remote server unavailable" +// @router /trigger/{triggerID} [put] func updateTrigger(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) @@ -95,6 +111,16 @@ func writeErrorSaveResponse(writer http.ResponseWriter, request *http.Request, t render.JSON(writer, request, response) } +// nolint: gofmt,goimports +// +// @summary Remove trigger +// @id remove-trigger +// @tags trigger +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 "Successfully removed" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID} [delete] func removeTrigger(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) err := controller.RemoveTrigger(database, triggerID) @@ -103,6 +129,19 @@ func removeTrigger(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get an existing trigger +// @id get-trigger +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param populated query bool false "Populated" default(false) +// @success 200 {object} dto.Trigger "Trigger data" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID} [get] func getTrigger(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) @@ -140,6 +179,18 @@ func checkingTemplateFilling(request *http.Request, trigger dto.Trigger) *api.Er return nil } +// nolint: gofmt,goimports +// +// @summary Get the trigger state as at last check +// @id get-trigger-state +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.TriggerCheck "Trigger state fetched successful" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID}/state [get] func getTriggerState(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) triggerState, err := controller.GetTriggerLastCheck(database, triggerID) @@ -152,6 +203,17 @@ func getTriggerState(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get a trigger with its throttling i.e its next allowed message time +// @id get-trigger-throttling +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.ThrottlingResponse "Trigger throttle info retrieved" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @router /trigger/{triggerID}/throttling [get] func getTriggerThrottling(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) triggerState, err := controller.GetTriggerThrottling(database, triggerID) @@ -164,6 +226,16 @@ func getTriggerThrottling(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Deletes throttling for a trigger +// @id delete-trigger-throttling +// @tags trigger +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 "Trigger throttling has been deleted" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID}/throttling [delete] func deleteThrottling(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) err := controller.DeleteTriggerThrottling(database, triggerID) @@ -172,6 +244,19 @@ func deleteThrottling(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Set metrics and the trigger itself to maintenance mode +// @id set-trigger-maintenance +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param body body dto.TriggerMaintenance true "Maintenance data" +// @success 200 "Trigger or metric have been scheduled for maintenance" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID}/setMaintenance [put] func setTriggerMaintenance(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) triggerMaintenance := dto.TriggerMaintenance{} @@ -188,6 +273,17 @@ func setTriggerMaintenance(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get trigger dump +// @id get-trigger-dump +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.TriggerDump "Trigger dump" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID}/dump [get] func triggerDump(writer http.ResponseWriter, request *http.Request) { triggerID, log := prepareTriggerContext(request) diff --git a/api/handler/trigger_metrics.go b/api/handler/trigger_metrics.go index d48360b29..a1f95fa4a 100644 --- a/api/handler/trigger_metrics.go +++ b/api/handler/trigger_metrics.go @@ -21,6 +21,21 @@ func triggerMetrics(router chi.Router) { router.Delete("/nodata", deleteTriggerNodataMetrics) } +// nolint: gofmt,goimports +// +// @summary Get metrics associated with certain trigger +// @id get-trigger-metrics +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param from query string false "Start time for metrics retrieval" default(-10minutes) +// @param to query string false "End time for metrics retrieval" default(now) +// @success 200 {object} dto.TriggerMetrics "Trigger metrics retrieved successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID}/metrics [get] func getTriggerMetrics(writer http.ResponseWriter, request *http.Request) { metricSourceProvider := middleware.GetTriggerTargetsSourceProvider(request) triggerID := middleware.GetTriggerID(request) @@ -49,6 +64,19 @@ func getTriggerMetrics(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Delete metric from last check and all trigger pattern metrics +// @id delete-trigger-metric +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param name query string false "Name of the target metric" default(DevOps.my_server.hdd.freespace_mbytes) +// @success 200 "Trigger metric deleted successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID}/metrics [delete] func deleteTriggerMetric(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) @@ -64,6 +92,18 @@ func deleteTriggerMetric(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Delete all metrics from last data which are in NODATA state. It also deletes all trigger patterns of those metrics +// @id delete-trigger-nodata-metrics +// @tags trigger +// @produce json +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 "Trigger nodata metrics deleted successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID}/metrics/nodata [delete] func deleteTriggerNodataMetrics(writer http.ResponseWriter, request *http.Request) { triggerID := middleware.GetTriggerID(request) if err := controller.DeleteTriggerNodataMetrics(database, triggerID); err != nil { diff --git a/api/handler/trigger_render.go b/api/handler/trigger_render.go index c2e961211..02794c759 100644 --- a/api/handler/trigger_render.go +++ b/api/handler/trigger_render.go @@ -18,6 +18,24 @@ import ( "github.com/moira-alert/moira/plotting" ) +// nolint: gofmt,goimports +// +// @summary Render trigger metrics plot +// @id render-trigger-metrics +// @tags trigger +// @produce png +// @param triggerID path string true "Trigger ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @param target query string false "Target metric name" default(t1) +// @param from query string false "Start time for metrics retrieval" default(-1hour) +// @param to query string false "End time for metrics retrieval" default(now) +// @param timezone query string false "Timezone for rendering" default(UTC) +// @param theme query string false "Plot theme" default(light) +// @param realtime query bool false "Fetch real-time data" default(false) +// @success 200 "Rendered plot image successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/{triggerID}/render [get] func renderTrigger(writer http.ResponseWriter, request *http.Request) { sourceProvider, targetName, from, to, triggerID, fetchRealtimeData, err := getEvaluationParameters(request) if err != nil { diff --git a/api/handler/triggers.go b/api/handler/triggers.go index fe61fae70..3b78433da 100644 --- a/api/handler/triggers.go +++ b/api/handler/triggers.go @@ -38,6 +38,16 @@ func triggers(metricSourceProvider *metricSource.SourceProvider, searcher moira. } } +// nolint: gofmt,goimports +// +// @summary Get all triggers +// @id get-all-triggers +// @tags trigger +// @produce json +// @success 200 {object} dto.TriggersList "Fetched all triggers" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger [get] func getAllTriggers(writer http.ResponseWriter, request *http.Request) { triggersList, errorResponse := controller.GetAllTriggers(database) if errorResponse != nil { @@ -51,7 +61,22 @@ func getAllTriggers(writer http.ResponseWriter, request *http.Request) { } } -// createTrigger handler creates moira.Trigger. +// nolint: gofmt,goimports +// createTrigger handler creates moira.Trigger +// +// @summary Create a new trigger +// @id create-trigger +// @tags trigger +// @accept json +// @produce json +// @param validate query bool false "For validating targets" +// @param trigger body dto.Trigger true "Trigger data" +// @success 200 {object} dto.SaveTriggerResponse "Trigger created successfully" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @failure 503 {object} api.ErrorRemoteServerUnavailableExample "Remote server unavailable" +// @router /trigger [put] func createTrigger(writer http.ResponseWriter, request *http.Request) { trigger, err := getTriggerFromRequest(request) if err != nil { @@ -133,6 +158,18 @@ func getMetricTTLByTrigger(request *http.Request, trigger *dto.Trigger) time.Dur return ttl } +// nolint: gofmt,goimports +// +// @summary Validates trigger target +// @id trigger-check +// @tags trigger +// @accept json +// @produce json +// @param trigger body dto.Trigger true "Trigger data" +// @success 200 {object} dto.TriggerCheckResponse "Validation is done, see response body for validation result" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/check [put] func triggerCheck(writer http.ResponseWriter, request *http.Request) { trigger := &dto.Trigger{} response := dto.TriggerCheckResponse{} @@ -157,6 +194,26 @@ func triggerCheck(writer http.ResponseWriter, request *http.Request) { render.JSON(writer, request, response) } +// nolint: gofmt,goimports +// +// @summary Search triggers. Replaces the deprecated `page` path +// @description You can also add filtering by tags, for this purpose add query parameters tags[0]=test, tags[1]=test1 and so on +// @description For example, `/api/trigger/search?tags[0]=test&tags[1]=test1` +// @id search-triggers +// @tags trigger +// @produce json +// @param onlyProblems query boolean false "Only include problems" default(false) +// @param text query string false "Search text" default(cpu) +// @param p query integer false "Page number" default(0) +// @param size query integer false "Page size" default(10) +// @param createPager query boolean false "Create pager" default(false) +// @param pagerID query string false "Pager ID" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.TriggersList "Successfully fetched matching triggers" +// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/search [get] func searchTriggers(writer http.ResponseWriter, request *http.Request) { request.ParseForm() //nolint onlyErrors := getOnlyProblemsFlag(request) @@ -181,6 +238,17 @@ func searchTriggers(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Delete triggers pager +// @tags trigger +// @produce json +// @param pagerID path string true "Pager ID to delete" default(bcba82f5-48cf-44c0-b7d6-e1d32c64a88c) +// @success 200 {object} dto.TriggersSearchResultDeleteResponse "Successfully deleted pager" +// @failure 404 {object} api.ErrorNotFoundExample "Resource not found" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /trigger/pagers/{pagerID} [delete] func deletePager(writer http.ResponseWriter, request *http.Request) { pagerID := middleware.GetPagerID(request) diff --git a/api/handler/user.go b/api/handler/user.go index 8432f544f..17c856698 100644 --- a/api/handler/user.go +++ b/api/handler/user.go @@ -16,6 +16,15 @@ func user(router chi.Router) { router.Get("/settings", getUserSettings) } +// nolint: gofmt,goimports +// +// @summary Gets the username of the authenticated user if it is available +// @id get-user-name +// @tags user +// @produce json +// @success 200 {object} dto.User "User name fetched successfully" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @router /user [get] func getUserName(writer http.ResponseWriter, request *http.Request) { userLogin := middleware.GetLogin(request) if err := render.Render(writer, request, &dto.User{Login: userLogin}); err != nil { @@ -24,6 +33,16 @@ func getUserName(writer http.ResponseWriter, request *http.Request) { } } +// nolint: gofmt,goimports +// +// @summary Get the user's contacts and subscriptions +// @id get-user-settings +// @tags user +// @produce json +// @success 200 {object} dto.UserSettings "Settings fetched successfully" +// @failure 422 {object} api.ErrorRenderExample "Render error" +// @failure 500 {object} api.ErrorInternalServerExample "Internal server error" +// @router /user/settings [get] func getUserSettings(writer http.ResponseWriter, request *http.Request) { userLogin := middleware.GetLogin(request) userSettings, err := controller.GetUserSettings(database, userLogin) diff --git a/datatypes.go b/datatypes.go index 2c37daad9..852cba7bd 100644 --- a/datatypes.go +++ b/datatypes.go @@ -42,16 +42,16 @@ const ( // NotificationEvent represents trigger state changes event type NotificationEvent struct { - IsTriggerEvent bool `json:"trigger_event,omitempty"` - Timestamp int64 `json:"timestamp"` - Metric string `json:"metric"` - Value *float64 `json:"value,omitempty"` + IsTriggerEvent bool `json:"trigger_event,omitempty" example:"true"` + Timestamp int64 `json:"timestamp" example:"1590741878"` + Metric string `json:"metric" example:"carbon.agents.*.metricsReceived"` + Value *float64 `json:"value,omitempty" example:"70"` Values map[string]float64 `json:"values,omitempty"` - State State `json:"state"` - TriggerID string `json:"trigger_id"` + State State `json:"state" example:"OK"` + TriggerID string `json:"trigger_id" example:"5ff37996-8927-4cab-8987-970e80d8e0a8"` SubscriptionID *string `json:"sub_id,omitempty"` ContactID string `json:"contact_id,omitempty"` - OldState State `json:"old_state"` + OldState State `json:"old_state" example:"ERROR"` Message *string `json:"msg,omitempty"` MessageEventInfo *EventInfo `json:"event_message"` } @@ -70,7 +70,7 @@ type NotificationEventHistoryItem struct { // EventInfo - a base for creating messages. type EventInfo struct { Maintenance *MaintenanceInfo `json:"maintenance,omitempty"` - Interval *int64 `json:"interval,omitempty"` + Interval *int64 `json:"interval,omitempty" example:"0"` } // CreateMessage - creates a message based on EventInfo. @@ -157,14 +157,14 @@ func NotificationEventsToTemplatingEvents(events NotificationEvents) []templatin // TriggerData represents trigger object type TriggerData struct { - ID string `json:"id"` - Name string `json:"name"` - Desc string `json:"desc"` - Targets []string `json:"targets"` - WarnValue float64 `json:"warn_value"` - ErrorValue float64 `json:"error_value"` - IsRemote bool `json:"is_remote"` - Tags []string `json:"__notifier_trigger_tags"` + ID string `json:"id" example:"292516ed-4924-4154-a62c-ebe312431fce"` + Name string `json:"name" example:"Not enough disk space left"` + Desc string `json:"desc" example:"check the size of /var/log"` + Targets []string `json:"targets" example:"devOps.my_server.hdd.freespace_mbytes"` + WarnValue float64 `json:"warn_value" example:"5000"` + ErrorValue float64 `json:"error_value" example:"1000"` + IsRemote bool `json:"is_remote" example:"false"` + Tags []string `json:"__notifier_trigger_tags" example:"server,disk"` } // GetTriggerURI gets frontUri and returns triggerUrl, returns empty string on selfcheck and test notifications @@ -184,47 +184,47 @@ type Team struct { // ContactData represents contact object type ContactData struct { - Type string `json:"type"` - Value string `json:"value"` - ID string `json:"id"` - User string `json:"user"` + Type string `json:"type" example:"mail"` + Value string `json:"value" example:"devops@example.com"` + ID string `json:"id" example:"1dd38765-c5be-418d-81fa-7a5f879c2315"` + User string `json:"user" example:""` Team string `json:"team"` } // SubscriptionData represents user subscription type SubscriptionData struct { - Contacts []string `json:"contacts"` - Tags []string `json:"tags"` + Contacts []string `json:"contacts" example:"acd2db98-1659-4a2f-b227-52d71f6e3ba1"` + Tags []string `json:"tags" example:"server,cpu"` Schedule ScheduleData `json:"sched"` Plotting PlottingData `json:"plotting"` - ID string `json:"id"` - Enabled bool `json:"enabled"` - AnyTags bool `json:"any_tags"` - IgnoreWarnings bool `json:"ignore_warnings,omitempty"` - IgnoreRecoverings bool `json:"ignore_recoverings,omitempty"` - ThrottlingEnabled bool `json:"throttling"` - User string `json:"user"` - TeamID string `json:"team_id"` + ID string `json:"id" example:"292516ed-4924-4154-a62c-ebe312431fce"` + Enabled bool `json:"enabled" example:"true"` + AnyTags bool `json:"any_tags" example:"false"` + IgnoreWarnings bool `json:"ignore_warnings,omitempty" example:"false"` + IgnoreRecoverings bool `json:"ignore_recoverings,omitempty" example:"false"` + ThrottlingEnabled bool `json:"throttling" example:"false"` + User string `json:"user" example:""` + TeamID string `json:"team_id" example:"324516ed-4924-4154-a62c-eb124234fce"` } // PlottingData represents plotting settings type PlottingData struct { - Enabled bool `json:"enabled"` - Theme string `json:"theme"` + Enabled bool `json:"enabled" example:"true"` + Theme string `json:"theme" example:"dark"` } // ScheduleData represents subscription schedule type ScheduleData struct { Days []ScheduleDataDay `json:"days"` - TimezoneOffset int64 `json:"tzOffset"` - StartOffset int64 `json:"startOffset"` - EndOffset int64 `json:"endOffset"` + TimezoneOffset int64 `json:"tzOffset" example:"-60"` + StartOffset int64 `json:"startOffset" example:"0"` + EndOffset int64 `json:"endOffset" example:"1439"` } // ScheduleDataDay represents week day of schedule type ScheduleDataDay struct { - Enabled bool `json:"enabled"` - Name string `json:"name,omitempty"` + Enabled bool `json:"enabled" example:"true"` + Name string `json:"name,omitempty" example:"Mon"` } // ScheduledNotification represent notification object @@ -233,9 +233,9 @@ type ScheduledNotification struct { Trigger TriggerData `json:"trigger"` Contact ContactData `json:"contact"` Plotting PlottingData `json:"plotting"` - Throttled bool `json:"throttled"` - SendFail int `json:"send_fail"` - Timestamp int64 `json:"timestamp"` + Throttled bool `json:"throttled" example:"false"` + SendFail int `json:"send_fail" example:"0"` + Timestamp int64 `json:"timestamp" example:"1594471927"` } // MatchedMetric represents parsed and matched metric data @@ -266,23 +266,23 @@ const ( // Trigger represents trigger data object type Trigger struct { - ID string `json:"id"` - Name string `json:"name"` - Desc *string `json:"desc,omitempty"` - Targets []string `json:"targets"` - WarnValue *float64 `json:"warn_value"` - ErrorValue *float64 `json:"error_value"` - TriggerType string `json:"trigger_type"` - Tags []string `json:"tags"` - TTLState *TTLState `json:"ttl_state,omitempty"` - TTL int64 `json:"ttl,omitempty"` + ID string `json:"id" example:"292516ed-4924-4154-a62c-ebe312431fce"` + Name string `json:"name" example:"Not enough disk space left"` + Desc *string `json:"desc,omitempty" example:"check the size of /var/log"` + Targets []string `json:"targets" example:"devOps.my_server.hdd.freespace_mbytes"` + WarnValue *float64 `json:"warn_value" example:"5000"` + ErrorValue *float64 `json:"error_value" example:"1000"` + TriggerType string `json:"trigger_type" example:"rising"` + Tags []string `json:"tags" example:"server,disk"` + TTLState *TTLState `json:"ttl_state,omitempty" example:"NODATA"` + TTL int64 `json:"ttl,omitempty" example:"600"` Schedule *ScheduleData `json:"sched,omitempty"` - Expression *string `json:"expression,omitempty"` + Expression *string `json:"expression,omitempty" example:""` PythonExpression *string `json:"python_expression,omitempty"` - Patterns []string `json:"patterns"` - IsRemote bool `json:"is_remote"` - MuteNewMetrics bool `json:"mute_new_metrics"` - AloneMetrics map[string]bool `json:"alone_metrics"` + Patterns []string `json:"patterns" example:""` + IsRemote bool `json:"is_remote" example:"false"` + MuteNewMetrics bool `json:"mute_new_metrics" example:"false"` + AloneMetrics map[string]bool `json:"alone_metrics" example:"t1:true"` CreatedAt *int64 `json:"created_at"` UpdatedAt *int64 `json:"updated_at"` CreatedBy string `json:"created_by"` @@ -292,7 +292,7 @@ type Trigger struct { // TriggerCheck represents trigger data with last check data and check timestamp type TriggerCheck struct { Trigger - Throttling int64 `json:"throttling"` + Throttling int64 `json:"throttling" example:"0"` LastCheck CheckData `json:"last_check"` Highlights map[string]string `json:"highlights"` } @@ -309,15 +309,15 @@ type CheckData struct { // MetricsToTargetRelation is a map that holds relation between metric names that was alone during last // check and targets that fetched this metric // {"t1": "metric.name.1", "t2": "metric.name.2"} - MetricsToTargetRelation map[string]string `json:"metrics_to_target_relation"` - Score int64 `json:"score"` - State State `json:"state"` - Maintenance int64 `json:"maintenance,omitempty"` + MetricsToTargetRelation map[string]string `json:"metrics_to_target_relation" example:"t1:metric.name.1,t2:metric.name.2"` + Score int64 `json:"score" example:"100"` + State State `json:"state" example:"OK"` + Maintenance int64 `json:"maintenance,omitempty" example:"0"` MaintenanceInfo MaintenanceInfo `json:"maintenance_info"` - Timestamp int64 `json:"timestamp,omitempty"` - EventTimestamp int64 `json:"event_timestamp,omitempty"` - LastSuccessfulCheckTimestamp int64 `json:"last_successful_check_timestamp"` - Suppressed bool `json:"suppressed,omitempty"` + Timestamp int64 `json:"timestamp,omitempty" example:"1590741916"` + EventTimestamp int64 `json:"event_timestamp,omitempty" example:"1590741878"` + LastSuccessfulCheckTimestamp int64 `json:"last_successful_check_timestamp" example:"1590741916"` + Suppressed bool `json:"suppressed,omitempty" example:"true"` SuppressedState State `json:"suppressed_state,omitempty"` Message string `json:"msg,omitempty"` } @@ -334,14 +334,14 @@ func (checkData *CheckData) RemoveMetricsToTargetRelation() { // MetricState represents metric state data for given timestamp type MetricState struct { - EventTimestamp int64 `json:"event_timestamp"` - State State `json:"state"` - Suppressed bool `json:"suppressed"` + EventTimestamp int64 `json:"event_timestamp" example:"1590741878"` + State State `json:"state" example:"OK"` + Suppressed bool `json:"suppressed" example:"false"` SuppressedState State `json:"suppressed_state,omitempty"` - Timestamp int64 `json:"timestamp"` - Value *float64 `json:"value,omitempty"` + Timestamp int64 `json:"timestamp" example:"1590741878"` + Value *float64 `json:"value,omitempty" example:"70"` Values map[string]float64 `json:"values,omitempty"` - Maintenance int64 `json:"maintenance,omitempty"` + Maintenance int64 `json:"maintenance,omitempty" example:"0"` MaintenanceInfo MaintenanceInfo `json:"maintenance_info"` // AloneMetrics map[string]string `json:"alone_metrics"` // represents a relation between name of alone metrics and their targets } @@ -360,9 +360,9 @@ func (metricState *MetricState) GetMaintenance() (MaintenanceInfo, int64) { // MaintenanceInfo represents user and time set/unset maintenance type MaintenanceInfo struct { StartUser *string `json:"setup_user"` - StartTime *int64 `json:"setup_time"` + StartTime *int64 `json:"setup_time" example:"0"` StopUser *string `json:"remove_user"` - StopTime *int64 `json:"remove_time"` + StopTime *int64 `json:"remove_time" example:"0"` } // Set maintanace start and stop users and times diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 000000000..a8f0f0241 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,3 @@ +// Will be generated in `Dockerfile.api` by the `make spec` command for swagger docs. DO NOT EDIT. + +package docs \ No newline at end of file diff --git a/go.mod b/go.mod index 14e7107fe..09559cd88 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) -require github.com/mitchellh/mapstructure v1.5.0 +require ( + github.com/mitchellh/mapstructure v1.5.0 + github.com/swaggo/http-swagger v1.3.4 +) require ( bitbucket.org/tebeka/strftime v0.0.0-20140926081919-2194253a23c0 // indirect @@ -146,7 +149,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.14.0 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.1 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tinylib/msgp v1.1.8 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect @@ -158,12 +161,12 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.6.0 // indirect + golang.org/x/crypto v0.12.0 // indirect golang.org/x/exp v0.0.0-20200924195034-c827fd4f18b9 // indirect golang.org/x/image v0.5.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect gonum.org/v1/gonum v0.12.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect @@ -173,13 +176,24 @@ require ( ) require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.4 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.11 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/swaggo/swag v1.8.12 // indirect + golang.org/x/tools v0.11.1 // indirect ) // Have to exclude version that is incorectly retracted by authors diff --git a/go.sum b/go.sum index 09b73f652..b7930c20f 100644 --- a/go.sum +++ b/go.sum @@ -399,13 +399,16 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/JaderDias/movingmedian v0.0.0-20220813210630-d8c6b6de8835 h1:mbxQnovjDz5SvlatpxkbiMvybHH1hsSEu6OhPDLlfU8= github.com/JaderDias/movingmedian v0.0.0-20220813210630-d8c6b6de8835/go.mod h1:zsfWLaDctbM7aV1TsQAwkVswuKQ0k7PK4rjC1VZqpbI= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= @@ -602,6 +605,21 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= @@ -793,6 +811,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -824,7 +844,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -842,6 +862,11 @@ github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ= github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8= @@ -903,6 +928,7 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -1053,12 +1079,19 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= +github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= +github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w= +github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= @@ -1131,8 +1164,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1178,6 +1211,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1245,8 +1279,9 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1386,8 +1421,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1409,8 +1444,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1478,6 +1513,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= +golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1739,6 +1776,7 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1768,6 +1806,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=