commit - 82fc97ff8eb537c28bce19af5ffb697773bd200c
commit + 26fb88e9ebc4192da36411de66032158b424369d
blob - 78f3060f0579f6a1d8864e12180a78cf336c87ba
blob + 0f4a86c710832cc1ac128db30560d8b871d6635a
--- README.md
+++ README.md
[](http://godocs.io/olowe.co/icinga)
+Send any patches, questions or requests to the [mailing list][list] [~otl/icinga@lists.sr.ht](mailto:~otl/icinga@lists.sr.ht) 🙂
+
+[list]: https://lists.sr.ht/~otl/icinga
+
## Quick Start
A Client manages interaction with an Icinga2 server.
[godocs]: https://godocs.io/olowe.co/icinga
+## Development
+
+Some code is automatically generated. Ensure it's up-to-date before starting work:
+
+ go generate
+
+Make some changes, then run the tests. No fancy test framework:
+
+ go test
+
+Send any questions, patches, requests to the mailing list [~otl/icinga@lists.sr.ht](mailto:~otl/icinga@lists.sr.ht)
+
+### Testing
+
+Some tests dial an instance of Icinga2 running on the loopback address
+and the standard Icinga2 port 5665 ("127.0.0.1:5665"). If this fails,
+those tests are skipped. To run these tests, create the following API
+user:
+
+ object ApiUser "root" {
+ password = "icinga"
+ permissions = [ "*" ]
+ }
+
+Getting data from 127.0.0.1:5665 to an Icinga server is left as an
+exercise to the reader!
+
+Personally, I run an Alpine Linux virtual machine using qemu. You
+could also use the [official Icinga2 container image][image].
+
+[image]: https://hub.docker.com/r/icinga/icinga2
+
+### Code generation
+
+Source code for the basic lookup, create and delete operations of some
+Icinga2 object types, such as Host and Service, are generated
+automatically.
+
+The shell script crud.sh writes Go source code by reading a template
+file and doing some text substitution. It loops through object types,
+piping the template file crud.skel into the awk script crud.awk for
+each.
+
+crud.sh writes code to the standard output by default:
+
+ ./crud.sh
+
+If the flag `-o` is set, code will be written to the file
+specified instead of to standard output:
+
+ ./crud.sh -o crud.go
+
+Code generation is used because the functions are trivial and call the exact
+same underlying methods on Client anyway. The only thing that differs is the type.
+Perhaps when Go gets type parameters then this will go away?
+
## Why Another Package?
The [icinga2 terraform provider][tf] uses the package [github.com/lrsmith/go-icinga2-api/iapi][lrsmith].
blob - /dev/null
blob + 57745a77e751377bfb9591ab85be6498a03c4c39 (mode 644)
--- /dev/null
+++ crud.awk
+{
+ gsub("TYPE", type)
+ gsub("LOWER", tolower(type))
+ gsub("PLURAL", tolower(type) "s")
+ print
+}
blob - d65e59b24fb1e7f2a481c9205a661f34ad299e46
blob + c91758f751aced6fa19666b0f27ba3d014a2446a
--- host.go
+++ host.go
package icinga
-import (
- "encoding/json"
- "fmt"
-)
+import "encoding/json"
// Host represents a Host object.
type Host struct {
}
return json.Marshal(jhost)
}
-
-// Hosts returns Hosts matching the filter expression filter.
-// If no hosts match, error wraps ErrNoMatch.
-// To fetch all hosts, set filter to the empty string ("").
-func (c *Client) Hosts(filter string) ([]Host, error) {
- objects, err := c.filterObjects("/objects/hosts", filter)
- if err != nil {
- return nil, fmt.Errorf("get hosts filter %q: %w", filter, err)
- }
- var hosts []Host
- for _, o := range objects {
- v, ok := o.(Host)
- if !ok {
- return nil, fmt.Errorf("get all hosts: %T in response", v)
- }
- hosts = append(hosts, v)
- }
- return hosts, nil
-}
-
-// LookupHost returns the Host identified by name. If no Host is found,
-// error wraps ErrNotExist.
-func (c *Client) LookupHost(name string) (Host, error) {
- obj, err := c.lookupObject("/objects/hosts/" + name)
- if err != nil {
- return Host{}, fmt.Errorf("lookup %s: %w", name, err)
- }
- v, ok := obj.(Host)
- if !ok {
- return Host{}, fmt.Errorf("lookup %s: result type %T is not host", name, obj)
- }
- return v, nil
-}
-
-// CreateHost creates the Host host.
-// The Name and CheckCommand fields of host must be non-zero.
-func (c *Client) CreateHost(host Host) error {
- if err := c.createObject(host); err != nil {
- return fmt.Errorf("create host %s: %w", host.Name, err)
- }
- return nil
-}
-
-// DeleteHost deletes the Host identified by name.
-// If no Host is found, error wraps ErrNotExist.
-func (c *Client) DeleteHost(name string) error {
- if err := c.deleteObject("/objects/hosts/" + name); err != nil {
- return fmt.Errorf("delete host %s: %w", name, err)
- }
- return nil
-}
blob - /dev/null
blob + a11f3493120c3b4c35c45f48922a3837c1ebf953 (mode 644)
--- /dev/null
+++ crud.go
+// Code generated by ./crud.sh ; DO NOT EDIT.
+
+package icinga
+
+import "fmt"
+
+// Hosts returns a slice of Host matching the filter expression filter.
+// If no hosts match, error wraps ErrNoMatch.
+// To fetch all host, set filter to the empty string ("").
+func (c *Client) Hosts(filter string) ([]Host, error) {
+ objects, err := c.filterObjects("/objects/hosts", filter)
+ if err != nil {
+ return nil, fmt.Errorf("get hosts filter %q: %w", filter, err)
+ }
+ var hosts []Host
+ for _, o := range objects {
+ v, ok := o.(Host)
+ if !ok {
+ return nil, fmt.Errorf("get hosts filter %q: %T in response", filter, v)
+ }
+ hosts = append(hosts, v)
+ }
+ return hosts, nil
+}
+
+// LookupHost returns the Host identified by name. If no Host is found, error
+// wraps ErrNotExist.
+func (c *Client) LookupHost(name string) (Host, error) {
+ obj, err := c.lookupObject("/objects/hosts/" + name)
+ if err != nil {
+ return Host{}, fmt.Errorf("lookup host %s: %w", name, err)
+ }
+ v, ok := obj.(Host)
+ if !ok {
+ return Host{}, fmt.Errorf("lookup host %s: result type %T is not Host", name, v)
+ }
+ return v, nil
+}
+
+// CreateHost creates host. Some fields of host must be set for successful
+// creation; see the type definition of Host for details.
+func (c *Client) CreateHost(host Host) error {
+ if err := c.createObject(host); err != nil {
+ return fmt.Errorf("create host %s: %w", host.Name, err)
+ }
+ return nil
+}
+
+// DeleteHost deletes the Host identified by name.
+// If no Host is found, error wraps ErrNotExist.
+func (c *Client) DeleteHost(name string) error {
+ if err := c.deleteObject("/objects/hosts/" + name); err != nil {
+ return fmt.Errorf("delete host %s: %w", name, err)
+ }
+ return nil
+}
+
+// Services returns a slice of Service matching the filter expression filter.
+// If no hosts match, error wraps ErrNoMatch.
+// To fetch all service, set filter to the empty string ("").
+func (c *Client) Services(filter string) ([]Service, error) {
+ objects, err := c.filterObjects("/objects/services", filter)
+ if err != nil {
+ return nil, fmt.Errorf("get services filter %q: %w", filter, err)
+ }
+ var services []Service
+ for _, o := range objects {
+ v, ok := o.(Service)
+ if !ok {
+ return nil, fmt.Errorf("get services filter %q: %T in response", filter, v)
+ }
+ services = append(services, v)
+ }
+ return services, nil
+}
+
+// LookupService returns the Service identified by name. If no Service is found, error
+// wraps ErrNotExist.
+func (c *Client) LookupService(name string) (Service, error) {
+ obj, err := c.lookupObject("/objects/services/" + name)
+ if err != nil {
+ return Service{}, fmt.Errorf("lookup service %s: %w", name, err)
+ }
+ v, ok := obj.(Service)
+ if !ok {
+ return Service{}, fmt.Errorf("lookup service %s: result type %T is not Service", name, v)
+ }
+ return v, nil
+}
+
+// CreateService creates service. Some fields of service must be set for successful
+// creation; see the type definition of Service for details.
+func (c *Client) CreateService(service Service) error {
+ if err := c.createObject(service); err != nil {
+ return fmt.Errorf("create service %s: %w", service.Name, err)
+ }
+ return nil
+}
+
+// DeleteService deletes the Service identified by name.
+// If no Service is found, error wraps ErrNotExist.
+func (c *Client) DeleteService(name string) error {
+ if err := c.deleteObject("/objects/services/" + name); err != nil {
+ return fmt.Errorf("delete service %s: %w", name, err)
+ }
+ return nil
+}
+
+// Users returns a slice of User matching the filter expression filter.
+// If no hosts match, error wraps ErrNoMatch.
+// To fetch all user, set filter to the empty string ("").
+func (c *Client) Users(filter string) ([]User, error) {
+ objects, err := c.filterObjects("/objects/users", filter)
+ if err != nil {
+ return nil, fmt.Errorf("get users filter %q: %w", filter, err)
+ }
+ var users []User
+ for _, o := range objects {
+ v, ok := o.(User)
+ if !ok {
+ return nil, fmt.Errorf("get users filter %q: %T in response", filter, v)
+ }
+ users = append(users, v)
+ }
+ return users, nil
+}
+
+// LookupUser returns the User identified by name. If no User is found, error
+// wraps ErrNotExist.
+func (c *Client) LookupUser(name string) (User, error) {
+ obj, err := c.lookupObject("/objects/users/" + name)
+ if err != nil {
+ return User{}, fmt.Errorf("lookup user %s: %w", name, err)
+ }
+ v, ok := obj.(User)
+ if !ok {
+ return User{}, fmt.Errorf("lookup user %s: result type %T is not User", name, v)
+ }
+ return v, nil
+}
+
+// CreateUser creates user. Some fields of user must be set for successful
+// creation; see the type definition of User for details.
+func (c *Client) CreateUser(user User) error {
+ if err := c.createObject(user); err != nil {
+ return fmt.Errorf("create user %s: %w", user.Name, err)
+ }
+ return nil
+}
+
+// DeleteUser deletes the User identified by name.
+// If no User is found, error wraps ErrNotExist.
+func (c *Client) DeleteUser(name string) error {
+ if err := c.deleteObject("/objects/users/" + name); err != nil {
+ return fmt.Errorf("delete user %s: %w", name, err)
+ }
+ return nil
+}
+
blob - /dev/null
blob + c2fa08026c533355acbe737f3b03804ce4f614cc (mode 755)
--- /dev/null
+++ crud.sh
+#!/bin/sh
+
+types="Host Service User"
+
+args=`getopt o: $*`
+if test $? -ne 0
+then
+ echo "usage: lookupgen.sh [-o file]"
+ exit 2
+fi
+set -- $args
+while test "$#" -ne 0
+do
+ case "$1"
+ in
+ -o)
+ file="$2"; shift; shift;;
+ --)
+ shift; break;;
+ esac
+done
+
+head="// Code generated by $0 $@; DO NOT EDIT.
+
+package icinga
+
+import \"fmt\"
+"
+
+if test -n "$file"
+then
+ echo "$head" > "$file"
+else
+ echo "$head"
+fi
+
+for t in $types
+do
+ if test -n "$file"
+ then
+ awk -v "type=$t" -f crud.awk crud.skel >> "$file"
+ else
+ awk -v "type=$t" -f crud.awk crud.skel
+ fi
+done
blob - /dev/null
blob + cf986a305b5ea0e52dc58d6de5e4a6469b80f18a (mode 644)
--- /dev/null
+++ crud.skel
+// TYPEs returns a slice of TYPE matching the filter expression filter.
+// If no hosts match, error wraps ErrNoMatch.
+// To fetch all LOWER, set filter to the empty string ("").
+func (c *Client) TYPEs(filter string) ([]TYPE, error) {
+ objects, err := c.filterObjects("/objects/PLURAL", filter)
+ if err != nil {
+ return nil, fmt.Errorf("get PLURAL filter %q: %w", filter, err)
+ }
+ var PLURAL []TYPE
+ for _, o := range objects {
+ v, ok := o.(TYPE)
+ if !ok {
+ return nil, fmt.Errorf("get PLURAL filter %q: %T in response", filter, v)
+ }
+ PLURAL = append(PLURAL, v)
+ }
+ return PLURAL, nil
+}
+
+// LookupTYPE returns the TYPE identified by name. If no TYPE is found, error
+// wraps ErrNotExist.
+func (c *Client) LookupTYPE(name string) (TYPE, error) {
+ obj, err := c.lookupObject("/objects/PLURAL/" + name)
+ if err != nil {
+ return TYPE{}, fmt.Errorf("lookup LOWER %s: %w", name, err)
+ }
+ v, ok := obj.(TYPE)
+ if !ok {
+ return TYPE{}, fmt.Errorf("lookup LOWER %s: result type %T is not TYPE", name, v)
+ }
+ return v, nil
+}
+
+// CreateTYPE creates LOWER. Some fields of LOWER must be set for successful
+// creation; see the type definition of TYPE for details.
+func (c *Client) CreateTYPE(LOWER TYPE) error {
+ if err := c.createObject(LOWER); err != nil {
+ return fmt.Errorf("create LOWER %s: %w", LOWER.Name, err)
+ }
+ return nil
+}
+
+// DeleteTYPE deletes the TYPE identified by name.
+// If no TYPE is found, error wraps ErrNotExist.
+func (c *Client) DeleteTYPE(name string) error {
+ if err := c.deleteObject("/objects/PLURAL/" + name); err != nil {
+ return fmt.Errorf("delete LOWER %s: %w", name, err)
+ }
+ return nil
+}
+
blob - a09a08269b2f988f03f4099e057e222fde2177ac
blob + 72cb8f1953b6a3cdff843006531f78c4a0895fa8
--- object.go
+++ object.go
+
package icinga
import (
type object interface {
name() string
- attrs() map[string]interface{}
path() string
}
+//go:generate ./crud.sh -o crud.go
+
func (c *Client) lookupObject(objpath string) (object, error) {
resp, err := c.get(objpath, "")
if err != nil {
blob - 8d8a80f0fe3ffcf01b3a2054aaf32e0b6a8dccbf
blob + 57ff914a3e5b3f142c17ae0e797b260301b9233d
--- service.go
+++ service.go
package icinga
-import (
- "encoding/json"
- "fmt"
-)
+import "encoding/json"
func (s Service) name() string {
return s.Name
}{Attrs: attrs}
return json.Marshal(jservice)
}
-
-func (c *Client) CreateService(service Service) error {
- if err := c.createObject(service); err != nil {
- return fmt.Errorf("create service %s: %w", service.Name, err)
- }
- return nil
-}
-
-func (c *Client) LookupService(name string) (Service, error) {
- obj, err := c.lookupObject("/objects/services/" + name)
- if err != nil {
- return Service{}, fmt.Errorf("lookup %s: %w", name, err)
- }
- v, ok := obj.(Service)
- if !ok {
- return Service{}, fmt.Errorf("lookup %s: result type %T is not service", name, obj)
- }
- return v, nil
-}
blob - 1ad4fa1f9efbf7d447b2dbac6ea29dbda6992300
blob + b9c85caf88e41b5f1c13adf09e932558615f949f
--- user.go
+++ user.go
package icinga
-import (
- "encoding/json"
- "fmt"
-)
+import "encoding/json"
// User represents a User object.
// Note that this is different from an ApiUser.
m["email"] = u.Email
return m
}
-
-func (c *Client) Users() ([]User, error) {
- objects, err := c.filterObjects("/objects/users", "")
- if err != nil {
- return nil, fmt.Errorf("get all users: %w", err)
- }
- var users []User
- for _, o := range objects {
- v, ok := o.(User)
- if !ok {
- return nil, fmt.Errorf("get all users: %T in response", v)
- }
- users = append(users, v)
- }
- return users, nil
-}
-
-func (c *Client) LookupUser(name string) (User, error) {
- obj, err := c.lookupObject("/objects/users/" + name)
- if err != nil {
- return User{}, fmt.Errorf("lookup %s: %w", name, err)
- }
- v, ok := obj.(User)
- if !ok {
- return User{}, fmt.Errorf("lookup %s: result type %T is not user", name, v)
- }
- return v, nil
-}
-
-// CreateUser creates user.
-// An error is returned if the User already exists or on any other error.
-func (c *Client) CreateUser(user User) error {
- if err := c.createObject(user); err != nil {
- return fmt.Errorf("create user %s: %w", user.Name, err)
- }
- return nil
-}
-
-// DeleteUser deletes the User identified by name.
-// ErrNotExist is returned if the User doesn't exist.
-func (c *Client) DeleteUser(name string) error {
- if err := c.deleteObject("/objects/users/" + name); err != nil {
- return fmt.Errorf("delete user %s: %w", name, err)
- }
- return nil
-}