Initial commit
authorSofian Brabez <sbz@6dev.net>
Thu, 24 May 2018 09:19:06 +0000 (10:19 +0100)
committerSofian Brabez <sbz@6dev.net>
Thu, 24 May 2018 09:19:06 +0000 (10:19 +0100)
etcd.go [new file with mode: 0644]
http.go [new file with mode: 0644]
main.go [new file with mode: 0644]
provider.go [new file with mode: 0644]
slackapi.go [new file with mode: 0644]
vault.go [new file with mode: 0644]

diff --git a/etcd.go b/etcd.go
new file mode 100644 (file)
index 0000000..742f95c
--- /dev/null
+++ b/etcd.go
@@ -0,0 +1,170 @@
+package main
+
+import (
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
+       "strconv"
+
+       log "github.com/sirupsen/logrus"
+)
+
+type Etcd struct {
+       URL              string
+       ConnectionStatus string
+       ClusterStatus    string
+}
+
+type EtcdHealthResult struct {
+       Health string `json:"health"`
+}
+
+type EtcdMembersEntry struct {
+       ClientURLs []string `json:"clientURLs"`
+}
+
+type EtcdMembersResult struct {
+       Members []EtcdMembersEntry `json:"members"`
+}
+
+func (e *Etcd) CheckHealth() (bool, error) {
+       healthURL := e.URL + "/health"
+       cnx := NewConnection(healthURL)
+       log.Debugf("Etcd connection on %s", healthURL)
+       resp, err := cnx.Get()
+       if err != nil {
+               return false, err
+       }
+       defer resp.Body.Close()
+
+       log.Debugf("Etcd CheckHealth response: %s", resp.Status)
+
+       buf, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               return false, err
+       }
+
+       var result EtcdHealthResult
+
+       err = json.Unmarshal(buf, &result)
+       if err != nil {
+               return false, err
+       }
+
+       log.Debugf("Etcd CheckHealth JSON result: %+v", result)
+
+       boolResult, err := strconv.ParseBool(result.Health)
+       if err != nil {
+               return false, err
+       }
+
+       return boolResult == true, nil
+}
+
+func (e *Etcd) CheckCluster() (bool, error) {
+       clusterURL := e.URL + "/v2/members"
+       cnx := NewConnection(clusterURL)
+       log.Debugf("Etcd connection on %s", clusterURL)
+       resp, err := cnx.Get()
+       if err != nil {
+               return false, err
+       }
+       defer resp.Body.Close()
+
+       log.Debugf("Etcd CheckCluster response: %s", resp.Status)
+
+       buf, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               return false, err
+       }
+
+       var result EtcdMembersResult
+
+       err = json.Unmarshal(buf, &result)
+       if err != nil {
+               return false, err
+       }
+
+       alives := make([]bool, 0, len(result.Members))
+       for _, member := range result.Members {
+               for _, client := range member.ClientURLs {
+                       alives = append(alives, getMemberHealth(client))
+               }
+       }
+
+       log.Debugf("Etcd CheckCluster JSON result: %+v", result)
+
+       log.Debugf("Etcd Cluster Members alive result: %+v", alives)
+
+       return ensureMembersAlive(alives), nil
+}
+
+func getMemberHealth(memberUrl string) bool {
+       url := memberUrl + "/health"
+       cnx := NewConnection(url)
+       log.Debugf("Etcd Member connection on %s", url)
+       resp, err := cnx.Get()
+       if err != nil {
+               log.Fatalf("Error Get: %s", err)
+       }
+       defer resp.Body.Close()
+
+       buf, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               log.Fatalf("Error ReadAll: %s", err)
+       }
+
+       var result EtcdHealthResult
+
+       err = json.Unmarshal(buf, &result)
+       if err != nil {
+               log.Fatalf("Error Unmarshal: %s", err)
+       }
+
+       boolResult, err := strconv.ParseBool(result.Health)
+       if err != nil {
+               log.Fatalf("Error ParseBool: %s", err)
+       }
+
+       return boolResult
+}
+
+func ensureMembersAlive(alives []bool) bool {
+       for _, alive := range alives {
+               if alive != true {
+                       return false
+               }
+       }
+
+       return true
+}
+
+func (e *Etcd) Test() (bool, string) {
+       // test health
+       result, err := e.CheckHealth()
+       if err != nil {
+               log.Errorf("CheckHealth error: %s", err)
+               e.ConnectionStatus = "KO"
+       }
+       e.ConnectionStatus = "OK"
+       log.Debugf("Etcd CheckHealth result: %t", result)
+
+       // test cluster
+       result, err = e.CheckCluster()
+       if err != nil {
+               log.Errorf("CheckCluster error: %s", err)
+               e.ClusterStatus = "KO"
+       }
+       e.ClusterStatus = "OK"
+       log.Debugf("Etcd CheckCluster result: %t", result)
+
+       return result, fmt.Sprintf("> %s `%s` status *%s*. Cluster status *%s*.", e.Name(), e.URL, e.ConnectionStatus, e.ClusterStatus)
+}
+
+func (e *Etcd) Name() string {
+       return "Etcd"
+}
+
+func NewEtcd(url string) CheckProvider {
+       return &Etcd{URL: url}
+}
diff --git a/http.go b/http.go
new file mode 100644 (file)
index 0000000..2024d91
--- /dev/null
+++ b/http.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+       "bytes"
+       "crypto/tls"
+       "net/http"
+       "time"
+)
+
+const (
+       connectionTimeout = time.Second * 10
+)
+
+var (
+       transport = &http.Transport{
+               TLSClientConfig: &tls.Config{
+                       InsecureSkipVerify: true,
+               },
+       }
+       client = &http.Client{
+               Timeout:   connectionTimeout,
+               Transport: transport,
+       }
+)
+
+type HeaderMap = map[string]string
+
+type Connection struct {
+       url     string
+       headers HeaderMap
+}
+
+func (c *Connection) Post(message string) (*http.Response, error) {
+       req, err := http.NewRequest("POST", c.url, bytes.NewBufferString(message))
+       if err != nil {
+               return nil, err
+       }
+
+       for k, v := range c.headers {
+               req.Header.Set(k, v)
+       }
+
+       resp, err := client.Do(req)
+       return resp, err
+}
+
+func (c *Connection) Get() (*http.Response, error) {
+       req, err := http.NewRequest("GET", c.url, nil)
+       if err != nil {
+               return nil, err
+       }
+
+       resp, err := client.Do(req)
+       return resp, err
+}
+
+func (c *Connection) Head() (*http.Response, error) {
+       req, err := http.NewRequest("HEAD", c.url, nil)
+       if err != nil {
+               return nil, err
+       }
+
+       resp, err := client.Do(req)
+       return resp, err
+}
+
+func (c *Connection) SetHeaders(headers HeaderMap) {
+       c.headers = headers
+}
+
+func NewConnection(url string) *Connection {
+       return &Connection{url: url}
+}
diff --git a/main.go b/main.go
new file mode 100644 (file)
index 0000000..3385021
--- /dev/null
+++ b/main.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+       "strings"
+
+       log "github.com/sirupsen/logrus"
+
+       "github.com/kelseyhightower/envconfig"
+)
+
+type Config struct {
+       VaultURL        string `envconfig:"VAULT_URL" required:"true"`
+       EtcdURL         string `envconfig:"ETCD_URL" required:"true"`
+       SlackWebHookURL string `envconfig:"SLACK_WEBHOOK_URL" required:"true"`
+       SlackChannel    string `envconfig:"SLACK_CHANNEL" required:"true"`
+       Verbose         bool   `envconfig:"VERBOSE"`
+}
+
+func main() {
+       var config Config
+       err := envconfig.Process("monitor", &config)
+       if err != nil {
+               log.Fatalf("Error reading config: %s", err)
+       }
+
+       if config.Verbose {
+               log.SetLevel(log.DebugLevel)
+       }
+
+       checks := []CheckProvider{
+               NewVault(config.VaultURL),
+               NewEtcd(config.EtcdURL),
+       }
+
+       var msgs []string
+       for _, check := range checks {
+               success, msg := check.Test()
+               log.Infof("%s check [success: %t, msg: %s]", check.Name(), success, msg)
+               if !success {
+                       msgs = append(msgs, msg)
+               }
+       }
+
+       if len(msgs) < 1 {
+               log.Infof("All checks successful.")
+               return
+       }
+
+       slack := DefaultSlackClient(config.SlackWebHookURL, config.SlackChannel)
+       err = slack.Send(strings.Join(msgs, "\n"))
+       if err != nil {
+               log.Fatalf("Error sending to Slack: %s", err)
+       }
+}
diff --git a/provider.go b/provider.go
new file mode 100644 (file)
index 0000000..3dacde7
--- /dev/null
@@ -0,0 +1,8 @@
+package main
+
+type CheckProvider interface {
+       CheckHealth() (bool, error)
+       CheckCluster() (bool, error)
+       Test() (bool, string)
+       Name() string
+}
diff --git a/slackapi.go b/slackapi.go
new file mode 100644 (file)
index 0000000..8db97b8
--- /dev/null
@@ -0,0 +1,70 @@
+package main
+
+import (
+       "encoding/json"
+
+       log "github.com/sirupsen/logrus"
+)
+
+type SlackClient struct {
+       url        string
+       channel    string
+       username   string
+       icon       string
+       link_names int
+}
+
+func NewSlackClient(url, channel, username, icon string, link_names int) *SlackClient {
+       return &SlackClient{
+               url:        url,
+               channel:    channel,
+               username:   username,
+               icon:       icon,
+               link_names: link_names,
+       }
+}
+
+func DefaultSlackClient(url, channel string) *SlackClient {
+       return NewSlackClient(url, channel, "theknowledge-bot", ":boom:", 1)
+}
+
+func (s *SlackClient) Send(message string) error {
+
+       cnx := NewConnection(s.url)
+       headers := HeaderMap{
+               "Content-type": "application/json",
+       }
+       cnx.SetHeaders(headers)
+
+       jsonStruct := struct {
+               Text      string `json:"text"`
+               Channel   string `json:"channel"`
+               Linknames int    `json:"link_names"`
+               Username  string `json:"username"`
+               Icon      string `json:"icon"`
+       }{
+               message,
+               s.channel,
+               s.link_names,
+               s.username,
+               s.icon,
+       }
+
+       payload, err := json.Marshal(jsonStruct)
+       if err != nil {
+               log.Errorf("Marshal failed %s", err)
+               return err
+       }
+
+       log.Debugf("Slack payload '%s' to send", string(payload))
+
+       resp, err := cnx.Post(string(payload))
+       if err != nil {
+               log.Errorf("Post failed %s", err)
+               return err
+       }
+
+       log.Debugf("Post message succeeded %s", resp.Status)
+
+       return nil
+}
diff --git a/vault.go b/vault.go
new file mode 100644 (file)
index 0000000..6538d92
--- /dev/null
+++ b/vault.go
@@ -0,0 +1,94 @@
+package main
+
+import (
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
+       "net/http"
+
+       log "github.com/sirupsen/logrus"
+)
+
+type Vault struct {
+       URL              string
+       ConnectionStatus string
+       ClusterStatus    string
+}
+
+func (v *Vault) CheckHealth() (bool, error) {
+       healthURL := v.URL + "/v1/sys/health"
+       cnx := NewConnection(healthURL)
+       log.Debugf("Vault Connection on %s", healthURL)
+       resp, err := cnx.Head()
+       if err != nil {
+               return false, err
+       }
+       defer resp.Body.Close()
+
+       log.Debugf("Vault CheckHealth response: %s", resp.Status)
+
+       return resp.StatusCode == http.StatusOK, nil
+}
+
+func (v *Vault) CheckCluster() (bool, error) {
+       ClusterURL := v.URL + "/v1/sys/leader"
+       cnx := NewConnection(ClusterURL)
+       log.Debugf("Vault Connection on %s", ClusterURL)
+       resp, err := cnx.Get()
+       if err != nil {
+               return false, err
+       }
+       defer resp.Body.Close()
+
+       log.Debugf("Vault CheckCluster response: %s", resp.Status)
+
+       buf, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               return false, err
+       }
+
+       var result struct {
+               LeaderAddress        string `json:"leader_address"`
+               IsSelf               bool   `json:"is_self"`
+               HAEnabled            bool   `json:"ha_enabled"`
+               LeaderClusterAddress string `json:"leader_cluster_address"`
+       }
+       err = json.Unmarshal(buf, &result)
+       if err != nil {
+               return false, err
+       }
+
+       log.Debugf("Vault CheckCluster JSON result: %+v", result)
+
+       return result.HAEnabled == true, nil
+}
+
+func (v *Vault) Test() (bool, string) {
+       // test health
+       result, err := v.CheckHealth()
+       if err != nil {
+               log.Errorf("Vault CheckHealth error: %s", err)
+               v.ConnectionStatus = "KO"
+       }
+       v.ConnectionStatus = "OK"
+       log.Debugf("Vault CheckHealth result: %t", result)
+
+       // test cluster
+       result, err = v.CheckCluster()
+       if err != nil {
+               log.Errorf("Vault CheckCluster error: %s", err)
+               v.ClusterStatus = "KO"
+       }
+       v.ClusterStatus = "OK"
+       log.Debugf("Vault CheckCluster result: %t", result)
+
+       return result, fmt.Sprintf("> %s `%s` status *%s*. Cluster status *%s*.", v.Name(), v.URL, v.ConnectionStatus, v.ClusterStatus)
+}
+
+func (v *Vault) Name() string {
+       return "Vault"
+}
+
+func NewVault(url string) CheckProvider {
+       return &Vault{URL: url}
+}