package kea import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" ) // ControlClient talks to a kea-ctrl-agent REST endpoint. type ControlClient struct { HTTP *http.Client } // NewControlClient returns a ControlClient with a bounded timeout. func NewControlClient() *ControlClient { return &ControlClient{HTTP: &http.Client{Timeout: 5 * time.Second}} } type command struct { Command string `json:"command"` Service []string `json:"service,omitempty"` Arguments any `json:"arguments,omitempty"` } type response struct { Result int `json:"result"` Text string `json:"text"` } // ConfigReload asks the dhcp4 server behind the agent at baseURL to re-read its // config file from disk (the hot-reload path, analogous to rndc reconfig). func (c *ControlClient) ConfigReload(ctx context.Context, baseURL string) error { return c.send(ctx, baseURL, command{Command: "config-reload", Service: []string{"dhcp4"}}) } func (c *ControlClient) send(ctx context.Context, baseURL string, cmd command) error { body, err := json.Marshal(cmd) if err != nil { return err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := c.HTTP.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("kea control %q: http %d", cmd.Command, resp.StatusCode) } var results []response if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { return fmt.Errorf("decode kea control response: %w", err) } for _, r := range results { if r.Result != 0 { return fmt.Errorf("kea control %q failed: %s", cmd.Command, r.Text) } } return nil }