286 lines
8.1 KiB
Go
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
|
|
}
|