Skip to content

Commit

Permalink
a compiling proof of concept
Browse files Browse the repository at this point in the history
- abstract type
- http, tcp, icmp & dns monitor types
- unmarshal from json into any monitor type
  • Loading branch information
matejkramny committed Feb 5, 2017
1 parent 0cd6fa1 commit 36bf228
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 257 deletions.
53 changes: 51 additions & 2 deletions api.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,56 @@
package cachet

import (
"bytes"
"crypto/tls"
"errors"
"io/ioutil"
"net/http"
)

type CachetAPI struct {
Url string `json:"api_url"`
Token string `json:"api_token"`
URL string `json:"url"`
Token string `json:"token"`
Insecure bool `json:"insecure"`
}

func (api CachetAPI) Ping() error {
resp, _, err := api.NewRequest("GET", "/ping", nil)
if err != nil {
return err
}

if resp.StatusCode != 200 {
return errors.New("API Responded with non-200 status code")
}

return nil
}

func (api CachetAPI) NewRequest(requestType, url string, reqBody []byte) (*http.Response, []byte, error) {
req, err := http.NewRequest(requestType, api.URL+url, bytes.NewBuffer(reqBody))

req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Cachet-Token", api.Token)

transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
}
if api.Insecure {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}

client := &http.Client{
Transport: transport,
}

res, err := client.Do(req)
if err != nil {
return nil, []byte{}, err
}

defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)

return res, body, nil
}
117 changes: 75 additions & 42 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ import (
"os/signal"
"sync"

"strings"

"github.com/Sirupsen/logrus"
cachet "github.com/castawaylabs/cachet-monitor"
docopt "github.com/docopt/docopt-go"
yaml "gopkg.in/yaml.v2"
)

const usage = `cachet-monitor
Expand All @@ -26,19 +23,19 @@ Usage:
cachet-monitor print-config
Arguments:
PATH path to config.yml
PATH path to config.json
LOGPATH path to log output (defaults to STDOUT)
NAME name of this logger
Examples:
cachet-monitor -c /root/cachet-monitor.yml
cachet-monitor -c /root/cachet-monitor.yml --log=/var/log/cachet-monitor.log --name="development machine"
cachet-monitor -c /root/cachet-monitor.json
cachet-monitor -c /root/cachet-monitor.json --log=/var/log/cachet-monitor.log --name="development machine"
Options:
-c PATH.yml --config PATH Path to configuration file
-h --help Show this screen.
--version Show version
print-config Print example configuration
-c PATH.json --config PATH Path to configuration file
-h --help Show this screen.
--version Show version
print-config Print example configuration
Environment varaibles:
CACHET_API override API url from configuration
Expand All @@ -59,43 +56,50 @@ func main() {
logrus.SetOutput(getLogger(arguments["--log"]))

if len(os.Getenv("CACHET_API")) > 0 {
cfg.APIUrl = os.Getenv("CACHET_API")
cfg.API.URL = os.Getenv("CACHET_API")
}
if len(os.Getenv("CACHET_TOKEN")) > 0 {
cfg.APIToken = os.Getenv("CACHET_TOKEN")
cfg.API.Token = os.Getenv("CACHET_TOKEN")
}
if len(os.Getenv("CACHET_DEV")) > 0 {
logrus.SetLevel(logrus.DebugLevel)
}

if err := cfg.ValidateConfiguration(); err != nil {
panic(err)
if valid := cfg.Validate(); !valid {
logrus.Errorf("Invalid configuration")
os.Exit(1)
}

logrus.Infof("System: %s\nAPI: %s\nMonitors: %d\n\n", cfg.SystemName, cfg.APIUrl, len(cfg.Monitors))
logrus.Infof("System: %s\nAPI: %s\nMonitors: %d\n\n", cfg.SystemName, cfg.API.URL, len(cfg.Monitors))
logrus.Infof("Pinging cachet")
if err := cfg.API.Ping(); err != nil {
logrus.Warnf("Cannot ping cachet!\n%v", err)
}

wg := &sync.WaitGroup{}
for _, mon := range cfg.Monitors {
for _, monitorInterface := range cfg.Monitors {
mon := monitorInterface.GetMonitor()
l := logrus.WithFields(logrus.Fields{
"name": mon.Name,
"interval": mon.CheckInterval,
"method": mon.Method,
"url": mon.URL,
"timeout": mon.HttpTimeout,
"interval": mon.Interval,
"target": mon.Target,
"timeout": mon.Timeout,
})
l.Info(" Starting monitor")

// print features
if len(mon.HttpHeaders) > 0 {
for _, h := range mon.HttpHeaders {
logrus.Infof(" - HTTP-Header '%s' '%s'", h.Name, h.Value)
if mon.Type == "http" {
httpMonitor := monitorInterface.(*cachet.HTTPMonitor)

for k, v := range httpMonitor.Headers {
logrus.Infof(" - HTTP-Header '%s' '%s'", k, v)
}
if httpMonitor.ExpectedStatusCode > 0 {
l.Infof(" - Expect HTTP %d", httpMonitor.ExpectedStatusCode)
}
if len(httpMonitor.ExpectedBody) > 0 {
l.Infof(" - Expect Body to match \"%v\"", httpMonitor.ExpectedBody)
}
}
if mon.ExpectedStatusCode > 0 {
l.Infof(" - Expect HTTP %d", mon.ExpectedStatusCode)
}
if len(mon.ExpectedBody) > 0 {
l.Infof(" - Expect Body to match \"%v\"", mon.ExpectedBody)
}
if mon.MetricID > 0 {
l.Infof(" - Log lag to metric id %d\n", mon.MetricID)
Expand All @@ -113,23 +117,24 @@ func main() {

logrus.Warnf("Abort: Waiting monitors to finish")
for _, mon := range cfg.Monitors {
mon.Stop()
mon.(*cachet.AbstractMonitor).Stop()
}

wg.Wait()
}

func getLogger(logPath *string) *os.File {
if logPath == nil || len(*logPath) == 0 {
func getLogger(logPath interface{}) *os.File {
if logPath == nil || len(logPath.(string)) == 0 {
return os.Stdout
}

if file, err := os.Create(logPath); err != nil {
file, err := os.Create(logPath.(string))
if err != nil {
logrus.Errorf("Unable to open file '%v' for logging: \n%v", logPath, err)
os.Exit(1)
} else {
return file
}

return file
}

func getConfiguration(path string) (*cachet.CachetMonitor, error) {
Expand Down Expand Up @@ -157,15 +162,43 @@ func getConfiguration(path string) (*cachet.CachetMonitor, error) {
}
}

// test file path for yml
if strings.HasSuffix(path, ".yml") || strings.HasSuffix(path, ".yaml") {
err = yaml.Unmarshal(data, &cfg)
} else {
err = json.Unmarshal(data, &cfg)
if err = json.Unmarshal(data, &cfg); err != nil {
logrus.Warnf("Unable to parse configuration file")
}

if err != nil {
logrus.Warnf("Unable to parse configuration file")
cfg.Monitors = make([]cachet.MonitorInterface, len(cfg.RawMonitors))
for index, rawMonitor := range cfg.RawMonitors {
var abstract cachet.AbstractMonitor
if err := json.Unmarshal(rawMonitor, &abstract); err != nil {
logrus.Errorf("Unable to unmarshal monitor (index: %d): %v", index, err)
continue
}

var t cachet.MonitorInterface
var err error

switch abstract.Type {
case "http", "":
var s cachet.HTTPMonitor
err = json.Unmarshal(rawMonitor, &s)
t = &s
case "dns":
// t = cachet.DNSMonitor
case "icmp":
// t = cachet.ICMPMonitor
case "tcp":
// t = cachet.TCPMonitor
default:
logrus.Errorf("Invalid monitor type (index: %d) %v", index, abstract.Type)
continue
}

if err != nil {
logrus.Errorf("Unable to unmarshal monitor to type (index: %d): %v", index, err)
continue
}

cfg.Monitors[index] = t
}

return &cfg, err
Expand Down
32 changes: 21 additions & 11 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
package cachet

import (
"fmt"
"net"
"os"
"time"

"encoding/json"

"github.com/Sirupsen/logrus"
)

type CachetMonitor struct {
Name string `json:"system_name"`
API CachetAPI `json:"api"`
Monitors []*Monitor `json:"monitors"`
SystemName string `json:"system_name"`
API CachetAPI `json:"api"`
RawMonitors []json.RawMessage `json:"monitors"`

Monitors []MonitorInterface `json:"-"`
}

// Validate configuration
func (cfg *CachetMonitor) Validate() bool {
valid := true

if len(cfg.Name) == 0 {
if len(cfg.SystemName) == 0 {
// get hostname
cfg.Name = getHostname()
cfg.SystemName = getHostname()
}

if len(cfg.API.Token) == 0 || len(cfg.API.Url) == 0 {
fmt.Println(cfg.API)
if len(cfg.API.Token) == 0 || len(cfg.API.URL) == 0 {
logrus.Warnf("API URL or API Token missing.\nGet help at https://github.com/castawaylabs/cachet-monitor")
valid = false
}
Expand All @@ -32,7 +40,7 @@ func (cfg *CachetMonitor) Validate() bool {
}

for _, monitor := range cfg.Monitors {
if err := monitor.Validate(); !valid {
if errs := monitor.Validate(); len(errs) > 0 {
valid = false
}
}
Expand All @@ -48,11 +56,13 @@ func getHostname() string {
}

addrs, err := net.InterfaceAddrs()
if err != nil {
if err != nil || len(addrs) == 0 {
return "unknown"
}

for _, addr := range addrs {
return addr.String()
}
return addrs[0].String()
}

func getMs() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}
3 changes: 3 additions & 0 deletions dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package cachet

type DNSMonitor struct{}
19 changes: 9 additions & 10 deletions example.config.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
{
"api_url": "https://demo.cachethq.io/api/v1",
"api_token": "9yMHsdioQosnyVK4iCVR",
"insecure_api": true,
"api": {
"url": "https://demo.cachethq.io/api/v1",
"token": "9yMHsdioQosnyVK4iCVR",
"insecure": true
},
"monitors": [
{
"name": "google",
"url": "https://google.com",
"url": "https://google.com",
"threshold": 80,
"component_id": 1,
"interval": 10,
"timeout": 5,
"headers": [
{
"header": "Authorization",
"value": "Basic <hash>"
}
],
"headers": {
"Authorization": "Basic <hash>"
},
"expected_status_code": 200,
"strict_tls": true
}
Expand Down
16 changes: 0 additions & 16 deletions example.config.yml

This file was deleted.

Loading

0 comments on commit 36bf228

Please sign in to comment.