From 3babb912c4ef5004ac2a140e765202b25c0786a3 Mon Sep 17 00:00:00 2001 From: jessicatoscani Date: Fri, 4 Aug 2023 14:40:34 +0200 Subject: [PATCH] api v2 --- cmd/status.go | 7 +-- pkg/status/status.go | 121 ++++++++++++++++++++++--------------------- 2 files changed, 66 insertions(+), 62 deletions(-) diff --git a/cmd/status.go b/cmd/status.go index 3b0efcf5..f473a248 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -62,8 +62,8 @@ func statusShow() error { t.Append([]string{"Services", buf.String()}) buf.Reset() - // Get the impacted services by zone (incidents and maintenances) - incidents, maintenances, err := status.GetIncidents() + // Get the active incidents with impacted services by zone + incidents, err := status.Incidents.GetActiveEvents(status.Services) if err != nil { return err } @@ -79,7 +79,8 @@ func statusShow() error { t.Append([]string{"Incidents", buf.String()}) buf.Reset() - // Show maintenances currently taking place + // Get the active maintenances with impacted services by zone + maintenances, err := status.Maintenanances.GetActiveEvents(status.Services) if len(maintenances) > 0 { mt := table.NewEmbeddedTable(buf) mt.Table.AppendBulk(maintenances) diff --git a/pkg/status/status.go b/pkg/status/status.go index 1d393e29..e537342e 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -8,75 +8,37 @@ import ( "time" ) -// https://www.statuspal.io/api-docs +// https://www.statuspal.io/api-docs/v2 const ( - statusPalURL = "https://statuspal.eu/api/v1/status_pages/" + statusPalURL = "https://statuspal.eu/api/v2/status_pages/" statusContentPage = "application/json; charset=utf-8" dateLayout = "2006-01-02T15:04:05" IncidentTypeScheduled = "scheduled" ) // https://www.statuspal.io/api-docs#tag/Status/operation/getStatusPageStatus + +// Exoscale Services: Parent / child services +// Parent services are type StatusPalStatus struct { - // Services: all the (parent) services of the StatusPage with the current incident type + // Services: all the services of the StatusPage with the current incident type Services Services `json:"services"` - // Maintenances only contains the future scheduled maintenances - // Ongoing maintenances are in Incidents - // Incidents - Incidents []Incident `json:"incidents"` + // Active Incidents and Maintenances + Incidents Events `json:"incidents"` + Maintenanances Events `json:"maintenances"` } // Get the status of global services (status of global or zones, no details of impacted services) func (s StatusPalStatus) GetStatusByZone() ([][]string, error) { global := make([][]string, len(s.Services)) - for _, svc := range s.Services { + for index, svc := range s.Services { state := svc.getIncidentType() - global = append(global, []string{*svc.Name, state}) + global[index] = []string{*svc.Name, state} } return global, nil } -func (s StatusPalStatus) GetIncidents() ([][]string, [][]string, error) { - var incidents IncidentsDetails - var maintenances IncidentsDetails - - services := s.Services - // In Incidents, we have maintenances and incidents currently taking place - // We need to show them in different tables - for _, event := range s.Incidents { - // Get all the services impacted by the incident (name and id) - // Child and parent are all mixed, we need to rebuild the dependency - for _, impacted := range event.Services { - if services.isParentService(*impacted.Id) { - continue - } - svcName, err := services.getServiceNamebyId(*impacted.Id) - if err != nil { - return nil, nil, err - } - started, err := time.Parse(dateLayout, *event.StartsAt) - if err != nil { - return nil, nil, err - } - startTimeUTC := started.Format(time.RFC822) - eventDetails := []string{svcName, *event.Title} - if *event.Type == IncidentTypeScheduled { - eventDetails = append(eventDetails, "scheduled at "+startTimeUTC) - maintenances = append(maintenances, eventDetails) - } else { - - eventDetails = append(eventDetails, fmt.Sprint(*event.Type), "since "+startTimeUTC) - incidents = append(incidents, eventDetails) - } - } - } - // Sort by zones - sort.Sort(incidents) - sort.Sort(maintenances) - return incidents, maintenances, nil -} - // A service can contains several child services // In our case: // - Parent services = Global and all the zones @@ -108,21 +70,62 @@ func (s *Service) getIncidentType() string { } } -type Incident struct { +// Active Maintenance or Incident +type Event struct { Id *int `json:"id,omitempty"` Title *string `json:"title,omitempty"` // The time at which the incident/maintenance started(UTC). StartsAt *string `json:"starts_at"` // Type of current incident (major, minor, scheduled) + Type *string `json:"type"` - // Services impacted (only id and name) - Services Services `json:"services"` + + // Services impacted + ServiceIds []int `json:"service_ids"` +} + +type Events []Event + +// Get Active Incidents or Maintenances with the full service name (Zone+Product) +func (e Events) GetActiveEvents(services Services) ([][]string, error) { + var events EventsDetails + + for _, event := range e { + // Get all the services impacted by the incident (name and id) + // Child and parent are all mixed, we need to rebuild the dependency + for _, impacted := range event.ServiceIds { + if services.IsParentService(impacted) { + continue + } + svcName, err := services.GetServiceNamebyId(impacted) + if err != nil { + return nil, err + } + started, err := time.Parse(dateLayout, *event.StartsAt) + if err != nil { + return nil, err + } + startTimeUTC := started.Format(time.RFC822) + eventDetails := []string{svcName, *event.Title} + if *event.Type == IncidentTypeScheduled { + eventDetails = append(eventDetails, "scheduled at "+startTimeUTC) + events = append(events, eventDetails) + } else { + + eventDetails = append(eventDetails, fmt.Sprint(*event.Type), "since "+startTimeUTC) + events = append(events, eventDetails) + } + } + } + // Sort by zones + sort.Sort(events) + return events, nil } type Services []Service // We have 2 levels of services, check if a service is a parent -func (s Services) isParentService(id int) bool { +func (s Services) IsParentService(id int) bool { for _, service := range s { if service.Id != nil && *service.Id == id { return true @@ -133,7 +136,7 @@ func (s Services) isParentService(id int) bool { } // Return the Zone and the impacted service = fullname (parent svc + child svc) -func (s Services) getServiceNamebyId(id int) (string, error) { +func (s Services) GetServiceNamebyId(id int) (string, error) { // For all zones / global services for _, parentService := range s { // id provided is a parent service, return the name @@ -153,22 +156,22 @@ func (s Services) getServiceNamebyId(id int) (string, error) { } // Details of incident: zone, service, description, start time and type -type IncidentsDetails [][]string +type EventsDetails [][]string // Implement Sort interface -func (t IncidentsDetails) Less(i, j int) bool { +func (t EventsDetails) Less(i, j int) bool { return t[i][0] < t[j][0] } -func (t IncidentsDetails) Len() int { +func (t EventsDetails) Len() int { return len(t) } -func (t IncidentsDetails) Swap(i, j int) { +func (t EventsDetails) Swap(i, j int) { t[i], t[j] = t[j], t[i] } // Get the status of a status page func GetStatusPage(subdomain string) (*StatusPalStatus, error) { - url := statusPalURL + subdomain + "/status" + url := statusPalURL + subdomain + "/summary" resp, err := http.Get(url) if err != nil { return nil, err