231 lines
6.5 KiB
Go
231 lines
6.5 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
type Client struct {
|
|
serverURL string
|
|
credential string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func New(serverURL, credential string) *Client {
|
|
return &Client{
|
|
serverURL: serverURL,
|
|
credential: credential,
|
|
httpClient: &http.Client{},
|
|
}
|
|
}
|
|
|
|
func (c *Client) doPost(path string, body interface{}, out interface{}) error {
|
|
data, err := json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", c.serverURL+path, bytes.NewReader(data))
|
|
if err != nil {
|
|
return fmt.Errorf("create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if c.credential != "" {
|
|
req.Header.Set("Authorization", "Bearer "+c.credential)
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("execute request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode >= 400 {
|
|
return fmt.Errorf("server returned %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
if out != nil {
|
|
var apiResp struct {
|
|
Success bool `json:"success"`
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(respBody, &apiResp); err != nil {
|
|
return fmt.Errorf("decode response: %w", err)
|
|
}
|
|
if !apiResp.Success {
|
|
return fmt.Errorf("api returned success=false")
|
|
}
|
|
if err := json.Unmarshal(apiResp.Data, out); err != nil {
|
|
return fmt.Errorf("decode data: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) doGet(path string, out interface{}) error {
|
|
req, err := http.NewRequest("GET", c.serverURL+path, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("create request: %w", err)
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+c.credential)
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("execute request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode >= 400 {
|
|
return fmt.Errorf("server returned %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
if out != nil {
|
|
var apiResp struct {
|
|
Success bool `json:"success"`
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(respBody, &apiResp); err != nil {
|
|
return fmt.Errorf("decode response: %w", err)
|
|
}
|
|
if err := json.Unmarshal(apiResp.Data, out); err != nil {
|
|
return fmt.Errorf("decode data: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type EnrollRequest struct {
|
|
Token string `json:"token"`
|
|
Hostname string `json:"hostname"`
|
|
Platform string `json:"platform"`
|
|
OSName string `json:"os_name"`
|
|
OSVersion string `json:"os_version"`
|
|
Architecture string `json:"architecture"`
|
|
KernelVersion string `json:"kernel_version,omitempty"`
|
|
CPUModel string `json:"cpu_model,omitempty"`
|
|
CPUCores int `json:"cpu_cores,omitempty"`
|
|
TotalMemoryBytes uint64 `json:"total_memory_bytes,omitempty"`
|
|
TotalDiskBytes uint64 `json:"total_disk_bytes,omitempty"`
|
|
IPAddress string `json:"ip_address,omitempty"`
|
|
AgentVersion string `json:"agent_version"`
|
|
}
|
|
|
|
type EnrollResponse struct {
|
|
DeviceID string `json:"device_id"`
|
|
Credential string `json:"credential"`
|
|
}
|
|
|
|
func (c *Client) Enroll(req EnrollRequest) (*EnrollResponse, error) {
|
|
var resp EnrollResponse
|
|
if err := c.doPost("/api/v1/agent/enroll", &req, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
type MetricsData struct {
|
|
CPUPercent float64 `json:"cpu_usage_percent"`
|
|
MemoryPercent float64 `json:"memory_usage_percent"`
|
|
MemoryUsed uint64 `json:"memory_used_bytes"`
|
|
MemoryTotal uint64 `json:"memory_total_bytes"`
|
|
DiskPercent float64 `json:"disk_usage_percent"`
|
|
DiskUsed uint64 `json:"disk_used_bytes"`
|
|
DiskTotal uint64 `json:"disk_total_bytes"`
|
|
Load1 float64 `json:"load_average_1m"`
|
|
Load5 float64 `json:"load_average_5m"`
|
|
Load15 float64 `json:"load_average_15m"`
|
|
UptimeSeconds uint64 `json:"uptime_seconds"`
|
|
}
|
|
|
|
type HeartbeatRequest struct {
|
|
Hostname string `json:"hostname"`
|
|
OSVersion string `json:"os_version"`
|
|
KernelVersion string `json:"kernel_version"`
|
|
AgentVersion string `json:"agent_version"`
|
|
IPAddress string `json:"ip_address"`
|
|
Metrics *MetricsData `json:"metrics,omitempty"`
|
|
Inventory *InventoryData `json:"inventory,omitempty"`
|
|
}
|
|
|
|
type InventoryData struct {
|
|
Packages []PackageData `json:"packages,omitempty"`
|
|
Services []ServiceData `json:"services,omitempty"`
|
|
NetworkInterfaces []NetworkInterfaceData `json:"network_interfaces,omitempty"`
|
|
}
|
|
|
|
type PackageData struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Manager string `json:"manager"`
|
|
}
|
|
|
|
type ServiceData struct {
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
type NetworkInterfaceData struct {
|
|
Name string `json:"name"`
|
|
Addresses []string `json:"addresses"`
|
|
MacAddress string `json:"mac_address,omitempty"`
|
|
}
|
|
|
|
func (c *Client) Heartbeat(req HeartbeatRequest) error {
|
|
return c.doPost("/api/v1/agent/heartbeat", &req, nil)
|
|
}
|
|
|
|
type JobInfo struct {
|
|
JobID string `json:"jobId"`
|
|
TargetID string `json:"targetId"`
|
|
JobType string `json:"jobType"`
|
|
Parameters map[string]interface{} `json:"parameters"`
|
|
}
|
|
|
|
func (c *Client) FetchJobs() ([]JobInfo, error) {
|
|
var jobs []JobInfo
|
|
if err := c.doGet("/api/v1/agent/jobs", &jobs); err != nil {
|
|
return nil, err
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
type JobResult struct {
|
|
Status string `json:"status"`
|
|
TargetID string `json:"target_id"`
|
|
Result map[string]interface{} `json:"result,omitempty"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
func (c *Client) ReportStart(jobID, targetID string) error {
|
|
path := fmt.Sprintf("/api/v1/agent/jobs/%s/start", jobID)
|
|
body := map[string]string{"target_id": targetID}
|
|
return c.doPost(path, &body, nil)
|
|
}
|
|
|
|
func (c *Client) IsJobCancelled(jobID string) bool {
|
|
var status struct {
|
|
JobStatus string `json:"job_status"`
|
|
TargetStatus string `json:"target_status"`
|
|
}
|
|
path := fmt.Sprintf("/api/v1/agent/jobs/%s/status", jobID)
|
|
if err := c.doGet(path, &status); err != nil {
|
|
return false
|
|
}
|
|
return status.JobStatus == "cancelled" || status.TargetStatus == "cancelled"
|
|
}
|
|
|
|
func (c *Client) SubmitResult(jobID string, result JobResult) error {
|
|
path := fmt.Sprintf("/api/v1/agent/jobs/%s/result", jobID)
|
|
return c.doPost(path, &result, nil)
|
|
}
|