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) }