commit - 6e415953de07ac11e0f54ac397d156b23f384a15
commit + 5055878019b72a9ed3408e337a21e868c3842cf5
blob - 89a3a5d5f479418d18c6e8b14503b9e41263a13a
blob + bdf22d2e28488f143a1bac4f1483ab50641bc261
--- host.go
+++ host.go
package icinga
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+)
+
+// Host represents a Host object.
type Host struct {
- Name string
- State int
+ Name string `json:"name"`
+ Address string `json:"address"`
+ Address6 string `json:"address6"`
+ Groups []string `json:"groups"`
+ State int `json:"state"`
+ CheckCommand string `json:"check_command"`
+ DisplayName string `json:"display_name"`
}
+type hostresults struct {
+ Results []hostresult `json:"results"`
+ results
+}
+
+type hostresult struct {
+ Host Host `json:"attrs"`
+ result
+}
+
var ErrNoHost = errors.New("no such host")
+func (h Host) MarshalJSON() ([]byte, error) {
+ type Attrs struct {
+ Address string `json:"address"`
+ CheckCommand string `json:"check_command"`
+ DisplayName string `json:"display_name"`
+ }
+ type host struct {
+ Attrs Attrs `json:"attrs"`
+ }
+ jhost := &host{
+ Attrs: Attrs{
+ Address: h.Address,
+ CheckCommand: h.CheckCommand,
+ DisplayName: h.DisplayName,
+ },
+ }
+ return json.Marshal(jhost)
+}
+
+// Hosts returns all Hosts in the Icinga2 configuration.
func (c *Client) Hosts() ([]Host, error) {
- _, err := c.get("/objects/hosts")
+ resp, err := c.get("/objects/hosts")
if err != nil {
return nil, err
}
- return nil, err
+ defer resp.Body.Close()
+ var res hostresults
+ if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
+ return nil, err
+ }
+ if res.Err() != nil {
+ return nil, res.Err()
+ }
+ var hosts []Host
+ for _, r := range res.Results {
+ hosts = append(hosts, r.Host)
+ }
+ return hosts, nil
}
+// LookupHost returns the Host identified by name.
+// If no Host is found, error wraps ErrNoHost.
func (c *Client) LookupHost(name string) (Host, error) {
resp, err := c.get("/objects/hosts/" + name)
if err != nil {
return Host{}, err
}
+// CreateHost creates the Host host.
+// The Name and CheckCommand fields of host must be non-zero.
+func (c *Client) CreateHost(host Host) error {
+ buf := &bytes.Buffer{}
+ if err := json.NewEncoder(buf).Encode(host); err != nil {
+ return err
+ }
+ if err := c.put("/objects/hosts/"+host.Name, buf); 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 ErrNoObject.
func (c *Client) DeleteHost(name string) error {
if err := c.delete("/objects/hosts/" + name); err != nil {
return fmt.Errorf("delete host %s: %w", name, err)
blob - 288e822520bdd2fc700aaa803244e7e0b422362a
blob + f7e037875aad09f3f9c8d9d471b89047f21183d9
--- http.go
+++ http.go
package icinga
import (
+ "encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
}
type result struct {
- Attrs interface{}
+ Attrs map[string]interface{}
Code int
Errors []string
Name string
var ErrNoObject = errors.New("no such object")
-func (res results) Error() string {
- var s []string
+func (res results) Err() error {
+ if len(res.Results) == 0 {
+ return nil
+ }
+ var errs []string
for _, r := range res.Results {
- s = append(s, r.Error())
+ if len(r.Errors) == 0 {
+ continue
+ }
+ errs = append(errs, strings.Join(r.Errors, ", "))
}
- return strings.Join(s, ", ")
+ if len(errs) == 0 {
+ return nil
+ }
+ return errors.New(strings.Join(errs, ", "))
}
-func (r result) Error() string {
- return strings.Join(r.Errors, ", ")
-}
-
func newRequest(method, host, path string, body io.Reader) (*http.Request, error) {
url := "https://" + host + versionPrefix + path
req, err := http.NewRequest(method, url, body)
return c.do(req)
}
-func (c *Client) put(path string, body io.Reader) (*http.Response, error) {
+func (c *Client) put(path string, body io.Reader) error {
req, err := newRequest(http.MethodPut, c.host, path, body)
if err != nil {
- return nil, err
+ return err
}
- return c.do(req)
+ resp, err := c.do(req)
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode == http.StatusOK {
+ return nil
+ }
+ defer resp.Body.Close()
+ var results results
+ if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
+ return fmt.Errorf("decode response: %w", err)
+ }
+ return results.Err()
}
func (c *Client) delete(path string) error {
req, err := newRequest(http.MethodDelete, c.host, path, nil)
if err != nil {
- return nil, err
+ return err
}
resp, err := c.do(req)
if err != nil {
if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
return fmt.Errorf("decode response: %w", err)
}
- return results
+ return results.Err()
}
func (c *Client) do(req *http.Request) (*http.Response, error) {
blob - 75cbc2e9e9ee0b2dc54816cbccab615657ab52a7
blob + a268dee69097cbccbda0932108fc00149c0685ef
--- icinga.go
+++ icinga.go
+// package icinga provides a client to the Icinga2 HTTP API.
+//
+// A Client manages interaction with an Icinga2 server.
+// It is created using Dial:
+//
+// client, err := icinga.Dial("icinga.example.com:5665", "icinga", "secret", http.DefaultClient)
+// if err != nil {
+// // handle error
+// }
+// host, err := icinga.LookupHost("myserver.example.com")
+// if err != nil {
+// // handle error
+// }
+//
+// Since Client wraps http.Client, exported methods of http.Client such
+// as Get and PostForm can be used to implement any extra functionality
+// not provided by this package. For example:
+//
+// resp, err := client.PostForm("https://icinga.example.com:5665", data)
+// if err != nil {
+// // handle error
+// }
+//
package icinga
import (
"net/http"
)
+// A Client represents a client connection to the Icinga2 HTTP API.
+// It should be created using Dial.
+// Since Client wraps http.Client, standard methods such as Get and
+// PostForm can be used to implement any functionality not provided by
+// methods of Client.
type Client struct {
- host string
+ addr string
username string
password string
*http.Client
}
-func Dial(host, username, password string, client *http.Client) (*Client, error) {
- c := &Client{host, username, password, client}
+// Dial returns a new Client connected to the Icinga2 server at addr.
+// The recommended value for client is http.DefaultClient.
+// But it may also be a modified client which, for example,
+// skips TLS certificate verification.
+func Dial(addr, username, password string, client *http.Client) (*Client, error) {
+ c := &Client{addr, username, password, client}
if _, err := c.Status(); err != nil {
return nil, err
}
blob - 64068f9309c6e8d5c6a9010a7480b5947d86d288
blob + ed0753ef3181ebb0e5612b6d812b2bc7fc1d7441
--- user.go
+++ user.go
"net/http"
)
+// User represents a User object.
+// Note that this is different from an ApiUser.
type User struct {
Name string
Email string
return testUser, nil
}
+// CreateUser creates the User u identified by u.Name.
+// An error is returned if the User already exists or on any other error.
func (c *Client) CreateUser(u User) error {
buf := &bytes.Buffer{}
if err := json.NewEncoder(buf).Encode(u); err != nil {
return err
}
- resp, err := c.put("/objects/users/"+u.Name, buf)
- if err != nil {
+ if err := c.put("/objects/users/"+u.Name, buf); err != nil {
return fmt.Errorf("create %s: %w", u.Name, err)
}
- if resp.StatusCode == http.StatusOK {
- return nil
- }
- defer resp.Body.Close()
- var results results
- if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
- return fmt.Errorf("create %s: decode response: %w", u.Name, err)
- }
- return fmt.Errorf("create %s: %w", u.Name, results)
+ return nil
}
+// DeleteUser deletes the User identified by name.
+// ErrNoUser is returned if the User doesn't exist.
func (c *Client) DeleteUser(name string) error {
if err := c.delete("/objects/users/" + name); err != nil {
return fmt.Errorf("delete user %s: %w", name, err)