added signed artifacts and SBOM generation capabilities
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/forgeo/forgebucket/internal/domain/sbom"
|
||||
)
|
||||
|
||||
type SBOMHandler struct {
|
||||
db *xorm.Engine
|
||||
generator *sbom.Generator
|
||||
}
|
||||
|
||||
func NewSBOMHandler(db *xorm.Engine, gen *sbom.Generator) *SBOMHandler {
|
||||
return &SBOMHandler{db: db, generator: gen}
|
||||
}
|
||||
|
||||
// GetForRun returns the SBOM report metadata for a pipeline run.
|
||||
// GET /api/v1/repos/{owner}/{repo}/runs/{runID}/sbom
|
||||
func (h *SBOMHandler) GetForRun(w http.ResponseWriter, r *http.Request) {
|
||||
runID, err := strconv.ParseInt(chi.URLParam(r, "runID"), 10, 64)
|
||||
if err != nil {
|
||||
jsonError(w, "invalid run ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
report, err := h.generator.GetForRun(runID)
|
||||
if err != nil {
|
||||
jsonError(w, "database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if report == nil {
|
||||
jsonError(w, "SBOM not yet generated for this run", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
jsonOK(w, report)
|
||||
}
|
||||
|
||||
// GetDocumentForRun streams the full CycloneDX JSON document for a run.
|
||||
// GET /api/v1/repos/{owner}/{repo}/runs/{runID}/sbom/document
|
||||
func (h *SBOMHandler) GetDocumentForRun(w http.ResponseWriter, r *http.Request) {
|
||||
runID, err := strconv.ParseInt(chi.URLParam(r, "runID"), 10, 64)
|
||||
if err != nil {
|
||||
jsonError(w, "invalid run ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
report, err := h.generator.GetForRun(runID)
|
||||
if err != nil {
|
||||
jsonError(w, "database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if report == nil {
|
||||
jsonError(w, "SBOM not yet generated for this run", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/vnd.cyclonedx+json")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="bom.json"`)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(report.BOMDocument)) //nolint:errcheck
|
||||
}
|
||||
|
||||
// GetLatest returns the most recent SBOM report metadata for a repo.
|
||||
// GET /api/v1/repos/{owner}/{repo}/sbom
|
||||
func (h *SBOMHandler) GetLatest(w http.ResponseWriter, r *http.Request) {
|
||||
repoID, ok := resolveRepoID(h.db, w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
report, err := h.generator.GetLatest(repoID)
|
||||
if err != nil {
|
||||
jsonError(w, "database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if report == nil {
|
||||
jsonError(w, "no SBOM generated yet — push a commit or trigger a pipeline run", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
jsonOK(w, report)
|
||||
}
|
||||
|
||||
// GetLatestDocument streams the latest CycloneDX JSON for a repo.
|
||||
// GET /api/v1/repos/{owner}/{repo}/sbom/document
|
||||
func (h *SBOMHandler) GetLatestDocument(w http.ResponseWriter, r *http.Request) {
|
||||
repoID, ok := resolveRepoID(h.db, w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
report, err := h.generator.GetLatest(repoID)
|
||||
if err != nil {
|
||||
jsonError(w, "database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if report == nil {
|
||||
jsonError(w, "no SBOM generated yet", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/vnd.cyclonedx+json")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="bom.json"`)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(report.BOMDocument)) //nolint:errcheck
|
||||
}
|
||||
|
||||
// Generate triggers on-demand SBOM generation for a repo at a given ref/SHA.
|
||||
// POST /api/v1/repos/{owner}/{repo}/sbom/generate?ref=<sha-or-branch>
|
||||
func (h *SBOMHandler) Generate(w http.ResponseWriter, r *http.Request) {
|
||||
repoID, ok := resolveRepoID(h.db, w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
sha := r.URL.Query().Get("ref")
|
||||
if sha == "" {
|
||||
sha = r.URL.Query().Get("sha")
|
||||
}
|
||||
if sha == "" {
|
||||
jsonError(w, "ref or sha query param required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
report, err := h.generator.GenerateOnDemand(repoID, sha)
|
||||
if err != nil {
|
||||
jsonError(w, "generation failed: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
jsonOK(w, report)
|
||||
}
|
||||
Reference in New Issue
Block a user