From 7a5ad278bbb8810820c7ff25a3c0a270fe0b4015 Mon Sep 17 00:00:00 2001 From: Matej Kramny Date: Sun, 19 Jul 2015 20:25:34 +0100 Subject: [PATCH 1/2] Improve fail reasons, fix api crashes - Add options about TLS verification - Fix crashes when cachet presents IDs as a string - Improve fail reasons --- cachet/component.go | 18 +++++++------ cachet/config.go | 13 ++++----- cachet/incident.go | 66 ++++++++++++++++++++++++--------------------- cachet/monitor.go | 23 +++++++++++++--- cachet/request.go | 7 +++++ readme.md | 8 ++++-- 6 files changed, 85 insertions(+), 50 deletions(-) diff --git a/cachet/component.go b/cachet/component.go index bebbad4..1e0a95c 100644 --- a/cachet/component.go +++ b/cachet/component.go @@ -1,15 +1,17 @@ package cachet +import "encoding/json" + // Component Cachet model type Component struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Status int `json:"status_id"` - HumanStatus string `json:"-"` - IncidentCount int `json:"-"` - CreatedAt int `json:"created_at"` - UpdatedAt int `json:"updated_at"` + ID json.Number `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Status json.Number `json:"status_id"` + HumanStatus string `json:"-"` + IncidentCount int `json:"-"` + CreatedAt *string `json:"created_at"` + UpdatedAt *string `json:"updated_at"` } // ComponentData json response model diff --git a/cachet/config.go b/cachet/config.go index 35af034..13b73e9 100644 --- a/cachet/config.go +++ b/cachet/config.go @@ -21,11 +21,12 @@ var Logger *log.Logger // CachetConfig is the monitoring tool configuration type CachetConfig struct { - APIUrl string `json:"api_url"` - APIToken string `json:"api_token"` - Monitors []*Monitor `json:"monitors"` - SystemName string `json:"system_name"` - LogPath string `json:"log_path"` + APIUrl string `json:"api_url"` + APIToken string `json:"api_token"` + Monitors []*Monitor `json:"monitors"` + SystemName string `json:"system_name"` + LogPath string `json:"log_path"` + InsecureAPI bool `json:"insecure_api"` } func init() { @@ -107,7 +108,7 @@ func init() { } } - flags := log.Llongfile|log.Ldate|log.Ltime + flags := log.Llongfile | log.Ldate | log.Ltime if len(os.Getenv("DEVELOPMENT")) > 0 { flags = 0 } diff --git a/cachet/incident.go b/cachet/incident.go index c8213c0..cce2c19 100644 --- a/cachet/incident.go +++ b/cachet/incident.go @@ -7,15 +7,15 @@ import ( // Incident Cachet data model type Incident struct { - ID int `json:"id"` - Name string `json:"name"` - Message string `json:"message"` - Status int `json:"status"` // 4? - HumanStatus string `json:"human_status"` - Component *Component `json:"-"` - ComponentID *int `json:"component_id"` - CreatedAt int `json:"created_at"` - UpdatedAt int `json:"updated_at"` + ID json.Number `json:"id"` + Name string `json:"name"` + Message string `json:"message"` + Status json.Number `json:"status"` // 4? + HumanStatus string `json:"human_status"` + Component *Component `json:"-"` + ComponentID *json.Number `json:"component_id"` + CreatedAt *string `json:"created_at"` + UpdatedAt *string `json:"updated_at"` } // IncidentData is a response when creating/updating an incident @@ -40,6 +40,7 @@ func GetIncidents() []Incident { err = json.Unmarshal(body, &data) if err != nil { Logger.Printf("Cannot parse incidents: %v\n", err) + panic(err) } return data.Incidents @@ -47,17 +48,19 @@ func GetIncidents() []Incident { // Send - Create or Update incident func (incident *Incident) Send() { - jsonBytes, err := json.Marshal(incident) - if err != nil { - Logger.Printf("Cannot encode incident: %v\n", err) - return - } + jsonBytes, _ := json.Marshal(map[string]interface{}{ + "name": incident.Name, + "message": incident.Message, + "status": incident.Status, + "component_id": incident.ComponentID, + "notify": true, + }) requestType := "POST" requestURL := "/incidents" - if incident.ID > 0 { + if len(incident.ID) > 0 { requestType = "PUT" - requestURL += "/" + strconv.Itoa(incident.ID) + requestURL += "/" + string(incident.ID) } resp, body, err := makeRequest(requestType, requestURL, jsonBytes) @@ -71,7 +74,7 @@ func (incident *Incident) Send() { var data IncidentData err = json.Unmarshal(body, &data) if err != nil { - Logger.Println("Cannot parse incident body.") + Logger.Println("Cannot parse incident body.", string(body)) panic(err) } else { incident.ID = data.Incident.ID @@ -89,7 +92,7 @@ func (incident *Incident) GetSimilarIncidentID() { incidents := GetIncidents() for _, inc := range incidents { - if incident.Name == inc.Name && incident.Message == inc.Message && incident.Status == inc.Status && incident.HumanStatus == inc.HumanStatus { + if incident.Name == inc.Name && incident.Message == inc.Message && incident.Status == inc.Status { incident.ID = inc.ID Logger.Printf("Updated incident id to %v\n", inc.ID) break @@ -98,7 +101,7 @@ func (incident *Incident) GetSimilarIncidentID() { } func (incident *Incident) fetchComponent() error { - _, body, err := makeRequest("GET", "/components/" + strconv.Itoa(*incident.ComponentID), nil) + _, body, err := makeRequest("GET", "/components/"+string(*incident.ComponentID), nil) if err != nil { return err } @@ -106,7 +109,7 @@ func (incident *Incident) fetchComponent() error { var data ComponentData err = json.Unmarshal(body, &data) if err != nil { - Logger.Println("Cannot parse component body.") + Logger.Println("Cannot parse component body. %v", string(body)) panic(err) } @@ -116,7 +119,7 @@ func (incident *Incident) fetchComponent() error { } func (incident *Incident) UpdateComponent() { - if incident.ComponentID == nil || *incident.ComponentID == 0 { + if incident.ComponentID == nil || len(*incident.ComponentID) == 0 { return } @@ -128,22 +131,23 @@ func (incident *Incident) UpdateComponent() { } } - switch incident.Status { + status, _ := strconv.Atoi(string(incident.Status)) + switch status { case 1, 2, 3: - if incident.Component.Status == 3 { - incident.Component.Status = 4 + if incident.Component.Status == "3" { + incident.Component.Status = "4" } else { - incident.Component.Status = 3 + incident.Component.Status = "3" } case 4: - incident.Component.Status = 1 + incident.Component.Status = "1" } jsonBytes, _ := json.Marshal(map[string]interface{}{ "status": incident.Component.Status, }) - resp, _, err := makeRequest("PUT", "/components/" + strconv.Itoa(incident.Component.ID), jsonBytes) + resp, _, err := makeRequest("PUT", "/components/"+string(incident.Component.ID), jsonBytes) if err != nil || resp.StatusCode != 200 { Logger.Printf("Could not update component: (resp code %d) %v", resp.StatusCode, err) return @@ -152,24 +156,24 @@ func (incident *Incident) UpdateComponent() { // SetInvestigating sets status to Investigating func (incident *Incident) SetInvestigating() { - incident.Status = 1 + incident.Status = "1" incident.HumanStatus = "Investigating" } // SetIdentified sets status to Identified func (incident *Incident) SetIdentified() { - incident.Status = 2 + incident.Status = "2" incident.HumanStatus = "Identified" } // SetWatching sets status to Watching func (incident *Incident) SetWatching() { - incident.Status = 3 + incident.Status = "3" incident.HumanStatus = "Watching" } // SetFixed sets status to Fixed func (incident *Incident) SetFixed() { - incident.Status = 4 + incident.Status = "4" incident.HumanStatus = "Fixed" } diff --git a/cachet/monitor.go b/cachet/monitor.go index 616ab74..413a418 100644 --- a/cachet/monitor.go +++ b/cachet/monitor.go @@ -1,7 +1,10 @@ package cachet import ( + "crypto/tls" + "encoding/json" "net/http" + "strconv" "time" ) @@ -15,6 +18,7 @@ type Monitor struct { Threshold float32 `json:"threshold"` ComponentID *int `json:"component_id"` ExpectedStatusCode int `json:"expected_status_code"` + StrictTLS *bool `json:"strict_tls"` History []bool `json:"-"` LastFailReason *string `json:"-"` @@ -42,6 +46,12 @@ func (monitor *Monitor) doRequest() bool { client := &http.Client{ Timeout: timeout, } + if monitor.StrictTLS != nil && *monitor.StrictTLS == false { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } + resp, err := client.Get(monitor.URL) if err != nil { errString := err.Error() @@ -51,7 +61,13 @@ func (monitor *Monitor) doRequest() bool { defer resp.Body.Close() - return resp.StatusCode == monitor.ExpectedStatusCode + if resp.StatusCode != monitor.ExpectedStatusCode { + failReason := "Unexpected response code: " + strconv.Itoa(resp.StatusCode) + ". Expected " + strconv.Itoa(monitor.ExpectedStatusCode) + monitor.LastFailReason = &failReason + return false + } + + return true } // AnalyseData decides if the monitor is statistically up or down and creates / resolves an incident @@ -76,10 +92,11 @@ func (monitor *Monitor) AnalyseData() { // is down, create an incident Logger.Println("Creating incident...") + component_id := json.Number(strconv.Itoa(*monitor.ComponentID)) monitor.Incident = &Incident{ Name: monitor.Name + " - " + Config.SystemName, - Message: monitor.Name + " failed", - ComponentID: monitor.ComponentID, + Message: monitor.Name + " check failed", + ComponentID: &component_id, } if monitor.LastFailReason != nil { diff --git a/cachet/request.go b/cachet/request.go index 644eb76..40f92b6 100644 --- a/cachet/request.go +++ b/cachet/request.go @@ -2,6 +2,7 @@ package cachet import ( "bytes" + "crypto/tls" "io/ioutil" "net/http" ) @@ -13,6 +14,12 @@ func makeRequest(requestType string, url string, reqBody []byte) (*http.Response req.Header.Set("X-Cachet-Token", Config.APIToken) client := &http.Client{} + if Config.InsecureAPI == true { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } + res, err := client.Do(req) if err != nil { return nil, []byte{}, err diff --git a/readme.md b/readme.md index ba98abf..0ab084f 100644 --- a/readme.md +++ b/readme.md @@ -40,15 +40,19 @@ Configuration "component_id": 0, "threshold": 80, "component_id": null, - "expected_status_code": 200 + "expected_status_code": 200, + "strict_tls": true } - ] + ], + "insecure_api": false } ``` *Notes:* - `metric_id` is optional +- `insecure_api` if true it will ignore HTTPS certificate errors (eg if self-signed) +- `strict_tls` if false (true is default) it will ignore HTTPS certificate errors (eg if monitor uses self-signed certificate) - `component_id` is optional - `threshold` is a percentage - `expected_status_code` is a http response code From 2b4097e90aca3b398c0005841de8f7bebe50e5d4 Mon Sep 17 00:00:00 2001 From: Matej Kramny Date: Sun, 19 Jul 2015 20:27:26 +0100 Subject: [PATCH 2/2] Update example config with default values --- example.config.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/example.config.json b/example.config.json index 737a2b1..c8f7fd4 100644 --- a/example.config.json +++ b/example.config.json @@ -8,7 +8,9 @@ "metric_id": 1, "threshold": 80, "component_id": null, - "expected_status_code": 200 + "expected_status_code": 200, + "strict_tls": true } - ] + ], + "insecure_api": false } \ No newline at end of file