Skip to content

Commit

Permalink
api v2
Browse files Browse the repository at this point in the history
  • Loading branch information
jessicatoscani committed Aug 4, 2023
1 parent 0a28ec0 commit 3babb91
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 62 deletions.
7 changes: 4 additions & 3 deletions cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)

Check failure on line 83 in cmd/status.go

View workflow job for this annotation

GitHub Actions / build

ineffectual assignment to err (ineffassign)
if len(maintenances) > 0 {
mt := table.NewEmbeddedTable(buf)
mt.Table.AppendBulk(maintenances)
Expand Down
121 changes: 62 additions & 59 deletions pkg/status/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 3babb91

Please sign in to comment.