Skip to content

Commit

Permalink
Rewrite binding code base
Browse files Browse the repository at this point in the history
 * Rename Livestatus struct to Client
 * Use net.Dialer instead of function in NewClientWithDialer
 * Handle Query/Command separately to permit reuse over multiple
   clients
 * Rewrite Nagios commands generation script (code now respects "go
   fmt")
 * Fix some linting issues
  • Loading branch information
vbatoufflet committed Jan 14, 2018
1 parent 77cc4b6 commit e3de2df
Show file tree
Hide file tree
Showing 18 changed files with 3,450 additions and 2,625 deletions.
37 changes: 22 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
go-livestatus [![](https://api.travis-ci.org/vbatoufflet/go-livestatus.png)](https://travis-ci.org/vbatoufflet/go-livestatus)
go-livestatus
=============

This package implements a [MK Livestatus][2] binding for Go.
[![][travis-badge]][travis-url] [![][godoc-badge]][godoc-url] [![][report-badge]][report-url]

The source code is available at [Github][0], licensed under the terms of the [BSD license][1].
This package implements a [MK Livestatus][mklivestatus-url] binding for Go.

The source code is available at [Github][project-url], licensed under the terms of the [BSD license][license-url].

Usage
-----
Expand All @@ -19,15 +21,16 @@ import (
)

func main() {
l := livestatus.NewLivestatus("tcp", "192.168.0.10:6557")
// or l := livestatus.NewLivestatus("unix", "/var/run/nagios/livestatus.sock")
c := livestatus.NewClient("tcp", "localhost:6557")
// or c := livestatus.NewClient("unix", "/var/run/nagios/livestatus.sock")
defer c.Close()

q := l.Query("hosts")
q := livestatus.NewQuery("hosts")
q.Columns("name", "state", "last_time_down")
q.Filter("name ~ ^db[0-9]+\\.")
// or q := l.Query("hosts").Columns("name", "state", "last_time_down").Filter("name ~ ^db[0-9]+\\.")
// or q := livestatus.Query("hosts").Columns("name", "state", "last_time_down").Filter("name ~ ^db[0-9]+\\.")

resp, err := q.Exec()
resp, err := c.Exec(q)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s", err)
os.Exit(1)
Expand All @@ -44,12 +47,12 @@ func main() {
fmt.Fprintf(os.Stderr, "Warning: %s", err)
}

lastDown, err := r.GetTime("last_time_down")
lastTimeDown, err := r.GetTime("last_time_down")
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: %s", err)
}

fmt.Printf("Host: %s, State: %d, Last time down: %s\n", name, state, lastDown)
fmt.Printf("Host: %s, State: %d, Last time down: %s\n", name, state, lastTimeDown)
}
}
```
Expand All @@ -61,8 +64,12 @@ Host: db1.example.net, State: 0, Last time down: 2015-04-03 06:54:32 +0200 CEST
Host: db2.example.net, State: 0, Last time down: 2015-06-07 12:34:56 +0200 CEST
```

See [GoDoc](https://godoc.org/github.com/vbatoufflet/go-livestatus) for further details.

[0]: https://github.com/vbatoufflet/go-livestatus
[1]: http://opensource.org/licenses/BSD-3-Clause
[2]: http://mathias-kettner.com/checkmk_livestatus.html
[godoc-badge]: https://godoc.org/github.com/vbatoufflet/go-livestatus?status.svg
[godoc-url]: https://godoc.org/github.com/vbatoufflet/go-livestatus
[license-url]: https://opensource.org/licenses/BSD-3-Clause
[mklivestatus-url]: https://mathias-kettner.com/checkmk_livestatus.html
[project-url]: https://github.com/vbatoufflet/go-livestatus
[report-badge]: https://goreportcard.com/badge/github.com/vbatoufflet/go-livestatus
[report-url]: https://goreportcard.com/report/github.com/vbatoufflet/go-livestatus
[travis-badge]: https://travis-ci.org/vbatoufflet/go-livestatus.svg
[travis-url]: https://travis-ci.org/vbatoufflet/go-livestatus
56 changes: 56 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package livestatus

import (
"net"
)

const bufferSize = 1024

// Client represents a Livestatus client instance.
type Client struct {
network string
address string
dialer *net.Dialer
conn net.Conn
}

// NewClient creates a new Livestatus client instance.
func NewClient(network, address string) *Client {
return NewClientWithDialer(network, address, new(net.Dialer))
}

// NewClientWithDialer creates a new Livestatus client instance using a provided network dialer.
func NewClientWithDialer(network, address string, dialer *net.Dialer) *Client {
return &Client{
network: network,
address: address,
dialer: dialer,
}
}

// Close closes any remaining connection.
func (c *Client) Close() {
if c.conn != nil {
c.conn.Close()
c.conn = nil
}
}

// Exec executes a given Livestatus query.
func (c *Client) Exec(r Request) (*Response, error) {
var err error

// Initialize connection if none available
if c.conn == nil {
c.conn, err = c.dialer.Dial(c.network, c.address)
if err != nil {
return nil, err
}

if !r.keepAlive() {
defer c.Close()
}
}

return r.handle(c.conn)
}
92 changes: 24 additions & 68 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,88 +7,44 @@ import (
"time"
)

// Command is a binding command instance.
// Command represents a Livestatus command instance.
type Command struct {
cmd string
vals []string
ls *Livestatus
name string
args []string
}

func newCommand(ls *Livestatus) *Command {
// NewCommand creates a new Livestatus command instance.
func NewCommand(name string, args ...string) *Command {
return &Command{
ls: ls,
name: name,
args: args,
}
}

func (c *Command) Raw(cmd string) {
c.cmd = cmd
// Arg appends a new argument to the command.
func (c *Command) Arg(v interface{}) *Command {
c.args = append(c.args, fmt.Sprintf("%v", v))
return c
}

func (c *Command) Arg(v interface{}) {
c.vals = append(c.vals, fmt.Sprintf("%v", v))
}

type CommandOpFunc func(*Command)

func (c *Command) Op(op CommandOpFunc) {
op(c)
}

// KeepAliveOff disables the default keepalive from Command
func (q *Query) KeepAliveOff() *Query {
q.ls.keepalive = false
return q
}

// Exec executes the query.
func (c *Command) Exec() (*Response, error) {
resp := &Response{}

var err error
var conn net.Conn

if c.ls.keepConn != nil {
conn = c.ls.keepConn
} else {
// Connect to socket
conn, err = c.dial()
if err != nil {
return nil, err
}
}

if !c.ls.keepalive {
c.ls.keepConn = nil
defer conn.Close()
} else {
c.ls.keepConn = conn
// String returns a string representation of the Livestatus command.
func (c Command) String() string {
s := fmt.Sprintf("COMMAND [%d] %s", time.Now().Unix(), c.name)
if len(c.args) > 0 {
s += ";" + strings.Join(c.args, ";")
}
s += "\n\n"

// Send command data
cmd, err := c.buildCmd(time.Now())
if err != nil {
return nil, err
}

conn.Write([]byte(cmd))
// You get nothing back from an external command
// no way of knowing if this has worked

return resp, nil
return s
}

func (c *Command) buildCmd(t time.Time) (string, error) {
cmdStr := fmt.Sprintf("COMMAND [%d] %s", t.Unix(), c.cmd)
cmdStr = fmt.Sprintf("%s;%s", cmdStr, strings.Join(c.vals, ";"))
cmdStr = strings.TrimRight(cmdStr, ";")
func (c Command) handle(conn net.Conn) (*Response, error) {
// Send query data
conn.Write([]byte(c.String()))

return fmt.Sprintf("%s\n", cmdStr), nil
return nil, nil
}

func (c *Command) dial() (net.Conn, error) {
if c.ls.dialer != nil {
return c.ls.dialer()
} else {
return net.Dial(c.ls.network, c.ls.address)
}
func (c Command) keepAlive() bool {
return true
}
45 changes: 45 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package livestatus

import (
"fmt"
"testing"
"time"
)

func Test_Command(t *testing.T) {
expected := fmt.Sprintf("COMMAND [%d] command1\n", time.Now().Unix())

c := NewCommand("command1")

result := c.String()
if result != expected {
t.Logf("\nExpected %q\nbut got %q\n", expected, result)
t.Fail()
}
}

func Test_CommandWithInlineArgs(t *testing.T) {
expected := fmt.Sprintf("COMMAND [%d] command1;arg1;arg2\n", time.Now().Unix())

c := NewCommand("command1", "arg1", "arg2")

result := c.String()
if result != expected {
t.Logf("\nExpected %q\nbut got %q\n", expected, result)
t.Fail()
}
}

func Test_CommandWithArgs(t *testing.T) {
expected := fmt.Sprintf("COMMAND [%d] command1;arg1;arg2\n", time.Now().Unix())

c := NewCommand("command1")
c.Arg("arg1")
c.Arg("arg2")

result := c.String()
if result != expected {
t.Logf("\nExpected %q\nbut got %q\n", expected, result)
t.Fail()
}
}
10 changes: 6 additions & 4 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"errors"
)

// Record retrieval errors
var (
ErrUnknownColumn = errors.New("unknown record column")
ErrInvalidQuery = errors.New("invalid query")
ErrInvalidValue = errors.New("invalid record value")
// ErrInvalidQuery represents an invalid query error.
ErrInvalidQuery = errors.New("invalid query")
// ErrInvalidType represents an invalid type error.
ErrInvalidType = errors.New("invalid type")
// ErrUnknownColumn represents an unknown column error.
ErrUnknownColumn = errors.New("unknown column")
)
53 changes: 0 additions & 53 deletions livestatus.go

This file was deleted.

Loading

0 comments on commit e3de2df

Please sign in to comment.