diff --git a/cmd/run.go b/cmd/run.go deleted file mode 100644 index 4bb030d..0000000 --- a/cmd/run.go +++ /dev/null @@ -1,22 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -func init() { - runCmd.Flags().StringP("htpasswd", "p", "/opt/sites/$SITE/etc/htpasswd", "htpasswd file") - rootCmd.AddCommand(runCmd) -} - -var runCmd = &cobra.Command{ - Use: "run [site]", - Short: "Run the Proxy", - Long: `Run the Check_MK LQL API Proxy`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Check_MK LQL API Proxy v0.1 -- HEAD") - }, -} diff --git a/example.sh b/example.sh index 2f6ec3c..862b757 100755 --- a/example.sh +++ b/example.sh @@ -1,11 +1,15 @@ #!/bin/bash -ARGS=$2 +# Configuration SERVER="http://localhost:8080" if [ -n "$1" ]; then SERVER=$1 fi +ARGS=$2 + + +# Execute set -ex # GET Hosts @@ -15,4 +19,7 @@ curl $2 -X POST -d '{"method": "GET", "table": "hosts", "columns": ["name", "add curl $2 -X POST -d '{"method": "GET", "table": "hosts", "columns": ["name", "address", "groups"], "limit": 3}' $SERVER/v1/raw # host stats from the tactical_overview widget -curl $2 -X POST -d '{"method": "GET", "table": "hosts", "query": [["Stats", "state >= 0"], ["Stats", "state > 0"], ["Stats", "scheduled_downtime_depth = 0"], ["StatsAnd", "2"], ["Stats", "state > 0"], ["Stats", "scheduled_downtime_depth = 0"], ["Stats", "acknowledged = 0"], ["StatsAnd", "3"], ["Stats", "host_staleness >= 1.5"], ["Stats", "host_scheduled_downtime_depth = 0"], ["StatsAnd", "2"]]}' $SERVER/v1/raw \ No newline at end of file +curl $2 -X POST -d '{"method": "GET", "table": "hosts", "query": [["Stats", "state >= 0"], ["Stats", "state > 0"], ["Stats", "scheduled_downtime_depth = 0"], ["StatsAnd", "2"], ["Stats", "state > 0"], ["Stats", "scheduled_downtime_depth = 0"], ["Stats", "acknowledged = 0"], ["StatsAnd", "3"], ["Stats", "host_staleness >= 1.5"], ["Stats", "host_scheduled_downtime_depth = 0"], ["StatsAnd", "2"]]}' $SERVER/v1/raw + +# Tactical overview data :) +curl $2 $SERVER/v1/stats/tactical_overview \ No newline at end of file diff --git a/lql/server.go b/lql/server.go index 913961e..0626c68 100644 --- a/lql/server.go +++ b/lql/server.go @@ -35,9 +35,11 @@ func NewServer(client *Client, logger *log.Logger, htpasswdPath string) (*Server // the API that will be served with // the specification. infos := &openapi.Info{ - Title: "LQL API", - Description: `This is the LQL API for your check_mk Server.`, - Version: "unset", + Title: "LQL API", + Description: `This is the LQL API for your check_mk Server. + +All v1/ endpoints require http basic auth`, + Version: "unset", } // Create a new route that serve the OpenAPI spec. fizz.GET("/openapi.json", nil, fizz.OpenAPI(infos, "json")) @@ -48,6 +50,11 @@ func NewServer(client *Client, logger *log.Logger, htpasswdPath string) (*Server htpasswd := auth.HtpasswdFileProvider(htpasswdPath) authenticator := auth.NewBasicAuthenticator("LQL API", htpasswd) v1Group.Use(basicAuthMiddleware(authenticator)) + } else { + // Inject empty user if not .htpasswd have been given + v1Group.Use(func(c *gin.Context) { + c.Set("user", "") + }) } v1Routes(v1Group) diff --git a/lql/v1.go b/lql/v1.go index ba4f8e1..212e3c3 100644 --- a/lql/v1.go +++ b/lql/v1.go @@ -6,22 +6,13 @@ import ( ) func v1Routes(grp *fizz.RouterGroup) { - // // Add a new fruit to the market. - // grp.POST("", []fizz.OperationOption{ - // fizz.Summary("Add a fruit to the market"), - // fizz.Response("400", "Bad request", nil, nil), - // }, tonic.Handler(CreateFruit, 200)) - - // // Remove a fruit from the market, - // // probably because it rotted. - // grp.DELETE("/:name", []fizz.OperationOption{ - // fizz.Summary("Remove a fruit from the market"), - // fizz.Response("400", "Fruit not found", nil, nil), - // }, tonic.Handler(DeleteFruit, 204)) - - // // List all available fruits. grp.POST("/raw", []fizz.OperationOption{ fizz.Summary("GET RAW LQL Data or execute a COMMAND"), fizz.Response("400", "Bad request", nil, nil), }, tonic.Handler(v1RawPost, 200)) + + grp.GET("/stats/tactical_overview", []fizz.OperationOption{ + fizz.Summary("GET tactical overview data"), + fizz.Response("400", "Bad request", nil, nil), + }, tonic.Handler(v1StatsGetTacticalOverview, 200)) } diff --git a/lql/v1raw.go b/lql/v1raw.go index ea7a3f6..e575238 100644 --- a/lql/v1raw.go +++ b/lql/v1raw.go @@ -30,6 +30,7 @@ func v1RawPost(c *gin.Context, params *V1RawRequestParams) ([]gin.H, error) { if err != nil { return nil, err } + user := c.GetString("user") // Param validation and request building request := []string{} @@ -63,5 +64,5 @@ func v1RawPost(c *gin.Context, params *V1RawRequestParams) ([]gin.H, error) { return nil, fmt.Errorf("Unknown Method requested: '%s'", params.Method) } - return client.Request(c, strings.Join(request, "\n"), "", params.Limit) + return client.Request(c, strings.Join(request, "\n"), user, params.Limit) } diff --git a/lql/v1stats.go b/lql/v1stats.go new file mode 100644 index 0000000..8b3d8b2 --- /dev/null +++ b/lql/v1stats.go @@ -0,0 +1,120 @@ +package lql + +import ( + "errors" + + "github.com/gin-gonic/gin" +) + +type V1StatsTacticalOverview struct { + Host *V1StatsTacticalOverviewEntry `json:"host" validate:"required" description:"Host stats"` + Services *V1StatsTacticalOverviewEntry `json:"services" validate:"required" description:"Service stats"` + Events *V1StatsTacticalOverviewEntry `json:"events" validate:"required" description:"Event stats"` +} + +type V1StatsTacticalOverviewEntry struct { + All float64 `json:"all" validate:"required" description:"all services/hosts"` + Problems float64 `json:"problems" validate:"require" description:"Num of problems"` + Unhandled float64 `json:"unhandled" validate:"require" description:"Num of unhandled"` + Stale float64 `json:"stale" validate:"require" description:"Num of stale"` +} + +func v1StatsGetTacticalOverview(c *gin.Context) (*V1StatsTacticalOverview, error) { + client, err := GinGetLqlClient(c) + if err != nil { + return nil, err + } + user := c.GetString("user") + + msg := `GET hosts +Stats: state >= 0 +Stats: state > 0 +Stats: scheduled_downtime_depth = 0 +StatsAnd: 2 +Stats: state > 0 +Stats: scheduled_downtime_depth = 0 +Stats: acknowledged = 0 +StatsAnd: 3 +Stats: host_staleness >= 1.5 +Stats: host_scheduled_downtime_depth = 0 +StatsAnd: 2` + + rsp, err := client.Request(c, msg, user, 0) + if err != nil { + return nil, err + } + + if len(rsp) < 1 { + return nil, errors.New("Received invalid host stats from socket") + } + + host := &V1StatsTacticalOverviewEntry{} + host.All = rsp[0]["stats_1"].(float64) + host.Problems = rsp[0]["stats_2"].(float64) + host.Unhandled = rsp[0]["stats_3"].(float64) + host.Stale = rsp[0]["stats_4"].(float64) + + msg = `GET services +Stats: state >= 0 +Stats: state > 0 +Stats: scheduled_downtime_depth = 0 +Stats: host_scheduled_downtime_depth = 0 +Stats: host_state = 0 +StatsAnd: 4 +Stats: state > 0 +Stats: scheduled_downtime_depth = 0 +Stats: host_scheduled_downtime_depth = 0 +Stats: acknowledged = 0 +Stats: host_state = 0 +StatsAnd: 5 +Stats: service_staleness >= 1.5 +Stats: host_scheduled_downtime_depth = 0 +Stats: service_scheduled_downtime_depth = 0 +StatsAnd: 3` + + rsp, err = client.Request(c, msg, user, 0) + if err != nil { + return nil, err + } + + if len(rsp) < 1 { + return nil, errors.New("Received invalid host stats from socket") + } + + svc := &V1StatsTacticalOverviewEntry{} + svc.All = rsp[0]["stats_1"].(float64) + svc.Problems = rsp[0]["stats_2"].(float64) + svc.Unhandled = rsp[0]["stats_3"].(float64) + svc.Stale = rsp[0]["stats_4"].(float64) + + msg = `GET eventconsoleevents +Stats: event_phase = open +Stats: event_phase = ack +StatsOr: 2 +Stats: event_phase = open +Stats: event_phase = ack +StatsOr: 2 +Stats: event_state != 0 +StatsAnd: 2 +Stats: event_phase = open +Stats: event_state != 0 +Stats: event_host_in_downtime != 1 +StatsAnd: 3` + + rsp, err = client.Request(c, msg, user, 0) + if err != nil { + return nil, err + } + + if len(rsp) < 1 { + return nil, errors.New("Received invalid host stats from socket") + } + + ev := &V1StatsTacticalOverviewEntry{} + ev.All = rsp[0]["stats_1"].(float64) + ev.Problems = rsp[0]["stats_2"].(float64) + ev.Unhandled = rsp[0]["stats_3"].(float64) + ev.Stale = 0 + + return &V1StatsTacticalOverview{Host: host, Services: svc, Events: ev}, nil +}