Add boot image management with ISO extraction and serving
Upload Proxmox ISOs via API or dashboard UI, extract kernel+initrd using pure-Go iso9660 library, store on disk, and serve over HTTP for PXE booting. Dynamic kernel/initrd filenames per image replace the previous hardcoded paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"provisioning/internal/image"
|
||||
)
|
||||
|
||||
type ImageAPI struct {
|
||||
Svc *image.Service
|
||||
}
|
||||
|
||||
func (a *ImageAPI) List(w http.ResponseWriter, r *http.Request) {
|
||||
images, err := a.Svc.Store.List(r.Context())
|
||||
if err != nil {
|
||||
writeJSONErr(w, http.StatusInternalServerError, "failed to list images")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, images)
|
||||
}
|
||||
|
||||
func (a *ImageAPI) Get(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idFromURL(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
img, err := a.Svc.Store.Get(r.Context(), id)
|
||||
if err != nil {
|
||||
writeJSONErr(w, http.StatusNotFound, "image not found")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, img)
|
||||
}
|
||||
|
||||
func (a *ImageAPI) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(0); err != nil {
|
||||
writeJSONErr(w, http.StatusBadRequest, "invalid multipart form")
|
||||
return
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(r.FormValue("name"))
|
||||
version := strings.TrimSpace(r.FormValue("version"))
|
||||
kind := strings.TrimSpace(r.FormValue("kind"))
|
||||
|
||||
file, _, err := r.FormFile("iso")
|
||||
if err != nil {
|
||||
writeJSONErr(w, http.StatusBadRequest, "iso file is required")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
img, err := a.Svc.Upload(r.Context(), image.UploadParams{
|
||||
Name: name,
|
||||
Kind: kind,
|
||||
Version: version,
|
||||
ISO: file,
|
||||
})
|
||||
if err != nil {
|
||||
status := http.StatusInternalServerError
|
||||
msg := err.Error()
|
||||
if strings.Contains(msg, "already exists") {
|
||||
status = http.StatusConflict
|
||||
} else if strings.Contains(msg, "invalid name") || strings.Contains(msg, "version is required") {
|
||||
status = http.StatusBadRequest
|
||||
}
|
||||
writeJSONErr(w, status, msg)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, img)
|
||||
}
|
||||
|
||||
func (a *ImageAPI) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idFromURL(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := a.Svc.Delete(r.Context(), id); err != nil {
|
||||
writeJSONErr(w, http.StatusNotFound, "image not found")
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (a *ImageAPI) SetDefault(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idFromURL(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := a.Svc.Store.SetDefault(r.Context(), id); err != nil {
|
||||
writeJSONErr(w, http.StatusInternalServerError, "failed to set default")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
||||
}
|
||||
Reference in New Issue
Block a user