icinga

Icinga2 API client in Go
git clone https://git.olowe.co/icinga
Log | Files | Refs | README | LICENSE

commit e0c24850440b8bcaa5bf6a49c0dd493fb30e94fb
parent 7aa5b0553214edbb1a6e71187215972e1e4877c6
Author: Oliver Lowe <o@olowe.co>
Date:   Fri, 14 Jan 2022 11:21:29 +1100

Support Icinga2 hostgroups

Tests of hostgroup operations are done in TestFilter

Diffstat:
Mcrud.go | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrud.sh | 2+-
Mhost.go | 30+++++++++++++++++++++++++++++-
Mhost_test.go | 25+++++++++++++++++++++++--
Mobject.go | 2+-
Mresponse.go | 6++++++
Muser_test.go | 2+-
7 files changed, 113 insertions(+), 6 deletions(-)

diff --git a/crud.go b/crud.go @@ -156,3 +156,55 @@ func (c *Client) DeleteUser(name string) error { } return nil } + +// HostGroups returns a slice of HostGroup matching the filter expression filter. +// If no hostgroups match, error wraps ErrNoMatch. +// To fetch all hostgroup, set filter to the empty string (""). +func (c *Client) HostGroups(filter string) ([]HostGroup, error) { + objects, err := c.filterObjects("/objects/hostgroups", filter) + if err != nil { + return nil, fmt.Errorf("get hostgroups filter %q: %w", filter, err) + } + var hostgroups []HostGroup + for _, o := range objects { + v, ok := o.(HostGroup) + if !ok { + return nil, fmt.Errorf("get hostgroups filter %q: %T in response", filter, v) + } + hostgroups = append(hostgroups, v) + } + return hostgroups, nil +} + +// LookupHostGroup returns the HostGroup identified by name. If no HostGroup is found, error +// wraps ErrNotExist. +func (c *Client) LookupHostGroup(name string) (HostGroup, error) { + obj, err := c.lookupObject("/objects/hostgroups/" + name) + if err != nil { + return HostGroup{}, fmt.Errorf("lookup hostgroup %s: %w", name, err) + } + v, ok := obj.(HostGroup) + if !ok { + return HostGroup{}, fmt.Errorf("lookup hostgroup %s: result type %T is not HostGroup", name, v) + } + return v, nil +} + +// CreateHostGroup creates hostgroup. Some fields of hostgroup must be set for successful +// creation; see the type definition of HostGroup for details. +func (c *Client) CreateHostGroup(hostgroup HostGroup) error { + if err := c.createObject(hostgroup); err != nil { + return fmt.Errorf("create hostgroup %s: %w", hostgroup.Name, err) + } + return nil +} + +// DeleteHostGroup deletes the HostGroup identified by name. +// If no HostGroup is found, error wraps ErrNotExist. +func (c *Client) DeleteHostGroup(name string) error { + if err := c.deleteObject("/objects/hostgroups/" + name); err != nil { + return fmt.Errorf("delete hostgroup %s: %w", name, err) + } + return nil +} + diff --git a/crud.sh b/crud.sh @@ -1,6 +1,6 @@ #!/bin/sh -types="Host Service User" +types="Host Service User HostGroup" args=`getopt o: $*` if test $? -ne 0 diff --git a/host.go b/host.go @@ -2,7 +2,8 @@ package icinga import "encoding/json" -// Host represents a Host object. +// Host represents a Host object. To create a Host, the Name and CheckCommand +// fields must be set. type Host struct { Name string `json:"name"` Address string `json:"address"` @@ -13,6 +14,11 @@ type Host struct { DisplayName string `json:"display_name"` } +type HostGroup struct { + Name string `json:"name"` + DisplayName string `json:"display_name"` +} + type HostState int const ( @@ -41,6 +47,14 @@ func (h Host) path() string { return "/objects/hosts/" + h.Name } +func (hg HostGroup) name() string { + return hg.Name +} + +func (hg HostGroup) path() string { + return "/objects/hostgroups/" + hg.Name +} + func (h Host) MarshalJSON() ([]byte, error) { type Attrs struct { Address string `json:"address"` @@ -59,3 +73,17 @@ func (h Host) MarshalJSON() ([]byte, error) { } return json.Marshal(jhost) } + +func (hg HostGroup) MarshalJSON() ([]byte, error) { + type attrs struct { + DisplayName string `json:"display_name"` + } + type group struct { + Attrs attrs `json:"attrs"` + } + return json.Marshal(&group{ + Attrs: attrs{ + DisplayName: hg.DisplayName, + }, + }) +} diff --git a/host_test.go b/host_test.go @@ -35,14 +35,28 @@ func TestFilter(t *testing.T) { tp.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} c := http.DefaultClient c.Transport = tp - client, err := Dial("127.0.0.1:5665", "root", "8eec5ede1673b757", c) + client, err := Dial("127.0.0.1:5665", "root", "icinga", c) if err != nil { t.Skipf("no local test icinga? got: %v", err) } + hostgroup := HostGroup{Name: "examples", DisplayName: "Test Group"} + if err := client.CreateHostGroup(hostgroup); err != nil { + t.Error(err) + } + hostgroup, err = client.LookupHostGroup(hostgroup.Name) + if err != nil { + t.Error(err) + } + defer client.DeleteHostGroup(hostgroup.Name) + var want, got []string for i := 0; i < 5; i++ { - h := Host{Name: randomHostname(), CheckCommand: "hostalive"} + h := Host{ + Name: randomHostname(), + CheckCommand: "hostalive", + Groups: []string{hostgroup.Name}, + } want = append(want, h.Name) if err := client.CreateHost(h); err != nil { if !errors.Is(err, ErrExist) { @@ -52,6 +66,13 @@ func TestFilter(t *testing.T) { } t.Logf("created host %s", h.Name) } + defer func() { + for _, name := range want { + if err := client.DeleteHost(name); err != nil { + t.Log(err) + } + } + }() hosts, err := client.Hosts("match(\"*example.org\", host.name)") if err != nil { t.Fatal(err) diff --git a/object.go b/object.go @@ -60,7 +60,7 @@ func (c *Client) filterObjects(objpath, expr string) ([]object, error) { func (c *Client) createObject(obj object) error { buf := &bytes.Buffer{} switch v := obj.(type) { - case Host, Service, User: + case Host, Service, User, HostGroup: if err := json.NewEncoder(buf).Encode(v); err != nil { return err } diff --git a/response.go b/response.go @@ -71,6 +71,12 @@ func parseResponse(r io.Reader) (*response, error) { return nil, err } resp.Results = append(resp.Results, u) + case "HostGroup": + var h HostGroup + if err := json.Unmarshal(r.Attrs, &h); err != nil { + return nil, err + } + resp.Results = append(resp.Results, h) default: return nil, fmt.Errorf("unsupported unmarshal of type %s", r.Type) } diff --git a/user_test.go b/user_test.go @@ -52,7 +52,7 @@ func TestUserRoundTrip(t *testing.T) { tp.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} c := http.DefaultClient c.Transport = tp - client, err := Dial("127.0.0.1:5665", "root", "8eec5ede1673b757", c) + client, err := Dial("127.0.0.1:5665", "root", "icinga", c) if err != nil { t.Skipf("no local test icinga? got: %v", err) }