-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmain.go
300 lines (262 loc) · 8.06 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package main
import (
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"time"
"github.com/gbl08ma/cachet"
urapi "github.com/gbl08ma/uptimerobot-api"
"github.com/gorilla/mux"
)
var configFilename string
var client *cachet.Client
var ur *urapi.UptimeRobot
var config *Config
var monitorStatus map[int]urapi.MonitorStatus
var metricLastSent map[int]time.Time
var pauseMonitoringChan chan bool
var resumeMonitoringChan chan struct{}
// UpdateComponentStatus updates the specified Cachet components to the
// specified status
func UpdateComponentStatus(componentStatus int, componentIDs ...int) {
for _, id := range componentIDs {
log.Println("Updating Cachet component", id,
"to Cachet status", componentStatus)
c := cachet.Component{
ID: id,
Status: fmt.Sprintf("%d", componentStatus),
Enabled: true,
}
_, _, err := client.Components.Update(id, &c)
if err != nil {
log.Println("UpdateComponentStatus:", err)
}
}
}
// UpdateMetric updates the specified Cachet metrics, setting their value for
// the specified time
func UpdateMetric(value int, timestamp time.Time, metricIDs ...int) {
for _, id := range metricIDs {
log.Println("Updating Cachet metric", id, "to value", value)
_, _, err := client.Metrics.AddPoint(
id, value, strconv.FormatInt(timestamp.Unix(), 10))
if err != nil {
log.Println("UpdateMetric:", err)
}
}
}
// UpdateComponents checks if the status of a UR monitor changed since the last
// check, and if yes, updates all Cachet components linked to that monitor.
func UpdateComponents(monitor urapi.Monitor) {
// check if status changed
if status, ok := monitorStatus[monitor.ID]; !ok ||
monitor.Status != status {
// status changed
if components, cok := config.MonitorComponentMap[monitor.ID]; cok {
status, statusok := config.MonitorComponentStatusMap[monitor.Status]
if statusok {
UpdateComponentStatus(status, components...)
}
}
// update status
monitorStatus[monitor.ID] = monitor.Status
}
}
// UpdateMetrics updates all Cachet metrics associated with a UR monitor
func UpdateMetrics(monitor urapi.Monitor) {
// update metric, if one is defined for this monitor
if metrics, ok := config.MonitorMetricMap[monitor.ID]; ok {
if len(monitor.ResponseTimes) > 0 {
// first array entry is always (as far as we could see) the most
// recent entry
if time.Time(monitor.ResponseTimes[0].DateTime).After(metricLastSent[monitor.ID]) {
UpdateMetric(monitor.ResponseTimes[0].Value,
time.Time(monitor.ResponseTimes[0].DateTime), metrics...)
metricLastSent[monitor.ID] =
time.Time(monitor.ResponseTimes[0].DateTime)
}
}
}
}
// Refresh retrieves monitor status from Uptime Robot and updates Cachet metrics
// and components, with the associations defined in the config.
func Refresh() error {
monitors, err := ur.GetMonitors(&urapi.GetMonitorsInput{
ResponseTimes: true,
})
if err != nil {
log.Println("Refresh:", err)
return err
}
for _, monitor := range monitors {
UpdateComponents(monitor)
UpdateMetrics(monitor)
}
return nil
}
// InitialSetup dumps to the console a help message about setting up Upcachet,
// and saves an example config file.
func InitialSetup() error {
log.Println("No monitor-component map configured. To aid with " +
"configuration, here is a list of monitors:")
monitors, err := ur.GetMonitors(&urapi.GetMonitorsInput{
ResponseTimes: true,
})
if err != nil {
return err
}
for _, monitor := range monitors {
log.Println(monitor.ID, "-", monitor.FriendlyName)
}
log.Println("and here is the list of Cachet components:")
componentResponse, _, err := client.Components.GetAll()
if err != nil {
return err
}
for _, component := range componentResponse.Components {
log.Println(component.ID, "-", component.Name)
}
config.MonitorComponentMap[123] = []int{456, 789}
log.Println("To aid with configuration, an example mapping between "+
"fictious Uptime Robot monitor 123 and fictious Cachet components "+
"456 and 789 has been added to", configFilename)
return config.Save(configFilename)
}
// MonitorUptimeRobot periodically updates UR monitor status and changes Cachet
// components and metrics accordingly
func MonitorUptimeRobot() error {
waitInterval := 1 * time.Minute
account, err := ur.GetAccountDetails()
if err != nil {
return fmt.Errorf("MonitorUptimeRobot: "+
"error getting account details: %s", err)
}
log.Println("Uptime Robot account using",
account.UpMonitors+account.DownMonitors+account.PausedMonitors,
"monitors out of", account.MonitorLimit)
if len(config.MonitorComponentMap) == 0 {
return InitialSetup()
}
log.Println("UptimeRobot monitor interval is",
account.MonitorInterval, "minutes.")
if config.CheckInterval.Nanoseconds() == 0 {
// make waitInterval be half the monitor interval
waitInterval = time.Duration(account.MonitorInterval) * 30 * time.Second
log.Println("Check interval is", waitInterval.Seconds(), "seconds.")
} else {
waitInterval = config.CheckInterval
}
ticker := time.NewTicker(waitInterval)
Refresh()
paused := false
for {
select {
case <-ticker.C:
if !paused {
Refresh()
}
case exit := <-pauseMonitoringChan:
if exit {
return nil
}
paused = true
case <-resumeMonitoringChan:
paused = false
Refresh()
}
}
}
// Index handles a request for the index
func Index(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Upcachet Uptime Robot endpoint")
}
func main() {
flag.StringVar(&configFilename, "c", defaultConfigFilename,
"config filename")
flag.Parse()
monitorStatus = make(map[int]urapi.MonitorStatus)
metricLastSent = make(map[int]time.Time)
pauseMonitoringChan = make(chan bool, 10)
resumeMonitoringChan = make(chan struct{}, 10)
log.Println("Upcachet - Uptime Robot -> Cachet bridge")
log.Println("Version", version)
log.Println("Copyright Segvault (http://segvault.tny.im) 2015-2017")
log.Println("---")
if f := os.Getenv("UPCACHET_CONFIG_FILE"); f != "" &&
configFilename == defaultConfigFilename {
configFilename = f
}
log.Print("Using config file: ", configFilename)
config = NewConfig()
// if this errors, there's no problem: we'll just use the default config
err := config.Load(configFilename)
if err != nil {
log.Print("Config defaults loaded")
config.Save(configFilename)
}
log.Print("Config loaded")
if config.CachetEndpoint == "" {
log.Println("Please specify a Cachet endpoint either through the " +
"config file, or through environment variable " +
"UPCACHET_CACHET_ENDPOINT")
return
}
log.Print("Initializing Cachet client...")
client, err = cachet.NewClient(config.CachetEndpoint, nil)
if err != nil {
log.Println("Init fail:", err)
return
}
log.Print("Pinging Cachet API endpoint...")
pong, resp, err := client.General.Ping()
if err != nil {
log.Println("Ping fail:", err)
return
}
if resp.StatusCode != 200 {
log.Println("Ping fail")
log.Printf("Result: %s\n", pong)
log.Printf("Status: %s\n", resp.Status)
if resp.StatusCode != 200 {
return
}
} else {
log.Print("Ping success")
}
if config.CachetAPIkey == "" {
log.Println("Please specify a Cachet API key either through the " +
"config file, or through environment variable " +
"UPCACHET_CACHET_APIKEY")
return
}
client.Authentication.SetTokenAuth(config.CachetAPIkey)
if config.UptimeRobotAPIkey == "" {
log.Println("Please specify a Uptime Robot API key either through " +
"the config file, or through environment variable " +
"UPCACHET_UPTIMEROBOT_APIKEY")
return
}
ur = urapi.New(config.UptimeRobotAPIkey)
if config.BindAddress != "" {
log.Println("Starting status verification server... ")
// the point of this server is just to listen on a port and serve a
// 200 OK so one can check whether the server is running with a browser
// or e.g. Uptime Robot
// it doesn't participate in the core functionality of Upcachet
r := mux.NewRouter()
r.HandleFunc("/", Index)
http.Handle("/", r)
go http.ListenAndServe(config.BindAddress, nil)
}
log.Println("Starting Uptime Robot monitor...")
err = MonitorUptimeRobot()
if err != nil {
log.Println(err)
} else {
log.Println("Cleanly exiting")
}
}