123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- package origin
- import (
- "encoding/json"
- "io/ioutil"
- "net/http"
- "strings"
- "time"
- "github.com/cloudflare/golibs/lrucache"
- )
- // StatusPage.io API docs:
- // https://www.cloudflarestatus.com/api/v2/#incidents-unresolved
- const (
- activeIncidentsURL = "https://yh6f0r4529hb.statuspage.io/api/v2/incidents/unresolved.json"
- argoTunnelKeyword = "argo tunnel"
- incidentDetailsPrefix = "https://www.cloudflarestatus.com/incidents/"
- )
- // IncidentLookup is an object that checks for active incidents in
- // the Cloudflare infrastructure.
- type IncidentLookup interface {
- ActiveIncidents() []Incident
- }
- // NewIncidentLookup returns a new IncidentLookup instance that caches its
- // results with a 1-minute TTL.
- func NewIncidentLookup() IncidentLookup {
- return newCachedIncidentLookup(fetchActiveIncidents)
- }
- type IncidentUpdate struct {
- Body string
- }
- type Incident struct {
- Name string
- ID string `json:"id"`
- Updates []IncidentUpdate `json:"incident_updates"`
- }
- type StatusPage struct {
- Incidents []Incident
- }
- func (i Incident) URL() string {
- return incidentDetailsPrefix + i.ID
- }
- func parseStatusPage(data []byte) (*StatusPage, error) {
- var result StatusPage
- err := json.Unmarshal(data, &result)
- return &result, err
- }
- func isArgoTunnelIncident(i Incident) bool {
- if strings.Contains(strings.ToLower(i.Name), argoTunnelKeyword) {
- return true
- }
- for _, u := range i.Updates {
- if strings.Contains(strings.ToLower(u.Body), argoTunnelKeyword) {
- return true
- }
- }
- return false
- }
- func fetchActiveIncidents() (incidents []Incident) {
- resp, err := http.Get(activeIncidentsURL)
- if err != nil {
- return
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return
- }
- statusPage, err := parseStatusPage(body)
- if err != nil {
- return
- }
- for _, i := range statusPage.Incidents {
- if isArgoTunnelIncident(i) {
- incidents = append(incidents, i)
- }
- }
- return incidents
- }
- type cachedIncidentLookup struct {
- cache *lrucache.LRUCache
- ttl time.Duration
- uncachedLookup func() []Incident
- }
- func newCachedIncidentLookup(uncachedLookup func() []Incident) *cachedIncidentLookup {
- return &cachedIncidentLookup{
- cache: lrucache.NewLRUCache(1),
- ttl: time.Minute,
- uncachedLookup: uncachedLookup,
- }
- }
- // We only need one cache entry. Always use the empty string as its key.
- const cacheKey = ""
- func (c *cachedIncidentLookup) ActiveIncidents() []Incident {
- if cached, ok := c.cache.GetNotStale(cacheKey); ok {
- if incidents, ok := cached.([]Incident); ok {
- return incidents
- }
- }
- incidents := c.uncachedLookup()
- c.cache.Set(cacheKey, incidents, time.Now().Add(c.ttl))
- return incidents
- }
|