Files
fleetforge/agents/forge-agent/internal/client/client.go
T

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