Better support for Talos ISO downloads.
This commit is contained in:
@@ -61,28 +61,27 @@ func NewAPI(dataDir, appsDir string) (*API, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all API routes (Phase 1 + Phase 2)
|
||||
func (api *API) RegisterRoutes(r *mux.Router) {
|
||||
// Phase 1: Instance management
|
||||
// Instance management
|
||||
r.HandleFunc("/api/v1/instances", api.CreateInstance).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances", api.ListInstances).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}", api.GetInstance).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}", api.DeleteInstance).Methods("DELETE")
|
||||
|
||||
// Phase 1: Config management
|
||||
// Config management
|
||||
r.HandleFunc("/api/v1/instances/{name}/config", api.GetConfig).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/config", api.UpdateConfig).Methods("PUT")
|
||||
r.HandleFunc("/api/v1/instances/{name}/config", api.ConfigUpdateBatch).Methods("PATCH")
|
||||
|
||||
// Phase 1: Secrets management
|
||||
// Secrets management
|
||||
r.HandleFunc("/api/v1/instances/{name}/secrets", api.GetSecrets).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/secrets", api.UpdateSecrets).Methods("PUT")
|
||||
|
||||
// Phase 1: Context management
|
||||
// Context management
|
||||
r.HandleFunc("/api/v1/context", api.GetContext).Methods("GET")
|
||||
r.HandleFunc("/api/v1/context", api.SetContext).Methods("POST")
|
||||
|
||||
// Phase 2: Node management
|
||||
// Node management
|
||||
r.HandleFunc("/api/v1/instances/{name}/nodes/discover", api.NodeDiscover).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/nodes/detect", api.NodeDetect).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/discovery", api.NodeDiscoveryStatus).Methods("GET")
|
||||
@@ -95,19 +94,25 @@ func (api *API) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/api/v1/instances/{name}/nodes/{node}/apply", api.NodeApply).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/nodes/{node}", api.NodeDelete).Methods("DELETE")
|
||||
|
||||
// Phase 2: PXE asset management
|
||||
r.HandleFunc("/api/v1/instances/{name}/pxe/assets", api.PXEListAssets).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/pxe/assets/download", api.PXEDownloadAsset).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/pxe/assets/{type}", api.PXEGetAsset).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/pxe/assets/{type}", api.PXEDeleteAsset).Methods("DELETE")
|
||||
// Asset management
|
||||
r.HandleFunc("/api/v1/assets", api.AssetsListSchematics).Methods("GET")
|
||||
r.HandleFunc("/api/v1/assets/{schematicId}", api.AssetsGetSchematic).Methods("GET")
|
||||
r.HandleFunc("/api/v1/assets/{schematicId}/download", api.AssetsDownload).Methods("POST")
|
||||
r.HandleFunc("/api/v1/assets/{schematicId}/pxe/{assetType}", api.AssetsServePXE).Methods("GET")
|
||||
r.HandleFunc("/api/v1/assets/{schematicId}/status", api.AssetsGetStatus).Methods("GET")
|
||||
r.HandleFunc("/api/v1/assets/{schematicId}", api.AssetsDeleteSchematic).Methods("DELETE")
|
||||
|
||||
// Phase 2: Operations
|
||||
// Instance-schematic relationship
|
||||
r.HandleFunc("/api/v1/instances/{name}/schematic", api.SchematicGetInstanceSchematic).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/schematic", api.SchematicUpdateInstanceSchematic).Methods("PUT")
|
||||
|
||||
// Operations
|
||||
r.HandleFunc("/api/v1/instances/{name}/operations", api.OperationList).Methods("GET")
|
||||
r.HandleFunc("/api/v1/operations/{id}", api.OperationGet).Methods("GET")
|
||||
r.HandleFunc("/api/v1/operations/{id}/stream", api.OperationStream).Methods("GET")
|
||||
r.HandleFunc("/api/v1/operations/{id}/cancel", api.OperationCancel).Methods("POST")
|
||||
|
||||
// Phase 3: Cluster operations
|
||||
// Cluster operations
|
||||
r.HandleFunc("/api/v1/instances/{name}/cluster/config/generate", api.ClusterGenerateConfig).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/cluster/bootstrap", api.ClusterBootstrap).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/cluster/endpoints", api.ClusterConfigureEndpoints).Methods("POST")
|
||||
@@ -118,7 +123,7 @@ func (api *API) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/api/v1/instances/{name}/cluster/talosconfig", api.ClusterGetTalosconfig).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/cluster/reset", api.ClusterReset).Methods("POST")
|
||||
|
||||
// Phase 4: Services
|
||||
// Services
|
||||
r.HandleFunc("/api/v1/instances/{name}/services", api.ServicesList).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/services", api.ServicesInstall).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/services/install-all", api.ServicesInstallAll).Methods("POST")
|
||||
@@ -134,7 +139,7 @@ func (api *API) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/api/v1/instances/{name}/services/{service}/compile", api.ServicesCompile).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/services/{service}/deploy", api.ServicesDeploy).Methods("POST")
|
||||
|
||||
// Phase 4: Apps
|
||||
// Apps
|
||||
r.HandleFunc("/api/v1/apps", api.AppsListAvailable).Methods("GET")
|
||||
r.HandleFunc("/api/v1/apps/{app}", api.AppsGetAvailable).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/apps", api.AppsListDeployed).Methods("GET")
|
||||
@@ -143,12 +148,12 @@ func (api *API) RegisterRoutes(r *mux.Router) {
|
||||
r.HandleFunc("/api/v1/instances/{name}/apps/{app}", api.AppsDelete).Methods("DELETE")
|
||||
r.HandleFunc("/api/v1/instances/{name}/apps/{app}/status", api.AppsGetStatus).Methods("GET")
|
||||
|
||||
// Phase 5: Backup & Restore
|
||||
// Backup & Restore
|
||||
r.HandleFunc("/api/v1/instances/{name}/apps/{app}/backup", api.BackupAppStart).Methods("POST")
|
||||
r.HandleFunc("/api/v1/instances/{name}/apps/{app}/backup", api.BackupAppList).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/apps/{app}/restore", api.BackupAppRestore).Methods("POST")
|
||||
|
||||
// Phase 5: Utilities
|
||||
// Utilities
|
||||
r.HandleFunc("/api/v1/utilities/health", api.UtilitiesHealth).Methods("GET")
|
||||
r.HandleFunc("/api/v1/instances/{name}/utilities/health", api.InstanceUtilitiesHealth).Methods("GET")
|
||||
r.HandleFunc("/api/v1/utilities/dashboard/token", api.UtilitiesDashboardToken).Methods("GET")
|
||||
|
||||
172
internal/api/v1/handlers_assets.go
Normal file
172
internal/api/v1/handlers_assets.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/assets"
|
||||
)
|
||||
|
||||
// AssetsListSchematics lists all available schematics
|
||||
func (api *API) AssetsListSchematics(w http.ResponseWriter, r *http.Request) {
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
|
||||
schematics, err := assetsMgr.ListSchematics()
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list schematics: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"schematics": schematics,
|
||||
})
|
||||
}
|
||||
|
||||
// AssetsGetSchematic returns details for a specific schematic
|
||||
func (api *API) AssetsGetSchematic(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
schematicID := vars["schematicId"]
|
||||
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
|
||||
schematic, err := assetsMgr.GetSchematic(schematicID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Schematic not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, schematic)
|
||||
}
|
||||
|
||||
// AssetsDownload downloads assets for a schematic
|
||||
func (api *API) AssetsDownload(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
schematicID := vars["schematicId"]
|
||||
|
||||
// Parse request body
|
||||
var req struct {
|
||||
Version string `json:"version"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
AssetTypes []string `json:"asset_types,omitempty"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Version == "" {
|
||||
respondError(w, http.StatusBadRequest, "version is required")
|
||||
return
|
||||
}
|
||||
|
||||
// Default platform to amd64 if not specified
|
||||
if req.Platform == "" {
|
||||
req.Platform = "amd64"
|
||||
}
|
||||
|
||||
// Download assets
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
if err := assetsMgr.DownloadAssets(schematicID, req.Version, req.Platform, req.AssetTypes); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to download assets: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"message": "Assets downloaded successfully",
|
||||
"schematic_id": schematicID,
|
||||
"version": req.Version,
|
||||
"platform": req.Platform,
|
||||
})
|
||||
}
|
||||
|
||||
// AssetsServePXE serves a PXE asset file
|
||||
func (api *API) AssetsServePXE(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
schematicID := vars["schematicId"]
|
||||
assetType := vars["assetType"]
|
||||
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
|
||||
// Get asset path
|
||||
assetPath, err := assetsMgr.GetAssetPath(schematicID, assetType)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Asset not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Open file
|
||||
file, err := os.Open(assetPath)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to open asset: %v", err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Get file info for size
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stat asset: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set appropriate content type
|
||||
var contentType string
|
||||
switch assetType {
|
||||
case "kernel":
|
||||
contentType = "application/octet-stream"
|
||||
case "initramfs":
|
||||
contentType = "application/x-xz"
|
||||
case "iso":
|
||||
contentType = "application/x-iso9660-image"
|
||||
default:
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
// Set headers
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size()))
|
||||
// Set Content-Disposition to suggest filename for download
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", info.Name()))
|
||||
|
||||
// Serve file
|
||||
http.ServeContent(w, r, info.Name(), info.ModTime(), file)
|
||||
}
|
||||
|
||||
// AssetsGetStatus returns download status for a schematic
|
||||
func (api *API) AssetsGetStatus(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
schematicID := vars["schematicId"]
|
||||
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
|
||||
status, err := assetsMgr.GetAssetStatus(schematicID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Schematic not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, status)
|
||||
}
|
||||
|
||||
// AssetsDeleteSchematic deletes a schematic and all its assets
|
||||
func (api *API) AssetsDeleteSchematic(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
schematicID := vars["schematicId"]
|
||||
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
|
||||
if err := assetsMgr.DeleteSchematic(schematicID); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete schematic: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{
|
||||
"message": "Schematic deleted successfully",
|
||||
"schematic_id": schematicID,
|
||||
})
|
||||
}
|
||||
@@ -22,8 +22,9 @@ func (api *API) NodeDiscover(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
// Parse request body - support both subnet and ip_list formats
|
||||
var req struct {
|
||||
Subnet string `json:"subnet"`
|
||||
IPList []string `json:"ip_list"`
|
||||
}
|
||||
|
||||
@@ -32,14 +33,21 @@ func (api *API) NodeDiscover(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.IPList) == 0 {
|
||||
respondError(w, http.StatusBadRequest, "ip_list is required")
|
||||
// If subnet provided, use it as a single "IP" for discovery
|
||||
// The discovery manager will scan this subnet
|
||||
var ipList []string
|
||||
if req.Subnet != "" {
|
||||
ipList = []string{req.Subnet}
|
||||
} else if len(req.IPList) > 0 {
|
||||
ipList = req.IPList
|
||||
} else {
|
||||
respondError(w, http.StatusBadRequest, "subnet or ip_list is required")
|
||||
return
|
||||
}
|
||||
|
||||
// Start discovery
|
||||
discoveryMgr := discovery.NewManager(api.dataDir, instanceName)
|
||||
if err := discoveryMgr.StartDiscovery(instanceName, req.IPList); err != nil {
|
||||
if err := discoveryMgr.StartDiscovery(instanceName, ipList); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start discovery: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,42 +3,72 @@ package v1
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/assets"
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/pxe"
|
||||
)
|
||||
|
||||
// PXEListAssets lists all PXE assets for an instance
|
||||
// DEPRECATED: This endpoint is deprecated. Use GET /api/v1/assets/{schematicId} instead.
|
||||
func (api *API) PXEListAssets(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
|
||||
// Add deprecation warning header
|
||||
w.Header().Set("X-Deprecated", "This endpoint is deprecated. Use GET /api/v1/assets/{schematicId} instead.")
|
||||
log.Printf("Warning: Deprecated endpoint /api/v1/instances/%s/pxe/assets called", instanceName)
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// List assets
|
||||
pxeMgr := pxe.NewManager(api.dataDir)
|
||||
assets, err := pxeMgr.ListAssets(instanceName)
|
||||
// Get schematic ID from instance config
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
schematicID, err := api.config.GetConfigValue(configPath, "cluster.nodes.talos.schematicId")
|
||||
if err != nil || schematicID == "" || schematicID == "null" {
|
||||
// Fall back to old PXE manager if no schematic configured
|
||||
pxeMgr := pxe.NewManager(api.dataDir)
|
||||
assets, err := pxeMgr.ListAssets(instanceName)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list assets: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"assets": assets,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Proxy to new asset system
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
schematic, err := assetsMgr.GetSchematic(schematicID)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list assets: %v", err))
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Schematic not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"assets": assets,
|
||||
"assets": schematic.Assets,
|
||||
})
|
||||
}
|
||||
|
||||
// PXEDownloadAsset downloads a PXE asset
|
||||
// DEPRECATED: This endpoint is deprecated. Use POST /api/v1/assets/{schematicId}/download instead.
|
||||
func (api *API) PXEDownloadAsset(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
|
||||
// Add deprecation warning header
|
||||
w.Header().Set("X-Deprecated", "This endpoint is deprecated. Use POST /api/v1/assets/{schematicId}/download instead.")
|
||||
log.Printf("Warning: Deprecated endpoint /api/v1/instances/%s/pxe/assets/download called", instanceName)
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
@@ -62,80 +92,154 @@ func (api *API) PXEDownloadAsset(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.URL == "" {
|
||||
respondError(w, http.StatusBadRequest, "url is required")
|
||||
// Get schematic ID from instance config
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
schematicID, err := api.config.GetConfigValue(configPath, "cluster.nodes.talos.schematicId")
|
||||
|
||||
// If no schematic configured or URL provided (old behavior), fall back to old PXE manager
|
||||
if (err != nil || schematicID == "" || schematicID == "null") || req.URL != "" {
|
||||
if req.URL == "" {
|
||||
respondError(w, http.StatusBadRequest, "url is required when schematic is not configured")
|
||||
return
|
||||
}
|
||||
|
||||
// Download asset using old PXE manager
|
||||
pxeMgr := pxe.NewManager(api.dataDir)
|
||||
if err := pxeMgr.DownloadAsset(instanceName, req.AssetType, req.Version, req.URL); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to download asset: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{
|
||||
"message": "Asset downloaded successfully",
|
||||
"asset_type": req.AssetType,
|
||||
"version": req.Version,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Download asset
|
||||
pxeMgr := pxe.NewManager(api.dataDir)
|
||||
if err := pxeMgr.DownloadAsset(instanceName, req.AssetType, req.Version, req.URL); err != nil {
|
||||
// Proxy to new asset system
|
||||
if req.Version == "" {
|
||||
respondError(w, http.StatusBadRequest, "version is required")
|
||||
return
|
||||
}
|
||||
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
assetTypes := []string{req.AssetType}
|
||||
platform := "amd64" // Default platform for backward compatibility
|
||||
if err := assetsMgr.DownloadAssets(schematicID, req.Version, platform, assetTypes); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to download asset: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{
|
||||
"message": "Asset downloaded successfully",
|
||||
"asset_type": req.AssetType,
|
||||
"version": req.Version,
|
||||
"message": "Asset downloaded successfully",
|
||||
"asset_type": req.AssetType,
|
||||
"version": req.Version,
|
||||
"schematic_id": schematicID,
|
||||
})
|
||||
}
|
||||
|
||||
// PXEGetAsset returns information about a specific asset
|
||||
// DEPRECATED: This endpoint is deprecated. Use GET /api/v1/assets/{schematicId}/pxe/{assetType} instead.
|
||||
func (api *API) PXEGetAsset(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
assetType := vars["type"]
|
||||
|
||||
// Add deprecation warning header
|
||||
w.Header().Set("X-Deprecated", "This endpoint is deprecated. Use GET /api/v1/assets/{schematicId}/pxe/{assetType} instead.")
|
||||
log.Printf("Warning: Deprecated endpoint /api/v1/instances/%s/pxe/assets/%s called", instanceName, assetType)
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Get asset path
|
||||
pxeMgr := pxe.NewManager(api.dataDir)
|
||||
assetPath, err := pxeMgr.GetAssetPath(instanceName, assetType)
|
||||
// Get schematic ID from instance config
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
schematicID, err := api.config.GetConfigValue(configPath, "cluster.nodes.talos.schematicId")
|
||||
if err != nil || schematicID == "" || schematicID == "null" {
|
||||
// Fall back to old PXE manager if no schematic configured
|
||||
pxeMgr := pxe.NewManager(api.dataDir)
|
||||
assetPath, err := pxeMgr.GetAssetPath(instanceName, assetType)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Asset not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify asset
|
||||
valid, err := pxeMgr.VerifyAsset(instanceName, assetType)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to verify asset: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"type": assetType,
|
||||
"path": assetPath,
|
||||
"valid": valid,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Proxy to new asset system - serve the file directly
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
assetPath, err := assetsMgr.GetAssetPath(schematicID, assetType)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Asset not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify asset
|
||||
valid, err := pxeMgr.VerifyAsset(instanceName, assetType)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to verify asset: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"type": assetType,
|
||||
"path": assetPath,
|
||||
"valid": valid,
|
||||
"type": assetType,
|
||||
"path": assetPath,
|
||||
"valid": true,
|
||||
"schematic_id": schematicID,
|
||||
})
|
||||
}
|
||||
|
||||
// PXEDeleteAsset deletes a PXE asset
|
||||
// DEPRECATED: This endpoint is deprecated. Use DELETE /api/v1/assets/{schematicId} instead.
|
||||
func (api *API) PXEDeleteAsset(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
assetType := vars["type"]
|
||||
|
||||
// Add deprecation warning header
|
||||
w.Header().Set("X-Deprecated", "This endpoint is deprecated. Use DELETE /api/v1/assets/{schematicId} instead.")
|
||||
log.Printf("Warning: Deprecated endpoint DELETE /api/v1/instances/%s/pxe/assets/%s called", instanceName, assetType)
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete asset
|
||||
pxeMgr := pxe.NewManager(api.dataDir)
|
||||
if err := pxeMgr.DeleteAsset(instanceName, assetType); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete asset: %v", err))
|
||||
// Get schematic ID from instance config
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
schematicID, err := api.config.GetConfigValue(configPath, "cluster.nodes.talos.schematicId")
|
||||
if err != nil || schematicID == "" || schematicID == "null" {
|
||||
// Fall back to old PXE manager if no schematic configured
|
||||
pxeMgr := pxe.NewManager(api.dataDir)
|
||||
if err := pxeMgr.DeleteAsset(instanceName, assetType); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete asset: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]string{
|
||||
"message": "Asset deleted successfully",
|
||||
"type": assetType,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Note: In the new system, we don't delete individual assets, only entire schematics
|
||||
// For backward compatibility, we'll just report success without doing anything
|
||||
respondJSON(w, http.StatusOK, map[string]string{
|
||||
"message": "Asset deleted successfully",
|
||||
"type": assetType,
|
||||
"message": "Individual asset deletion not supported in schematic mode. Use DELETE /api/v1/assets/{schematicId} to delete all assets.",
|
||||
"type": assetType,
|
||||
"schematic_id": schematicID,
|
||||
})
|
||||
}
|
||||
|
||||
122
internal/api/v1/handlers_schematic.go
Normal file
122
internal/api/v1/handlers_schematic.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/wild-cloud/wild-central/daemon/internal/assets"
|
||||
)
|
||||
|
||||
// SchematicGetInstanceSchematic returns the schematic configuration for an instance
|
||||
func (api *API) SchematicGetInstanceSchematic(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Get schematic ID from config
|
||||
schematicID, err := api.config.GetConfigValue(configPath, "cluster.nodes.talos.schematicId")
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get schematic ID: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Get version from config
|
||||
version, err := api.config.GetConfigValue(configPath, "cluster.nodes.talos.version")
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get version: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// If schematic is configured, get asset status
|
||||
var assetStatus interface{}
|
||||
if schematicID != "" && schematicID != "null" {
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
status, err := assetsMgr.GetAssetStatus(schematicID)
|
||||
if err == nil {
|
||||
assetStatus = status
|
||||
}
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"schematic_id": schematicID,
|
||||
"version": version,
|
||||
"assets": assetStatus,
|
||||
})
|
||||
}
|
||||
|
||||
// SchematicUpdateInstanceSchematic updates the schematic configuration for an instance
|
||||
func (api *API) SchematicUpdateInstanceSchematic(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
instanceName := vars["name"]
|
||||
|
||||
// Validate instance exists
|
||||
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
var req struct {
|
||||
SchematicID string `json:"schematic_id"`
|
||||
Version string `json:"version"`
|
||||
Download bool `json:"download,omitempty"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.SchematicID == "" {
|
||||
respondError(w, http.StatusBadRequest, "schematic_id is required")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Version == "" {
|
||||
respondError(w, http.StatusBadRequest, "version is required")
|
||||
return
|
||||
}
|
||||
|
||||
configPath := api.instance.GetInstanceConfigPath(instanceName)
|
||||
|
||||
// Update schematic ID in config
|
||||
if err := api.config.SetConfigValue(configPath, "cluster.nodes.talos.schematicId", req.SchematicID); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to set schematic ID: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Update version in config
|
||||
if err := api.config.SetConfigValue(configPath, "cluster.nodes.talos.version", req.Version); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to set version: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"message": "Schematic configuration updated successfully",
|
||||
"schematic_id": req.SchematicID,
|
||||
"version": req.Version,
|
||||
}
|
||||
|
||||
// Optionally download assets
|
||||
if req.Download {
|
||||
assetsMgr := assets.NewManager(api.dataDir)
|
||||
platform := "amd64" // Default platform
|
||||
if err := assetsMgr.DownloadAssets(req.SchematicID, req.Version, platform, nil); err != nil {
|
||||
response["download_warning"] = fmt.Sprintf("Failed to download assets: %v", err)
|
||||
} else {
|
||||
response["download_status"] = "Assets downloaded successfully"
|
||||
}
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, response)
|
||||
}
|
||||
Reference in New Issue
Block a user