commit 5055878019b72a9ed3408e337a21e868c3842cf5 from: Oliver Lowe date: Thu Jan 6 12:53:21 2022 UTC more docs commit - 6e415953de07ac11e0f54ac397d156b23f384a15 commit + 5055878019b72a9ed3408e337a21e868c3842cf5 blob - 89a3a5d5f479418d18c6e8b14503b9e41263a13a blob + bdf22d2e28488f143a1bac4f1483ab50641bc261 --- host.go +++ host.go @@ -1,20 +1,78 @@ 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 { @@ -26,6 +84,21 @@ func (c *Client) LookupHost(name string) (Host, error) 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 @@ -1,6 +1,8 @@ package icinga import ( + "encoding/json" + "errors" "fmt" "io" "net/http" @@ -14,7 +16,7 @@ type results struct { } type result struct { - Attrs interface{} + Attrs map[string]interface{} Code int Errors []string Name string @@ -23,18 +25,23 @@ type result struct { 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) @@ -71,18 +78,30 @@ func (c *Client) post(path string, body io.Reader) (*h 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 { @@ -98,7 +117,7 @@ func (c *Client) delete(path string) error { 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 @@ -1,3 +1,26 @@ +// 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 ( @@ -5,15 +28,24 @@ 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 @@ -8,6 +8,8 @@ import ( "net/http" ) +// User represents a User object. +// Note that this is different from an ApiUser. type User struct { Name string Email string @@ -47,26 +49,21 @@ func (c *Client) LookupUser(name string) (User, error) 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)