From 3a1cf6efee3d2a23e73d532b22d2f12b7c2ad547 Mon Sep 17 00:00:00 2001 From: Akihiko Horiuchi <12ff5b8@gmail.com> Date: Fri, 26 Jun 2015 01:00:52 +0900 Subject: [PATCH] split ohgi to sensu package --- Makefile | 2 +- README.md | 11 ++-- main.go | 155 ++++++++++++++++++++++++----------------------- ohgi/api.go | 49 --------------- ohgi/checks.go | 73 +++++++++------------- ohgi/clients.go | 92 +++++++++++----------------- ohgi/config.go | 51 ++++++++-------- ohgi/events.go | 94 +++++++++++++--------------- ohgi/health.go | 12 ++-- ohgi/history.go | 28 +++------ ohgi/info.go | 30 +++------ ohgi/misc.go | 128 ++++++++++++++++---------------------- ohgi/request.go | 15 ++--- ohgi/resolve.go | 15 ++--- ohgi/silence.go | 78 ++++++++++++------------ ohgi/version.go | 6 +- sensu/api.go | 83 +++++++++++++++++++++++++ sensu/checks.go | 58 ++++++++++++++++++ sensu/clients.go | 90 +++++++++++++++++++++++++++ sensu/events.go | 83 +++++++++++++++++++++++++ sensu/health.go | 18 ++++++ sensu/history.go | 32 ++++++++++ sensu/info.go | 45 ++++++++++++++ sensu/misc.go | 26 ++++++++ sensu/request.go | 35 +++++++++++ sensu/resolve.go | 35 +++++++++++ sensu/stashes.go | 98 ++++++++++++++++++++++++++++++ 27 files changed, 942 insertions(+), 500 deletions(-) delete mode 100644 ohgi/api.go create mode 100644 sensu/api.go create mode 100644 sensu/checks.go create mode 100644 sensu/clients.go create mode 100644 sensu/events.go create mode 100644 sensu/health.go create mode 100644 sensu/history.go create mode 100644 sensu/info.go create mode 100644 sensu/misc.go create mode 100644 sensu/request.go create mode 100644 sensu/resolve.go create mode 100644 sensu/stashes.go diff --git a/Makefile b/Makefile index e962d2a..888575c 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ run: gom run main.go ${ARGS} fmt: - gom exec goimports -w *.go ohgi/*.go + gom exec goimports -w *.go sensu/*.go ohgi/*.go build: fmt gom build $(GO_BUILDOPT) -o bin/ohgi main.go diff --git a/README.md b/README.md index 9640769..3b2966b 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,11 @@ "host": "192.168.11.20", "port": 4567 } - ], - "timeout": 3 // Optional + ] } Specify a datacenter by `-x`(`--datacenter`) option as below. -If datacenter is not specified, use first of `datacenters`. +If a datacenter is not specified, use first of `datacenters`. $ ohgi -x server-1 events @@ -50,16 +49,16 @@ If datacenter is not specified, use first of `datacenters`. ohgi [command] Available Commands: - checks List locally defined checks and request executions - request Issues a check execution request clients List and delete client(s) information jit Dynamically created clients, added to the client registry history Returns the history for a client + checks List locally defined checks and request executions + request Issues a check execution request events List and resolve current events resolve Resolves an event + silence Create, list, and delete silence stashes health Check the status of the API's transport & Redis connections, and query the transport's status info List the Sensu version and the transport and Redis connection information - silence Create, list, and delete silences version Print and check version of ohgi help Help about any command diff --git a/main.go b/main.go index 3ad4a63..ec68f50 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "strings" "./ohgi" + "./sensu" isatty "github.com/mattn/go-isatty" "github.com/spf13/cobra" ) @@ -18,10 +19,10 @@ func main() { limit int offset int delete bool - consumers int - messages int expiration string reason string + consumers int + messages int ) if !isatty.IsTerminal(os.Stdout.Fd()) { @@ -33,43 +34,11 @@ func main() { Short: "Sensu command-line tool by golang", Long: "Sensu command-line tool by golang\nhttps://github.com/hico-horiuchi/ohgi", PersistentPreRun: func(cmd *cobra.Command, args []string) { - ohgi.LoadConfig(datacenter) + sensu.DefaultAPI = ohgi.LoadConfig(datacenter) }, } rootCmd.PersistentFlags().StringVarP(&datacenter, "datacenter", "x", "", "Specify a datacenter") - rootCmd.AddCommand(&cobra.Command{ - Use: "checks [check]", - Short: "List locally defined checks and request executions", - Long: "checks Returns the list of checks\nchecks [check] Returns a check", - Run: func(cmd *cobra.Command, args []string) { - switch len(args) { - case 0: - fmt.Print(ohgi.GetChecks()) - case 1: - if strings.Contains(args[0], "*") { - fmt.Print(ohgi.GetChecksWildcard(args[0])) - } else { - fmt.Print(ohgi.GetChecksCheck(args[0])) - } - } - }, - }) - - rootCmd.AddCommand(&cobra.Command{ - Use: "request [check] [subscriber]", - Short: "Issues a check execution request", - Long: "request [check] Issues a check execution request\nrequest [check] [subscriber] Issues a check execution request", - Run: func(cmd *cobra.Command, args []string) { - switch len(args) { - case 1: - fmt.Print(ohgi.PostRequest(args[0], "")) - case 2: - fmt.Print(ohgi.PostRequest(args[0], args[1])) - } - }, - }) - clientsCmd := &cobra.Command{ Use: "clients [client]", Short: "List and delete client(s) information", @@ -77,15 +46,15 @@ func main() { Run: func(cmd *cobra.Command, args []string) { switch len(args) { case 0: - fmt.Print(ohgi.GetClients(limit, offset)) + fmt.Print(ohgi.GetClients(sensu.DefaultAPI, limit, offset)) case 1: if delete { - fmt.Print(ohgi.DeleteClientsClient(args[0])) + fmt.Print(ohgi.DeleteClientsClient(sensu.DefaultAPI, args[0])) } else { if strings.Contains(args[0], "*") { - fmt.Print(ohgi.GetClientsWildcard(args[0])) + fmt.Print(ohgi.GetClientsWildcard(sensu.DefaultAPI, args[0])) } else { - fmt.Print(ohgi.GetClientsClient(args[0])) + fmt.Print(ohgi.GetClientsClient(sensu.DefaultAPI, args[0])) } } } @@ -103,9 +72,9 @@ func main() { Run: func(cmd *cobra.Command, args []string) { switch len(args) { case 2: - fmt.Print(ohgi.PostClients(args[0], args[1], "")) + fmt.Print(ohgi.PostClients(sensu.DefaultAPI, args[0], args[1], []string{})) case 3: - fmt.Print(ohgi.PostClients(args[0], args[1], args[2])) + fmt.Print(ohgi.PostClients(sensu.DefaultAPI, args[0], args[1], strings.Split(args[2], ","))) } }, }) @@ -117,7 +86,39 @@ func main() { Run: func(cmd *cobra.Command, args []string) { switch len(args) { case 1: - fmt.Print(ohgi.GetHistory(args[0])) + fmt.Print(ohgi.GetClientsHistory(sensu.DefaultAPI, args[0])) + } + }, + }) + + rootCmd.AddCommand(&cobra.Command{ + Use: "checks [check]", + Short: "List locally defined checks and request executions", + Long: "checks Returns the list of checks\nchecks [check] Returns a check", + Run: func(cmd *cobra.Command, args []string) { + switch len(args) { + case 0: + fmt.Print(ohgi.GetChecks(sensu.DefaultAPI)) + case 1: + if strings.Contains(args[0], "*") { + fmt.Print(ohgi.GetChecksWildcard(sensu.DefaultAPI, args[0])) + } else { + fmt.Print(ohgi.GetChecksCheck(sensu.DefaultAPI, args[0])) + } + } + }, + }) + + rootCmd.AddCommand(&cobra.Command{ + Use: "request [check] [subscribers]", + Short: "Issues a check execution request", + Long: "request [check] Issues a check execution request\nrequest [check] [subscribers] Issues a check execution request", + Run: func(cmd *cobra.Command, args []string) { + switch len(args) { + case 1: + fmt.Print(ohgi.PostRequest(sensu.DefaultAPI, args[0], []string{})) + case 2: + fmt.Print(ohgi.PostRequest(sensu.DefaultAPI, args[0], strings.Split(args[1], ","))) } }, }) @@ -129,14 +130,14 @@ func main() { Run: func(cmd *cobra.Command, args []string) { switch len(args) { case 0: - fmt.Print(ohgi.GetEvents()) + fmt.Print(ohgi.GetEvents(sensu.DefaultAPI)) case 1: - fmt.Print(ohgi.GetEventsClient(args[0])) + fmt.Print(ohgi.GetEventsClient(sensu.DefaultAPI, args[0])) case 2: if delete { - fmt.Print(ohgi.DeleteEventsClientCheck(args[0], args[1])) + fmt.Print(ohgi.DeleteEventsClientCheck(sensu.DefaultAPI, args[0], args[1])) } else { - fmt.Print(ohgi.GetEventsClientCheck(args[0], args[1])) + fmt.Print(ohgi.GetEventsClientCheck(sensu.DefaultAPI, args[0], args[1])) } } }, @@ -151,60 +152,60 @@ func main() { Run: func(cmd *cobra.Command, args []string) { switch len(args) { case 2: - fmt.Print(ohgi.PostResolve(args[0], args[1])) + fmt.Print(ohgi.PostResolve(sensu.DefaultAPI, args[0], args[1])) } }, }) - healthCmd := &cobra.Command{ - Use: "health", - Short: "Check the status of the API's transport & Redis connections, and query the transport's status", - Long: "health Returns health information on transport & Redis connections", - Run: func(cmd *cobra.Command, args []string) { - fmt.Print(ohgi.GetHealth(consumers, messages)) - }, - } - healthCmd.Flags().IntVarP(&consumers, "consumers", "c", 1, "The minimum number of transport consumers to be considered healthy") - healthCmd.Flags().IntVarP(&messages, "messages", "m", 1, "The maximum ammount of transport queued messages to be considered healthy") - rootCmd.AddCommand(healthCmd) - - rootCmd.AddCommand(&cobra.Command{ - Use: "info", - Short: "List the Sensu version and the transport and Redis connection information", - Long: "info Returns information on the API", - Run: func(cmd *cobra.Command, args []string) { - fmt.Print(ohgi.GetInfo()) - }, - }) - silenceCmd := &cobra.Command{ Use: "silence [client] [check]", - Short: "Create, list, and delete silences", - Long: "silence Returns a list of silences\nsilence [client] Create a silence\nsilence [client] [check] Create a silence", + Short: "Create, list, and delete silence stashes", + Long: "silence Returns a list of silence stashes\nsilence [client] Create a silence stash\nsilence [client] [check] Create a silence stash", Run: func(cmd *cobra.Command, args []string) { switch len(args) { case 0: - fmt.Print(ohgi.GetSilence()) + fmt.Print(ohgi.GetSilence(sensu.DefaultAPI)) case 1: if delete { - fmt.Print(ohgi.DeleteSilence(args[0], "")) + fmt.Print(ohgi.DeleteSilence(sensu.DefaultAPI, args[0], "")) } else { - fmt.Print(ohgi.PostSilence(args[0], "", expiration, reason)) + fmt.Print(ohgi.PostSilence(sensu.DefaultAPI, args[0], "", expiration, reason)) } case 2: if delete { - fmt.Print(ohgi.DeleteSilence(args[0], args[1])) + fmt.Print(ohgi.DeleteSilence(sensu.DefaultAPI, args[0], args[1])) } else { - fmt.Print(ohgi.PostSilence(args[0], args[1], expiration, reason)) + fmt.Print(ohgi.PostSilence(sensu.DefaultAPI, args[0], args[1], expiration, reason)) } } }, } - silenceCmd.Flags().StringVarP(&expiration, "expiration", "e", "", "15m, 1h, 1d") + silenceCmd.Flags().StringVarP(&expiration, "expiration", "e", "", "e.g. 15m, 1h, 1d") silenceCmd.Flags().StringVarP(&reason, "reason", "r", "", "Enter a reason") - silenceCmd.Flags().BoolVarP(&delete, "delete", "d", false, "Delete a silence") + silenceCmd.Flags().BoolVarP(&delete, "delete", "d", false, "Remove silence stash") rootCmd.AddCommand(silenceCmd) + healthCmd := &cobra.Command{ + Use: "health", + Short: "Check the status of the API's transport & Redis connections, and query the transport's status", + Long: "health Returns health information on transport & Redis connections", + Run: func(cmd *cobra.Command, args []string) { + fmt.Print(ohgi.GetHealth(sensu.DefaultAPI, consumers, messages)) + }, + } + healthCmd.Flags().IntVarP(&consumers, "consumers", "c", 1, "The minimum number of transport consumers to be considered healthy") + healthCmd.Flags().IntVarP(&messages, "messages", "m", 0, "The maximum ammount of transport queued messages to be considered healthy") + rootCmd.AddCommand(healthCmd) + + rootCmd.AddCommand(&cobra.Command{ + Use: "info", + Short: "List the Sensu version and the transport and Redis connection information", + Long: "info Returns information on the API", + Run: func(cmd *cobra.Command, args []string) { + fmt.Print(ohgi.GetInfo(sensu.DefaultAPI)) + }, + }) + rootCmd.AddCommand(&cobra.Command{ Use: "version", Short: "Print and check the version of ohgi", diff --git a/ohgi/api.go b/ohgi/api.go deleted file mode 100644 index b45f3cc..0000000 --- a/ohgi/api.go +++ /dev/null @@ -1,49 +0,0 @@ -package ohgi - -import ( - "io" - "io/ioutil" - "net/http" - "strconv" -) - -func makeRequest(method string, namespace string, payload io.Reader) *http.Request { - url := "http://" + datacenter.Host + ":" + strconv.Itoa(datacenter.Port) + namespace - request, err := http.NewRequest(method, url, payload) - checkError(err) - - if datacenter.User != "" && datacenter.Password != "" { - request.SetBasicAuth(datacenter.User, datacenter.Password) - } - - if payload != nil { - request.Header.Set("Content-Type", "application/json") - } - - return request -} - -func doAPI(method string, namespace string, payload io.Reader) ([]byte, int) { - request := makeRequest(method, namespace, payload) - response, err := http.DefaultClient.Do(request) - checkError(err) - - status := response.StatusCode - body, err := ioutil.ReadAll(response.Body) - checkError(err) - - defer response.Body.Close() - return body, status -} - -func getAPI(namespace string) ([]byte, int) { - return doAPI("GET", namespace, nil) -} - -func postAPI(namespace string, payload io.Reader) ([]byte, int) { - return doAPI("POST", namespace, payload) -} - -func deleteAPI(namespace string) ([]byte, int) { - return doAPI("DELETE", namespace, nil) -} diff --git a/ohgi/checks.go b/ohgi/checks.go index a3179e8..edf36b8 100644 --- a/ohgi/checks.go +++ b/ohgi/checks.go @@ -1,58 +1,43 @@ package ohgi import ( - "encoding/json" "regexp" "strconv" "strings" -) -type checkStruct struct { - Name string - Command string - Subscribers []string - Interval int - Issued int - Executed int - Output string - Status int - Duration float32 - History []string -} + "../sensu" +) -func GetChecks() string { - var checks []checkStruct - var result []byte +func GetChecks(api *sensu.API) string { + var line string - contents, status := getAPI("/checks") - checkStatus(status) + checks, err := api.GetChecks() + checkError(err) - json.Unmarshal(contents, &checks) if len(checks) == 0 { return "No checks\n" } - result = append(result, bold("NAME COMMAND INTERVAL\n")...) - for _, c := range checks { - line := fillSpace(c.Name, 30) + fillSpace(c.Command, 60) + strconv.Itoa(c.Interval) + "\n" + result := []byte(bold("NAME COMMAND INTERVAL\n")) + for _, check := range checks { + line = fillSpace(check.Name, 30) + fillSpace(check.Command, 60) + strconv.Itoa(check.Interval) + "\n" result = append(result, line...) } return string(result) } -func GetChecksWildcard(pattern string) string { - var checks []checkStruct - var result []byte +func GetChecksWildcard(api *sensu.API, pattern string) string { + var match []string var matches []int - re := regexp.MustCompile("^" + strings.Replace(pattern, "*", ".*", -1) + "$") + var line string - contents, status := getAPI("/checks") - checkStatus(status) + checks, err := api.GetChecks() + checkError(err) - json.Unmarshal(contents, &checks) - for i, c := range checks { - match := re.FindStringSubmatch(c.Name) + re := regexp.MustCompile("^" + strings.Replace(pattern, "*", ".*", -1) + "$") + for i, check := range checks { + match = re.FindStringSubmatch(check.Name) if len(match) > 0 { matches = append(matches, i) } @@ -62,29 +47,27 @@ func GetChecksWildcard(pattern string) string { return "No checks\n" } - result = append(result, bold("NAME COMMAND INTERVAL\n")...) + result := []byte(bold("NAME COMMAND INTERVAL\n")) for _, i := range matches { - c := checks[i] - line := fillSpace(c.Name, 30) + fillSpace(c.Command, 60) + strconv.Itoa(c.Interval) + "\n" + check := checks[i] + line = fillSpace(check.Name, 30) + fillSpace(check.Command, 60) + strconv.Itoa(check.Interval) + "\n" result = append(result, line...) } return string(result) } -func GetChecksCheck(check string) string { - var c checkStruct +func GetChecksCheck(api *sensu.API, name string) string { var result []byte - contents, status := getAPI("/checks/" + check) - checkStatus(status) - - json.Unmarshal(contents, &c) + check, err := api.GetChecksCheck(name) + checkError(err) - result = append(result, (bold("NAME ") + c.Name + "\n")...) - result = append(result, (bold("COMMAND ") + c.Command + "\n")...) - result = append(result, (bold("SUBSCRIBERS ") + strings.Join(c.Subscribers, ", ") + "\n")...) - result = append(result, (bold("INTERVAL ") + strconv.Itoa(c.Interval) + "\n")...) + result = append(result, (bold("NAME ") + check.Name + "\n")...) + result = append(result, (bold("COMMAND ") + check.Command + "\n")...) + result = append(result, (bold("SUBSCRIBERS ") + strings.Join(check.Subscribers, ", ") + "\n")...) + result = append(result, (bold("INTERVAL ") + strconv.Itoa(check.Interval) + "\n")...) + result = append(result, (bold("HANDLERS ") + strings.Join(check.Handlers, ", ") + "\n")...) return string(result) } diff --git a/ohgi/clients.go b/ohgi/clients.go index f04456f..412859c 100644 --- a/ohgi/clients.go +++ b/ohgi/clients.go @@ -1,52 +1,42 @@ package ohgi import ( - "encoding/json" - "fmt" "regexp" "strings" -) -type clientStruct struct { - Name string `json:"name"` - Address string `json:"address"` - Subscriptions []string `json:"subscriptions"` - Timestamp int64 -} + "../sensu" +) -func GetClients(limit int, offset int) string { - var clients []clientStruct - var result []byte +func GetClients(api *sensu.API, limit int, offset int) string { + var line string - contents, status := getAPI(fmt.Sprintf("/clients?limit=%d&offset=%d", limit, offset)) - checkStatus(status) + clients, err := api.GetClients(limit, offset) + checkError(err) - json.Unmarshal(contents, &clients) if len(clients) == 0 { return "No clients\n" } - result = append(result, bold("NAME ADDRESS TIMESTAMP\n")...) - for _, c := range clients { - line := fillSpace(c.Name, 40) + fillSpace(c.Address, 40) + utoa(c.Timestamp) + "\n" + result := []byte(bold("NAME ADDRESS TIMESTAMP\n")) + for _, client := range clients { + line = fillSpace(client.Name, 40) + fillSpace(client.Address, 40) + utoa(client.Timestamp) + "\n" result = append(result, line...) } return string(result) } -func GetClientsWildcard(pattern string) string { - var clients []clientStruct - var result []byte +func GetClientsWildcard(api *sensu.API, pattern string) string { + var match []string var matches []int - re := regexp.MustCompile("^" + strings.Replace(pattern, "*", ".*", -1) + "$") + var line string - contents, status := getAPI("/clients") - checkStatus(status) + clients, err := api.GetClients(-1, -1) + checkError(err) - json.Unmarshal(contents, &clients) - for i, c := range clients { - match := re.FindStringSubmatch(c.Name) + re := regexp.MustCompile("^" + strings.Replace(pattern, "*", ".*", -1) + "$") + for i, client := range clients { + match = re.FindStringSubmatch(client.Name) if len(match) > 0 { matches = append(matches, i) } @@ -56,53 +46,41 @@ func GetClientsWildcard(pattern string) string { return "No clients\n" } - result = append(result, bold("NAME ADDRESS TIMESTAMP\n")...) + result := []byte(bold("NAME ADDRESS TIMESTAMP\n")) for _, i := range matches { - c := clients[i] - line := fillSpace(c.Name, 40) + fillSpace(c.Address, 40) + utoa(c.Timestamp) + "\n" + client := clients[i] + line = fillSpace(client.Name, 40) + fillSpace(client.Address, 40) + utoa(client.Timestamp) + "\n" result = append(result, line...) } return string(result) } -func GetClientsClient(client string) string { - var c clientStruct +func GetClientsClient(api *sensu.API, name string) string { var result []byte - contents, status := getAPI("/clients/" + client) - checkStatus(status) - - json.Unmarshal(contents, &c) + client, err := api.GetClientsClient(name) + checkError(err) - result = append(result, (bold("NAME ") + c.Name + "\n")...) - result = append(result, (bold("ADDRESS ") + c.Address + "\n")...) - result = append(result, (bold("SUBSCRIPTIONS ") + strings.Join(c.Subscriptions, ", ") + "\n")...) - result = append(result, (bold("TIMESTAMP ") + utoa(c.Timestamp) + "\n")...) + result = append(result, (bold("NAME ") + client.Name + "\n")...) + result = append(result, (bold("ADDRESS ") + client.Address + "\n")...) + result = append(result, (bold("SUBSCRIPTIONS ") + strings.Join(client.Subscriptions, ", ") + "\n")...) + result = append(result, (bold("TIMESTAMP ") + utoa(client.Timestamp) + "\n")...) + result = append(result, (bold("VERSION ") + client.Version + "\n")...) return string(result) } -func PostClients(name string, address string, subscriptions string) string { - c := clientStruct{ - Name: name, - Address: address, - Subscriptions: strings.Split(subscriptions, ","), - } - - body, err := json.Marshal(c) +func PostClients(api *sensu.API, name string, address string, subscriptions []string) string { + err := api.PostClients(name, address, subscriptions) checkError(err) - payload := strings.NewReader(string(body)) - _, status := postAPI("/clients", payload) - checkStatus(status) - - return httpStatus(status) + "\n" + return "Created\n" } -func DeleteClientsClient(client string) string { - _, status := deleteAPI("/clients/" + client) - checkStatus(status) +func DeleteClientsClient(api *sensu.API, name string) string { + err := api.DeleteClientsClient(name) + checkError(err) - return httpStatus(status) + "\n" + return "Accepted\n" } diff --git a/ohgi/config.go b/ohgi/config.go index 9d4471f..7d115a9 100644 --- a/ohgi/config.go +++ b/ohgi/config.go @@ -4,56 +4,53 @@ import ( "encoding/json" "errors" "io/ioutil" - "net/http" "os" - "time" -) -var datacenter datacenterStruct -var timeout = 3 * time.Second + "../sensu" +) type configStruct struct { - Datacenters []datacenterStruct - Timeout int + Datacenters []datacenterStruct `json:"datacenters"` } type datacenterStruct struct { - Name string - Host string - Port int - User string - Password string + sensu.API + Name string `json:"name"` } -func LoadConfig(dc string) { - var c configStruct +func LoadConfig(name string) *sensu.API { + var config configStruct bytes, err := ioutil.ReadFile(os.Getenv("HOME") + "/.ohgi.json") checkError(err) - json.Unmarshal(bytes, &c) - datacenter, err = c.selectDatacenter(dc) + err = json.Unmarshal(bytes, &config) + checkError(err) + + datacenter, err := config.selectDatacenter(name) checkError(err) - if c.Timeout > 0 { - timeout = time.Duration(c.Timeout) * time.Second + return &sensu.API{ + Host: datacenter.Host, + Port: datacenter.Port, + User: datacenter.User, + Password: datacenter.Password, } - http.DefaultClient.Timeout = timeout } -func (c configStruct) selectDatacenter(name string) (datacenterStruct, error) { +func (config configStruct) selectDatacenter(name string) (*datacenterStruct, error) { switch { - case len(c.Datacenters) < 1: - return datacenterStruct{}, errors.New("ohgi: no datacenters in config") + case len(config.Datacenters) == 0: + return nil, errors.New("ohgi: no datacenters in config") case name == "": - return c.Datacenters[0], nil + return &config.Datacenters[0], nil } - for _, dc := range c.Datacenters { - if dc.Name == name { - return dc, nil + for _, datacenter := range config.Datacenters { + if datacenter.Name == name { + return &datacenter, nil } } - return datacenterStruct{}, errors.New("ohgi: no such datacenter in config") + return nil, errors.New("ohgi: no such datacenter in config") } diff --git a/ohgi/events.go b/ohgi/events.go index c18b6aa..c735271 100644 --- a/ohgi/events.go +++ b/ohgi/events.go @@ -1,91 +1,81 @@ package ohgi import ( - "encoding/json" "strconv" "strings" -) -type eventStruct struct { - Id string - Client clientStruct - Check checkStruct - Occurrences int - Action string -} + "../sensu" +) -func GetEvents() string { - var events []eventStruct - var result []byte +func GetEvents(api *sensu.API) string { + var line string - contents, status := getAPI("/events") - checkStatus(status) + events, err := api.GetEvents() + checkError(err) - json.Unmarshal(contents, &events) if len(events) == 0 { return "No current events\n" } - result = append(result, bold(" CLIENT CHECK # TIMESTAMP\n")...) - for _, e := range events { - occurrences := strconv.Itoa(e.Occurrences) - line := statusBg(e.Check.Status) + fillSpace(e.Client.Name, 40) + fillSpace(e.Check.Name, 30) + fillSpace(occurrences, 10) + utoa(e.Client.Timestamp) + "\n" + result := []byte(bold(" CLIENT CHECK # EXECUTED\n")) + for _, event := range events { + line = indicateStatus(event.Check.Status) + fillSpace(event.Client.Name, 40) + fillSpace(event.Check.Name, 30) + fillSpace(strconv.Itoa(event.Occurrences), 10) + utoa(event.Check.Executed) + "\n" result = append(result, line...) } return string(result) } -func GetEventsClient(client string) string { - var events []eventStruct - var result []byte +func GetEventsClient(api *sensu.API, client string) string { + var line, output string - contents, status := getAPI("/events/" + client) - checkStatus(status) + events, err := api.GetEvents() + checkError(err) - json.Unmarshal(contents, &events) if len(events) == 0 { return "No current events for " + client + "\n" } - result = append(result, bold(" CHECK OUTPUT TIMESTAMP\n")...) - for _, e := range events { - output := strings.Replace(e.Check.Output, "\n", " ", -1) - line := statusBg(e.Check.Status) + fillSpace(e.Check.Name, 30) + fillSpace(output, 50) + utoa(e.Client.Timestamp) + "\n" + result := []byte(bold(" CHECK OUTPUT EXECUTED\n")) + for _, event := range events { + output = strings.Replace(event.Check.Output, "\n", " ", -1) + line = indicateStatus(event.Check.Status) + fillSpace(event.Check.Name, 30) + fillSpace(output, 50) + utoa(event.Check.Executed) + "\n" result = append(result, line...) } return string(result) } -func GetEventsClientCheck(client string, check string) string { - var e eventStruct +func GetEventsClientCheck(api *sensu.API, client string, check string) string { var result []byte - contents, status := getAPI("/events/" + client + "/" + check) - checkStatus(status) - - json.Unmarshal(contents, &e) - - result = append(result, (bold("CLIENT ") + e.Client.Name + "\n")...) - result = append(result, (bold("ADDRESS ") + e.Client.Address + "\n")...) - result = append(result, (bold("SUBSCRIPTIONS ") + strings.Join(e.Client.Subscriptions, ", ") + "\n")...) - result = append(result, (bold("TIMESTAMP ") + utoa(e.Client.Timestamp) + "\n")...) - result = append(result, (bold("CHECK ") + e.Check.Name + "\n")...) - result = append(result, (bold("COMMAND ") + e.Check.Command + "\n")...) - result = append(result, (bold("SUBSCRIBERS ") + strings.Join(e.Check.Subscribers, ", ") + "\n")...) - result = append(result, (bold("INTERVAL ") + strconv.Itoa(e.Check.Interval) + "\n")...) - result = append(result, (bold("OUTPUT ") + strings.Replace(e.Check.Output, "\n", " ", -1) + "\n")...) - result = append(result, (bold("STATUS ") + statusFg(e.Check.Status) + "\n")...) - result = append(result, (bold("HISTORY ") + strings.Join(e.Check.History, ", ") + "\n")...) - result = append(result, (bold("OCCURRENCES ") + strconv.Itoa(e.Occurrences) + "\n")...) + event, err := api.GetEventsClientCheck(client, check) + checkError(err) + + result = append(result, (bold("CLIENT ") + event.Client.Name + "\n")...) + result = append(result, (bold("ADDRESS ") + event.Client.Address + "\n")...) + result = append(result, (bold("SUBSCRIPTIONS ") + strings.Join(event.Client.Subscriptions, ", ") + "\n")...) + result = append(result, (bold("TIMESTAMP ") + utoa(event.Client.Timestamp) + "\n")...) + result = append(result, (bold("CHECK ") + event.Check.Name + "\n")...) + result = append(result, (bold("COMMAND ") + event.Check.Command + "\n")...) + result = append(result, (bold("SUBSCRIBERS ") + strings.Join(event.Check.Subscribers, ", ") + "\n")...) + result = append(result, (bold("INTERVAL ") + strconv.Itoa(event.Check.Interval) + "\n")...) + result = append(result, (bold("HANDLERS ") + strings.Join(event.Check.Handlers, ", ") + "\n")...) + result = append(result, (bold("ISSUED ") + utoa(event.Check.Issued) + "\n")...) + result = append(result, (bold("EXECUTED ") + utoa(event.Check.Executed) + "\n")...) + result = append(result, (bold("OUTPUT ") + strings.Replace(event.Check.Output, "\n", " ", -1) + "\n")...) + result = append(result, (bold("STATUS ") + paintStatus(event.Check.Status) + "\n")...) + result = append(result, (bold("DURATION ") + strconv.FormatFloat(event.Check.Duration, 'f', 3, 64) + "\n")...) + result = append(result, (bold("HISTORY ") + paintHistory(strings.Join(event.Check.History, ", ")) + "\n")...) + result = append(result, (bold("OCCURRENCES ") + strconv.Itoa(event.Occurrences) + "\n")...) + result = append(result, (bold("ACTION ") + event.Action + "\n")...) return string(result) } -func DeleteEventsClientCheck(client string, check string) string { - _, status := deleteAPI("/events/" + client + "/" + check) - checkStatus(status) +func DeleteEventsClientCheck(api *sensu.API, client string, check string) string { + err := api.DeleteEventsClientCheck(client, check) + checkError(err) - return httpStatus(status) + "\n" + return "Accepted\n" } diff --git a/ohgi/health.go b/ohgi/health.go index f1bc6e8..dfdc351 100644 --- a/ohgi/health.go +++ b/ohgi/health.go @@ -1,12 +1,10 @@ package ohgi -import ( - "fmt" -) +import "../sensu" -func GetHealth(consumers int, messages int) string { - _, status := getAPI(fmt.Sprintf("/health?consumers=%d&messages=%d", consumers, messages)) - checkStatus(status) +func GetHealth(api *sensu.API, consumers int, messages int) string { + err := api.GetHealth(consumers, messages) + checkError(err) - return httpStatus(status) + "\n" + return "No Content\n" } diff --git a/ohgi/history.go b/ohgi/history.go index 868a870..f3b57ca 100644 --- a/ohgi/history.go +++ b/ohgi/history.go @@ -1,32 +1,20 @@ package ohgi -import ( - "encoding/json" -) +import "../sensu" -type historyStruct struct { - Check string - History []int - Last_execution int64 - Last_status int -} - -func GetHistory(client string) string { - var histories []historyStruct - var result []byte +func GetClientsHistory(api *sensu.API, client string) string { + var line string - contents, status := getAPI("/clients/" + client + "/history") - checkStatus(status) + histories, err := api.GetClientsHistory(client) + checkError(err) - json.Unmarshal(contents, &histories) if len(histories) == 0 { return "No histories\n" } - result = append(result, bold("CHECK HISTORY TIMESTAMP\n")...) - for _, h := range histories { - history := historyFg(fillSpace(stoa(h.History, ", "), 48)) - line := fillSpace(h.Check, 30) + history + utoa(h.Last_execution) + "\n" + result := []byte(bold("CHECK HISTORY TIMESTAMP\n")) + for _, history := range histories { + line = fillSpace(history.Check, 30) + paintHistory(fillSpace(stoa(history.History, ", "), 48)) + utoa(history.LastExecution) + "\n" result = append(result, line...) } diff --git a/ohgi/info.go b/ohgi/info.go index 3d9d1b4..b88cd19 100644 --- a/ohgi/info.go +++ b/ohgi/info.go @@ -1,34 +1,20 @@ package ohgi import ( - "encoding/json" "strconv" -) -type infoStruct struct { - Sensu struct { - Version string - } - Transport struct { - Connected bool - } - Redis struct { - Connected bool - } -} + "../sensu" +) -func GetInfo() string { - var i infoStruct +func GetInfo(api *sensu.API) string { var result []byte - contents, status := getAPI("/info") - checkStatus(status) - - json.Unmarshal(contents, &i) + info, err := api.GetInfo() + checkError(err) - result = append(result, (bold("VERSION ") + i.Sensu.Version + "\n")...) - result = append(result, (bold("TRANSPORT ") + strconv.FormatBool(i.Transport.Connected) + "\n")...) - result = append(result, (bold("REDIS ") + strconv.FormatBool(i.Redis.Connected) + "\n")...) + result = append(result, (bold("VERSION ") + info.Sensu.Version + "\n")...) + result = append(result, (bold("TRANSPORT ") + strconv.FormatBool(info.Transport.Connected) + "\n")...) + result = append(result, (bold("REDIS ") + strconv.FormatBool(info.Redis.Connected) + "\n")...) return string(result) } diff --git a/ohgi/misc.go b/ohgi/misc.go index 50f573b..299fd72 100644 --- a/ohgi/misc.go +++ b/ohgi/misc.go @@ -11,31 +11,41 @@ import ( var EscapeSequence bool = true +func checkError(err error) { + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + func utoa(timestamp int64) string { - format := "2006/01/02 15:04:05" - return time.Unix(timestamp, 0).Format(format) + return time.Unix(timestamp, 0).Format("2006/01/02 15:04:05") } func stoa(arr []int, sep string) string { var result []byte for _, i := range arr { - line := strconv.Itoa(i) + sep - result = append(result, line...) + result = append(result, (strconv.Itoa(i) + sep)...) } return string(result) } func stoe(expiration string) int64 { + var expire int64 = -1 + str := []byte(expiration) format := regexp.MustCompile("([0-9]+)([smhd])") - group := format.FindSubmatch(str) - var expire int64 = -1 + matches := format.FindSubmatch(str) - if len(group) == 3 { - num, _ := strconv.ParseInt(string(group[1]), 10, 0) - switch string(group[2]) { + if len(matches) == 3 { + num, err := strconv.ParseInt(string(matches[1]), 10, 0) + if err != nil { + return expire + } + + switch string(matches[2]) { case "s": expire = num case "m": @@ -50,42 +60,16 @@ func stoe(expiration string) int64 { return expire } -func checkError(err error) { - if err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func checkStatus(status int) { - if status >= 300 { - fmt.Println(httpStatus(status)) - os.Exit(1) - } -} +func fillSpace(str string, max int) string { + length := len(str) + padding := 2 + width := max - padding -func httpStatus(status int) string { - switch status { - case 200: - return strconv.Itoa(status) + " OK" - case 201: - return strconv.Itoa(status) + " Created" - case 202: - return strconv.Itoa(status) + " Accepted" - case 204: - return strconv.Itoa(status) + " No Content" - case 400: - return strconv.Itoa(status) + " Bad Request" - case 401: - return strconv.Itoa(status) + " Unauthorized" - case 404: - return strconv.Itoa(status) + " Not Found" - case 500: - return strconv.Itoa(status) + " Internal Server Error" - case 503: - return strconv.Itoa(status) + " Service Unavailable" + if length > width { + return str[0:width] + strings.Repeat(" ", padding) + } else { + return str + strings.Repeat(" ", max-length) } - return "" } func bold(str string) string { @@ -96,24 +80,24 @@ func bold(str string) string { return "\x1b[1m" + str + "\x1b[0m" } -func historyFg(history string) string { +func indicateStatus(status int) string { if !EscapeSequence { - return history + return " " } - var format *regexp.Regexp - - format = regexp.MustCompile("(^| )(0)(|,)") - history = format.ReplaceAllString(history, "\x1b[32m$1$2$3\x1b[0m") - format = regexp.MustCompile("(^| )(1)(|,)") - history = format.ReplaceAllString(history, "\x1b[33m$1$2$3\x1b[0m") - format = regexp.MustCompile("(^| )(2)(|,)") - history = format.ReplaceAllString(history, "\x1b[31m$1$2$3\x1b[0m") - - return history + switch status { + case 0: + return "\x1b[42m \x1b[0m " + case 1: + return "\x1b[43m \x1b[0m " + case 2: + return "\x1b[41m \x1b[0m " + default: + return "\x1b[47m \x1b[0m " + } } -func statusFg(status int) string { +func paintStatus(status int) string { if !EscapeSequence { switch status { case 0: @@ -139,29 +123,19 @@ func statusFg(status int) string { } } -func statusBg(status int) string { +func paintHistory(history string) string { + var format *regexp.Regexp + if !EscapeSequence { - return " " + return history } - switch status { - case 0: - return "\x1b[42m \x1b[0m " - case 1: - return "\x1b[43m \x1b[0m " - case 2: - return "\x1b[41m \x1b[0m " - } - return "\x1b[47m \x1b[0m " -} + format = regexp.MustCompile("(^| )(0)(|,)") + history = format.ReplaceAllString(history, "\x1b[32m$1$2$3\x1b[0m") + format = regexp.MustCompile("(^| )(1)(|,)") + history = format.ReplaceAllString(history, "\x1b[33m$1$2$3\x1b[0m") + format = regexp.MustCompile("(^| )(2)(|,)") + history = format.ReplaceAllString(history, "\x1b[31m$1$2$3\x1b[0m") -func fillSpace(str string, max int) string { - padding := 2 - width := max - padding - length := len(str) - if length > width { - return str[0:width] + strings.Repeat(" ", padding) - } else { - return str + strings.Repeat(" ", max-length) - } + return history } diff --git a/ohgi/request.go b/ohgi/request.go index bd4419f..ee2ced7 100644 --- a/ohgi/request.go +++ b/ohgi/request.go @@ -1,15 +1,10 @@ package ohgi -import ( - "strings" -) +import "../sensu" -func PostRequest(check string, subscriber string) string { - body := `{"check":"` + check + `","subscribers":["` + subscriber + `"]}` - payload := strings.NewReader(body) +func PostRequest(api *sensu.API, check string, subscribers []string) string { + err := api.PostRequest(check, subscribers) + checkError(err) - _, status := postAPI("/request", payload) - checkStatus(status) - - return httpStatus(status) + "\n" + return "Accepted\n" } diff --git a/ohgi/resolve.go b/ohgi/resolve.go index 73fe6a3..b6fac62 100644 --- a/ohgi/resolve.go +++ b/ohgi/resolve.go @@ -1,15 +1,10 @@ package ohgi -import ( - "strings" -) +import "../sensu" -func PostResolve(client string, check string) string { - body := `{"client":"` + client + `","check":"` + check + `"}` - payload := strings.NewReader(body) +func PostResolve(api *sensu.API, client string, check string) string { + err := api.PostResolve(client, check) + checkError(err) - _, status := postAPI("/resolve", payload) - checkStatus(status) - - return httpStatus(status) + "\n" + return "Accepted\n" } diff --git a/ohgi/silence.go b/ohgi/silence.go index a1727d1..df50b60 100644 --- a/ohgi/silence.go +++ b/ohgi/silence.go @@ -1,60 +1,59 @@ package ohgi import ( - "encoding/json" - "fmt" - "strconv" "strings" "time" + + "../sensu" ) type silenceStruct struct { - Path string - Content struct { - Reason string - Source string - Timestamp float32 - } - Expire int64 + sensu.StashStruct + Content contentStruct `json:"content"` } -func GetSilence() string { +type contentStruct struct { + Timestamp float64 `json:"timestamp"` + Source string `json:"source"` + Reason string `json:"reason"` +} + +func GetSilence(api *sensu.API) string { var silences []silenceStruct - var result []byte + var line string + var path []string var expire string - contents, status := getAPI("/stashes") - checkStatus(status) + err := api.GetStashes(&silences, -1, -1) + checkError(err) - json.Unmarshal(contents, &silences) if len(silences) == 0 { - return "No silences\n" + return "No silence stashes\n" } - result = append(result, bold("CLIENT CHECK REASON EXPIRATION\n")...) - for _, s := range silences { - path := strings.Split(s.Path, "/") + result := []byte(bold("CLIENT CHECK REASON EXPIRATION\n")) + for _, silence := range silences { + path = strings.Split(silence.Path, "/") if path[0] != "silence" { continue } else if len(path) == 2 { path = append(path, "") } - if s.Expire == -1 { + if silence.Expire == -1 { expire = "Never" } else { - expire = utoa(time.Now().Unix() + s.Expire) + expire = utoa(time.Now().Unix() + silence.Expire) } - line := fillSpace(path[1], 40) + fillSpace(path[2], 30) + fillSpace(s.Content.Reason, 30) + expire + "\n" + line = fillSpace(path[1], 40) + fillSpace(path[2], 30) + fillSpace(silence.Content.Reason, 30) + expire + "\n" result = append(result, line...) } return string(result) } -func PostSilence(client string, check string, expiration string, reason string) string { - var body string +func PostSilence(api *sensu.API, client string, check string, expiration string, reason string) string { var path string if check == "" { @@ -63,22 +62,25 @@ func PostSilence(client string, check string, expiration string, reason string) path = "silence/" + client + "/" + check } - now := strconv.FormatInt(time.Now().Unix(), 10) - expire := stoe(expiration) - if expire == -1 { - body = fmt.Sprintf(`{"path":"%s","content":{"reason":"%s","source":"ohgi","timestamp":%s}}`, path, reason, now) - } else { - body = fmt.Sprintf(`{"path":"%s","content":{"reason":"%s","source":"ohgi","timestamp":%s},"expire":%d}`, path, reason, now, expire) + silence := silenceStruct{ + StashStruct: sensu.StashStruct{ + Path: path, + Expire: stoe(expiration), + }, + Content: contentStruct{ + Timestamp: float64(time.Now().Unix()), + Source: "ohgi", + Reason: reason, + }, } - payload := strings.NewReader(body) - _, status := postAPI("/stashes", payload) - checkStatus(status) + err := api.PostStashes(silence) + checkError(err) - return httpStatus(status) + "\n" + return "Created\n" } -func DeleteSilence(client string, check string) string { +func DeleteSilence(api *sensu.API, client string, check string) string { var path string if check == "" { @@ -87,8 +89,8 @@ func DeleteSilence(client string, check string) string { path = "silence/" + client + "/" + check } - _, status := deleteAPI("/stashes/" + path) - checkStatus(status) + err := api.DeleteStashesPath(path) + checkError(err) - return httpStatus(status) + "\n" + return "No Content\n" } diff --git a/ohgi/version.go b/ohgi/version.go index 2a83d22..bb17c1d 100644 --- a/ohgi/version.go +++ b/ohgi/version.go @@ -4,9 +4,11 @@ import ( "fmt" "time" - "github.com/tcnksm/go-latest" + latest "github.com/tcnksm/go-latest" ) +const TIMEOUT_SEC = 1 + func verCheck(version string) <-chan *latest.CheckResponse { verCheckCh := make(chan *latest.CheckResponse) @@ -37,7 +39,7 @@ func Version(version string) string { result = append(result, fmt.Sprintf("Latest version of ohgi is %s, please update it\n", res.Current)...) } return string(result) - case <-time.After(timeout): + case <-time.After(TIMEOUT_SEC * time.Second): return string(result) } } diff --git a/sensu/api.go b/sensu/api.go new file mode 100644 index 0000000..a7c5411 --- /dev/null +++ b/sensu/api.go @@ -0,0 +1,83 @@ +/* +The Sensu API provides access to the data that Sensu servers collect, such as client information & current events. +The API can be used to resolve events and request check executions, among other things. +*/ +package sensu + +import ( + "io" + "io/ioutil" + "net/http" + "strconv" +) + +var DefaultAPI *API = &API{ + Host: "localhost", + Port: 4567, +} + +type API struct { + Host string + Port int + User string + Password string +} + +type Response struct { + Body string + StatusCode int +} + +func (api API) get(namespace string) (*Response, error) { + return api.do("GET", namespace, nil) +} + +func (api API) post(namespace string, payload io.Reader) (*Response, error) { + return api.do("POST", namespace, payload) +} + +func (api API) delete(namespace string) (*Response, error) { + return api.do("DELETE", namespace, nil) +} + +func (api API) do(method string, namespace string, payload io.Reader) (*Response, error) { + request, err := api.newRequest(method, namespace, payload) + if err != nil { + return nil, err + } + + response, err := http.DefaultClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + return &Response{ + Body: string(body), + StatusCode: response.StatusCode, + }, nil +} + +func (api API) newRequest(method string, namespace string, payload io.Reader) (*http.Request, error) { + url := "http://" + api.Host + ":" + strconv.Itoa(api.Port) + namespace + + request, err := http.NewRequest(method, url, payload) + if err != nil { + return nil, err + } + + if api.User != "" && api.Password != "" { + request.SetBasicAuth(api.User, api.Password) + } + + if payload != nil { + request.Header.Set("Content-Type", "application/json") + } + + return request, nil +} diff --git a/sensu/checks.go b/sensu/checks.go new file mode 100644 index 0000000..16928fc --- /dev/null +++ b/sensu/checks.go @@ -0,0 +1,58 @@ +package sensu + +import ( + "encoding/json" + "errors" +) + +type CheckStruct struct { + Name string `json:"name"` + Command string `json:"command"` + Subscribers []string `json:"subscribers"` + Interval int `json:"interval"` + Handlers []string `json:"handlers"` + Issued int64 `json:"issued"` + Executed int64 `json:"executed"` + Output string `json:"output"` + Status int `json:"status"` + Duration float64 `json:"duration"` + History []string `json:"history"` +} + +// Returns the list of checks. +func (api API) GetChecks() ([]CheckStruct, error) { + var checks []CheckStruct + + response, err := api.get("/checks") + if err != nil { + return checks, err + } else if response.StatusCode != 200 { + return checks, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &checks) + if err != nil { + return checks, err + } + + return checks, nil +} + +// Returns a check. +func (api API) GetChecksCheck(name string) (CheckStruct, error) { + var check CheckStruct + + response, err := api.get("/checks/" + name) + if err != nil { + return check, err + } else if response.StatusCode != 200 { + return check, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &check) + if err != nil { + return check, err + } + + return check, nil +} diff --git a/sensu/clients.go b/sensu/clients.go new file mode 100644 index 0000000..87ba63a --- /dev/null +++ b/sensu/clients.go @@ -0,0 +1,90 @@ +package sensu + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +type ClientStruct struct { + Name string `json:"name"` + Address string `json:"address"` + Subscriptions []string `json:"subscriptions"` + Timestamp int64 `json:"timestamp"` + Version string `json:"version"` +} + +// Returns a list of clients. +func (api API) GetClients(limit int, offset int) ([]ClientStruct, error) { + var clients []ClientStruct + + response, err := api.get(fmt.Sprintf("/clients?limit=%d&offset=%d", limit, offset)) + if err != nil { + return clients, err + } else if response.StatusCode != 200 { + return clients, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &clients) + if err != nil { + return clients, err + } + + return clients, nil +} + +// Returns a client. +func (api API) GetClientsClient(name string) (ClientStruct, error) { + var client ClientStruct + + response, err := api.get("/clients/" + name) + if err != nil { + return client, err + } else if response.StatusCode != 200 { + return client, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &client) + if err != nil { + return client, err + } + + return client, nil +} + +// Create or update client data (e.g. Sensu JIT clients). +func (api API) PostClients(name string, address string, subscriptions []string) error { + client := ClientStruct{ + Name: name, + Address: address, + Subscriptions: subscriptions, + } + + body, err := json.Marshal(client) + if err != nil { + return err + } + payload := strings.NewReader(string(body)) + + response, err := api.post("/clients", payload) + if err != nil { + return err + } else if response.StatusCode != 201 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +} + +// Removes a client, resolving its current events. +func (api API) DeleteClientsClient(name string) error { + response, err := api.delete("/client/" + name) + if err != nil { + return err + } else if response.StatusCode != 202 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +} diff --git a/sensu/events.go b/sensu/events.go new file mode 100644 index 0000000..e65a6ce --- /dev/null +++ b/sensu/events.go @@ -0,0 +1,83 @@ +package sensu + +import ( + "encoding/json" + "errors" +) + +type EventStruct struct { + ID string `json:"id"` + Client ClientStruct `json:"client"` + Check CheckStruct `json:"check"` + Occurrences int `json:"occurrences"` + Action string `json:"action"` +} + +// Returns the list of current events. +func (api API) GetEvents() ([]EventStruct, error) { + var events []EventStruct + + response, err := api.get("/events") + if err != nil { + return events, err + } else if response.StatusCode != 200 { + return events, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &events) + if err != nil { + return events, err + } + + return events, nil +} + +// Returns the list of current events for a given client. +func (api API) GetEventsClient(client string) ([]EventStruct, error) { + var events []EventStruct + + response, err := api.get("/events/" + client) + if err != nil { + return events, err + } else if response.StatusCode != 200 { + return events, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &events) + if err != nil { + return events, err + } + + return events, nil +} + +// Returns an event for a given client & check name. +func (api API) GetEventsClientCheck(client string, check string) (EventStruct, error) { + var event EventStruct + + response, err := api.get("/event/" + client + "/" + check) + if err != nil { + return event, err + } else if response.StatusCode != 200 { + return event, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &event) + if err != nil { + return event, err + } + + return event, nil +} + +// Resolves an event for a given check on a given client. +func (api API) DeleteEventsClientCheck(client string, check string) error { + response, err := api.delete("/event/" + client + "/" + check) + if err != nil { + return err + } else if response.StatusCode != 202 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +} diff --git a/sensu/health.go b/sensu/health.go new file mode 100644 index 0000000..17fc737 --- /dev/null +++ b/sensu/health.go @@ -0,0 +1,18 @@ +package sensu + +import ( + "errors" + "fmt" +) + +// Returns health information on transport & Redis connections. +func (api API) GetHealth(consumers int, messages int) error { + response, err := api.get(fmt.Sprintf("/health?consumers=%d&messages=%d", consumers, messages)) + if err != nil { + return err + } else if response.StatusCode != 204 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +} diff --git a/sensu/history.go b/sensu/history.go new file mode 100644 index 0000000..a282a85 --- /dev/null +++ b/sensu/history.go @@ -0,0 +1,32 @@ +package sensu + +import ( + "encoding/json" + "errors" +) + +type historyStruct struct { + Check string `json:"check"` + History []int `json:"history"` + LastExecution int64 `json:"last_execution"` + LastStatus int `json:"last_status"` +} + +// Returns the history for a client. +func (api API) GetClientsHistory(client string) ([]historyStruct, error) { + var histories []historyStruct + + response, err := api.get("/clients/" + client + "/history") + if err != nil { + return histories, err + } else if response.StatusCode != 200 { + return histories, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &histories) + if err != nil { + return histories, err + } + + return histories, nil +} diff --git a/sensu/info.go b/sensu/info.go new file mode 100644 index 0000000..1000df1 --- /dev/null +++ b/sensu/info.go @@ -0,0 +1,45 @@ +package sensu + +import ( + "encoding/json" + "errors" +) + +type InfoStruct struct { + Sensu struct { + Version string `json:"version"` + } `json:"sensu"` + Transport struct { + Keepalives struct { + Messages int `json:"messages"` + Consumers int `json:"consumers"` + } `json:"keepalives"` + Results struct { + Messages int `json:"messages"` + Consumers int `json:"consumers"` + } `json:"results"` + Connected bool `json:"connected"` + } + Redis struct { + Connected bool `json:"connected"` + } `json: "redis"` +} + +// Returns information on the API. +func (api API) GetInfo() (InfoStruct, error) { + var info InfoStruct + + response, err := api.get("/info") + if err != nil { + return info, err + } else if response.StatusCode != 200 { + return info, errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), &info) + if err != nil { + return info, err + } + + return info, nil +} diff --git a/sensu/misc.go b/sensu/misc.go new file mode 100644 index 0000000..b2b5f57 --- /dev/null +++ b/sensu/misc.go @@ -0,0 +1,26 @@ +package sensu + +func StatusCodeToString(status int) string { + var str string + + switch status { + case 200: + str = "OK" + case 201: + str = "Created" + case 202: + str = "Accepted" + case 204: + str = "No Content" + case 400: + str = "Bad Request" + case 404: + str = "Not Found" + case 500: + str = "Internal Server Error" + case 503: + str = "Service Unavailable" + } + + return str +} diff --git a/sensu/request.go b/sensu/request.go new file mode 100644 index 0000000..d5e20ad --- /dev/null +++ b/sensu/request.go @@ -0,0 +1,35 @@ +package sensu + +import ( + "encoding/json" + "errors" + "strings" +) + +type RequestStruct struct { + Check string `json:"check"` + Subscribers []string `json:"subscribers"` +} + +// Issues a check execution request. +func (api API) PostRequest(check string, subscribers []string) error { + request := RequestStruct{ + Check: check, + Subscribers: subscribers, + } + + body, err := json.Marshal(request) + if err != nil { + return err + } + payload := strings.NewReader(string(body)) + + response, err := api.post("/request", payload) + if err != nil { + return err + } else if response.StatusCode != 202 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +} diff --git a/sensu/resolve.go b/sensu/resolve.go new file mode 100644 index 0000000..ae64885 --- /dev/null +++ b/sensu/resolve.go @@ -0,0 +1,35 @@ +package sensu + +import ( + "encoding/json" + "errors" + "strings" +) + +type ResolveStruct struct { + Client string `json:"client"` + Check string `json:"check"` +} + +// Resolves an event. +func (api API) PostResolve(client string, check string) error { + resolve := ResolveStruct{ + Client: client, + Check: check, + } + + body, err := json.Marshal(resolve) + if err != nil { + return err + } + payload := strings.NewReader(string(body)) + + response, err := api.post("/resolve", payload) + if err != nil { + return err + } else if response.StatusCode != 202 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +} diff --git a/sensu/stashes.go b/sensu/stashes.go new file mode 100644 index 0000000..ec2c216 --- /dev/null +++ b/sensu/stashes.go @@ -0,0 +1,98 @@ +package sensu + +import ( + "encoding/json" + "errors" + "fmt" + "regexp" + "strings" +) + +type StashStruct struct { + Path string `json:"path"` + Expire int64 `json:"expire"` +} + +// Returns a list of stashes. +func (api API) GetStashes(stashes interface{}, limit int, offset int) error { + response, err := api.get(fmt.Sprintf("/stashes?limit=%d&offset=%d", limit, offset)) + if err != nil { + return err + } else if response.StatusCode != 200 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), stashes) + if err != nil { + return err + } + + return nil +} + +// Create a stash. +func (api API) PostStashes(stash interface{}) error { + body, err := json.Marshal(stash) + if err != nil { + return err + } + + re := regexp.MustCompile(`"expire":-1[,|\}]`) + payload := strings.NewReader(string(re.ReplaceAll(body, []byte{}))) + + response, err := api.post("/stashes", payload) + if err != nil { + return err + } else if response.StatusCode != 201 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +} + +// Create a stash. +func (api API) PostStashesPath(path string, content interface{}) error { + body, err := json.Marshal(content) + if err != nil { + return err + } + payload := strings.NewReader(string(body)) + + response, err := api.post("/stashes/"+path, payload) + if err != nil { + return err + } else if response.StatusCode != 201 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +} + +// Get a stash. +func (api API) GetStashesPath(path string, content interface{}) error { + response, err := api.get("/stashes/" + path) + if err != nil { + return err + } else if response.StatusCode != 200 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + err = json.Unmarshal([]byte(response.Body), content) + if err != nil { + return err + } + + return nil +} + +// Delete a stash. +func (api API) DeleteStashesPath(path string) error { + response, err := api.delete("/stashes/" + path) + if err != nil { + return err + } else if response.StatusCode != 204 { + return errors.New("sensu: " + StatusCodeToString(response.StatusCode)) + } + + return nil +}