Files
spore-gateway/pkg/registry/registry.go
2025-10-22 19:57:48 +02:00

286 lines
8.1 KiB
Go

package registry
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"time"
log "github.com/sirupsen/logrus"
)
// RegistryClient represents a client for communicating with the SPORE registry
type RegistryClient struct {
BaseURL string
HTTPClient *http.Client
}
// NewRegistryClient creates a new registry API client
func NewRegistryClient(baseURL string) *RegistryClient {
return &RegistryClient{
BaseURL: baseURL,
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// FirmwareRecord represents a firmware record from the registry
type FirmwareRecord struct {
Name string `json:"name"`
Version string `json:"version"`
Size int64 `json:"size"`
Labels map[string]string `json:"labels"`
Path string `json:"download_url"`
}
// GroupedFirmware represents firmware grouped by name
type GroupedFirmware struct {
Name string `json:"name"`
Firmware []FirmwareRecord `json:"firmware"`
}
// FindFirmwareByNameAndVersion finds firmware in the registry by name and version
func (c *RegistryClient) FindFirmwareByNameAndVersion(name, version string) (*FirmwareRecord, error) {
// Get all firmware from registry
firmwareList, err := c.ListFirmware()
if err != nil {
return nil, fmt.Errorf("failed to list firmware: %w", err)
}
// Search through all firmware groups
for _, group := range firmwareList {
if group.Name == name {
for _, firmware := range group.Firmware {
if firmware.Version == version {
return &firmware, nil
}
}
}
}
return nil, fmt.Errorf("no firmware found with name %s and version %s", name, version)
}
// GetHealth checks the health of the registry
func (c *RegistryClient) GetHealth() (map[string]interface{}, error) {
url := fmt.Sprintf("%s/health", c.BaseURL)
resp, err := c.HTTPClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to get registry health: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("registry health check failed with status %d", resp.StatusCode)
}
var health map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&health); err != nil {
return nil, fmt.Errorf("failed to decode health response: %w", err)
}
return health, nil
}
// UploadFirmware uploads firmware to the registry
func (c *RegistryClient) UploadFirmware(metadata FirmwareMetadata, firmwareFile io.Reader) (map[string]interface{}, error) {
url := fmt.Sprintf("%s/firmware", c.BaseURL)
// Create multipart form data
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// Add metadata
metadataJSON, err := json.Marshal(metadata)
if err != nil {
return nil, fmt.Errorf("failed to marshal metadata: %w", err)
}
metadataPart, err := writer.CreateFormField("metadata")
if err != nil {
return nil, fmt.Errorf("failed to create metadata field: %w", err)
}
metadataPart.Write(metadataJSON)
// Add firmware file
firmwarePart, err := writer.CreateFormFile("firmware", fmt.Sprintf("%s-%s.bin", metadata.Name, metadata.Version))
if err != nil {
return nil, fmt.Errorf("failed to create firmware field: %w", err)
}
if _, err := io.Copy(firmwarePart, firmwareFile); err != nil {
return nil, fmt.Errorf("failed to copy firmware data: %w", err)
}
writer.Close()
req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to upload firmware: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("firmware upload failed with status %d: %s", resp.StatusCode, string(body))
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode upload response: %w", err)
}
return result, nil
}
// UpdateFirmwareMetadata updates firmware metadata in the registry
func (c *RegistryClient) UpdateFirmwareMetadata(name, version string, metadata FirmwareMetadata) (map[string]interface{}, error) {
url := fmt.Sprintf("%s/firmware/%s/%s", c.BaseURL, name, version)
metadataJSON, err := json.Marshal(metadata)
if err != nil {
return nil, fmt.Errorf("failed to marshal metadata: %w", err)
}
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(metadataJSON))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to update firmware metadata: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("firmware metadata update failed with status %d: %s", resp.StatusCode, string(body))
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode update response: %w", err)
}
return result, nil
}
// FirmwareMetadata represents firmware metadata for uploads
type FirmwareMetadata struct {
Name string `json:"name"`
Version string `json:"version"`
Labels map[string]string `json:"labels"`
}
// FindFirmwareByLabels finds firmware in the registry that matches the given labels
func (c *RegistryClient) FindFirmwareByLabels(labels map[string]string) (*FirmwareRecord, error) {
// Get all firmware from registry
firmwareList, err := c.ListFirmware()
if err != nil {
return nil, fmt.Errorf("failed to list firmware: %w", err)
}
// Search through all firmware groups
for _, group := range firmwareList {
for _, firmware := range group.Firmware {
if c.firmwareMatchesLabels(firmware.Labels, labels) {
return &firmware, nil
}
}
}
return nil, fmt.Errorf("no firmware found matching labels: %v", labels)
}
// firmwareMatchesLabels checks if firmware labels match the rollout labels
func (c *RegistryClient) firmwareMatchesLabels(firmwareLabels, rolloutLabels map[string]string) bool {
for key, value := range rolloutLabels {
if firmwareValue, exists := firmwareLabels[key]; !exists || firmwareValue != value {
return false
}
}
return true
}
// ListFirmware retrieves all firmware from the registry
func (c *RegistryClient) ListFirmware() ([]GroupedFirmware, error) {
url := fmt.Sprintf("%s/firmware", c.BaseURL)
resp, err := c.HTTPClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to get firmware list: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("firmware list request failed with status %d", resp.StatusCode)
}
var firmwareList []GroupedFirmware
if err := json.NewDecoder(resp.Body).Decode(&firmwareList); err != nil {
return nil, fmt.Errorf("failed to decode firmware list response: %w", err)
}
return firmwareList, nil
}
// DownloadFirmware downloads firmware binary from the registry
func (c *RegistryClient) DownloadFirmware(name, version string) ([]byte, error) {
url := fmt.Sprintf("%s/firmware/%s/%s", c.BaseURL, name, version)
resp, err := c.HTTPClient.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to download firmware: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("firmware download request failed with status %d", resp.StatusCode)
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read firmware data: %w", err)
}
log.WithFields(log.Fields{
"name": name,
"version": version,
"size": len(data),
}).Info("Downloaded firmware from registry")
return data, nil
}
// HealthCheck checks if the registry is healthy
func (c *RegistryClient) HealthCheck() error {
url := fmt.Sprintf("%s/health", c.BaseURL)
resp, err := c.HTTPClient.Get(url)
if err != nil {
return fmt.Errorf("failed to check registry health: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("registry health check failed with status %d", resp.StatusCode)
}
return nil
}