feat: Fleet Forge UEM - device management, agent, remote desktop (VNC), TLS reverse proxy
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user