cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.7.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

origin/cloudflare_status_page.go

117lines · modecode

1package origin
2
3import (
4 "encoding/json"
5 "io/ioutil"
6 "net/http"
7 "strings"
8 "time"
9
10 "github.com/cloudflare/golibs/lrucache"
11)
12
13// StatusPage.io API docs:
14// https://www.cloudflarestatus.com/api/v2/#incidents-unresolved
15const (
16 activeIncidentsURL = "https://yh6f0r4529hb.statuspage.io/api/v2/incidents/unresolved.json"
17 argoTunnelKeyword = "argo tunnel"
18 incidentDetailsPrefix = "https://www.cloudflarestatus.com/incidents/"
19)
20
21// IncidentLookup is an object that checks for active incidents in
22// the Cloudflare infrastructure.
23type IncidentLookup interface {
24 ActiveIncidents() []Incident
25}
26
27// NewIncidentLookup returns a new IncidentLookup instance that caches its
28// results with a 1-minute TTL.
29func NewIncidentLookup() IncidentLookup {
30 return newCachedIncidentLookup(fetchActiveIncidents)
31}
32
33type IncidentUpdate struct {
34 Body string
35}
36
37type Incident struct {
38 Name string
39 ID string `json:"id"`
40 Updates []IncidentUpdate `json:"incident_updates"`
41}
42
43type StatusPage struct {
44 Incidents []Incident
45}
46
47func (i Incident) URL() string {
48 return incidentDetailsPrefix + i.ID
49}
50
51func parseStatusPage(data []byte) (*StatusPage, error) {
52 var result StatusPage
53 err := json.Unmarshal(data, &result)
54 return &result, err
55}
56
57func isArgoTunnelIncident(i Incident) bool {
58 if strings.Contains(strings.ToLower(i.Name), argoTunnelKeyword) {
59 return true
60 }
61 for _, u := range i.Updates {
62 if strings.Contains(strings.ToLower(u.Body), argoTunnelKeyword) {
63 return true
64 }
65 }
66 return false
67}
68
69func fetchActiveIncidents() (incidents []Incident) {
70 resp, err := http.Get(activeIncidentsURL)
71 if err != nil {
72 return
73 }
74 defer resp.Body.Close()
75 body, err := ioutil.ReadAll(resp.Body)
76 if err != nil {
77 return
78 }
79 statusPage, err := parseStatusPage(body)
80 if err != nil {
81 return
82 }
83 for _, i := range statusPage.Incidents {
84 if isArgoTunnelIncident(i) {
85 incidents = append(incidents, i)
86 }
87 }
88 return incidents
89}
90
91type cachedIncidentLookup struct {
92 cache *lrucache.LRUCache
93 ttl time.Duration
94 uncachedLookup func() []Incident
95}
96
97func newCachedIncidentLookup(uncachedLookup func() []Incident) *cachedIncidentLookup {
98 return &cachedIncidentLookup{
99 cache: lrucache.NewLRUCache(1),
100 ttl: time.Minute,
101 uncachedLookup: uncachedLookup,
102 }
103}
104
105// We only need one cache entry. Always use the empty string as its key.
106const cacheKey = ""
107
108func (c *cachedIncidentLookup) ActiveIncidents() []Incident {
109 if cached, ok := c.cache.GetNotStale(cacheKey); ok {
110 if incidents, ok := cached.([]Incident); ok {
111 return incidents
112 }
113 }
114 incidents := c.uncachedLookup()
115 c.cache.Set(cacheKey, incidents, time.Now().Add(c.ttl))
116 return incidents
117}
118