217 lines
5.9 KiB
Go
217 lines
5.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/orchard9/rdev/internal/domain"
|
|
"github.com/orchard9/rdev/pkg/api"
|
|
)
|
|
|
|
// PipelineErrorResponse is the JSON representation of a pipeline error.
|
|
type PipelineErrorResponse struct {
|
|
Type string `json:"type"`
|
|
Message string `json:"message"`
|
|
IsWarning bool `json:"is_warning"`
|
|
}
|
|
|
|
// PipelineResponse is the JSON representation of a CI pipeline.
|
|
type PipelineResponse struct {
|
|
ID int64 `json:"id"`
|
|
Number int64 `json:"number"`
|
|
Status string `json:"status"`
|
|
Event string `json:"event"`
|
|
Branch string `json:"branch"`
|
|
Commit string `json:"commit"`
|
|
Message string `json:"message"`
|
|
Author string `json:"author"`
|
|
Started string `json:"started,omitempty"`
|
|
Finished string `json:"finished,omitempty"`
|
|
Errors []PipelineErrorResponse `json:"errors,omitempty"`
|
|
}
|
|
|
|
// ListPipelines returns recent CI pipeline executions for a project.
|
|
// GET /projects/{id}/pipelines
|
|
func (h *InfrastructureHandler) ListPipelines(w http.ResponseWriter, r *http.Request) {
|
|
projectID := chi.URLParam(r, "id")
|
|
ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
|
|
defer cancel()
|
|
|
|
if err := validateProjectID(projectID); err != nil {
|
|
api.WriteBadRequest(w, r, err.Error())
|
|
return
|
|
}
|
|
|
|
if h.ciProvider == nil {
|
|
api.WriteInternalError(w, r, "CI provider not configured")
|
|
return
|
|
}
|
|
|
|
pipelines, err := h.ciProvider.ListPipelines(ctx, h.defaultGitOwner, projectID)
|
|
if err != nil {
|
|
api.WriteNotFound(w, r, fmt.Sprintf("pipelines not found: %v", err))
|
|
return
|
|
}
|
|
|
|
resp := make([]PipelineResponse, len(pipelines))
|
|
for i, p := range pipelines {
|
|
resp[i] = PipelineResponse{
|
|
ID: p.ID,
|
|
Number: p.Number,
|
|
Status: p.Status,
|
|
Event: p.Event,
|
|
Branch: p.Branch,
|
|
Commit: p.Commit,
|
|
Message: p.Message,
|
|
Author: p.Author,
|
|
Started: formatTime(p.Started),
|
|
Finished: formatTime(p.Finished),
|
|
Errors: mapPipelineErrors(p.Errors),
|
|
}
|
|
}
|
|
|
|
api.WriteSuccess(w, r, resp)
|
|
}
|
|
|
|
// GetPipeline returns a specific CI pipeline execution for a project.
|
|
// GET /projects/{id}/pipelines/{number}
|
|
func (h *InfrastructureHandler) GetPipeline(w http.ResponseWriter, r *http.Request) {
|
|
projectID := chi.URLParam(r, "id")
|
|
numberStr := chi.URLParam(r, "number")
|
|
ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
|
|
defer cancel()
|
|
|
|
if err := validateProjectID(projectID); err != nil {
|
|
api.WriteBadRequest(w, r, err.Error())
|
|
return
|
|
}
|
|
|
|
number, err := strconv.ParseInt(numberStr, 10, 64)
|
|
if err != nil {
|
|
api.WriteBadRequest(w, r, "invalid pipeline number")
|
|
return
|
|
}
|
|
|
|
if h.ciProvider == nil {
|
|
api.WriteInternalError(w, r, "CI provider not configured")
|
|
return
|
|
}
|
|
|
|
p, err := h.ciProvider.GetPipeline(ctx, h.defaultGitOwner, projectID, number)
|
|
if err != nil {
|
|
api.WriteNotFound(w, r, fmt.Sprintf("pipeline not found: %v", err))
|
|
return
|
|
}
|
|
|
|
api.WriteSuccess(w, r, PipelineResponse{
|
|
ID: p.ID,
|
|
Number: p.Number,
|
|
Status: p.Status,
|
|
Event: p.Event,
|
|
Branch: p.Branch,
|
|
Commit: p.Commit,
|
|
Message: p.Message,
|
|
Author: p.Author,
|
|
Started: formatTime(p.Started),
|
|
Finished: formatTime(p.Finished),
|
|
Errors: mapPipelineErrors(p.Errors),
|
|
})
|
|
}
|
|
|
|
// formatTime formats a time.Time as RFC3339, returning empty string for zero time.
|
|
func formatTime(t time.Time) string {
|
|
if t.IsZero() {
|
|
return ""
|
|
}
|
|
return t.Format(time.RFC3339)
|
|
}
|
|
|
|
// mapPipelineErrors converts domain pipeline errors to response format.
|
|
func mapPipelineErrors(errors []domain.CIPipelineError) []PipelineErrorResponse {
|
|
if len(errors) == 0 {
|
|
return nil
|
|
}
|
|
resp := make([]PipelineErrorResponse, len(errors))
|
|
for i, e := range errors {
|
|
resp[i] = PipelineErrorResponse{
|
|
Type: e.Type,
|
|
Message: e.Message,
|
|
IsWarning: e.IsWarning,
|
|
}
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// PipelineStepResponse is the JSON representation of a pipeline step.
|
|
type PipelineStepResponse struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
ExitCode *int `json:"exit_code,omitempty"`
|
|
Duration int `json:"duration_seconds"`
|
|
Error string `json:"error,omitempty"`
|
|
Log string `json:"log,omitempty"`
|
|
}
|
|
|
|
// PipelineStepsResponse is the JSON representation of pipeline steps.
|
|
type PipelineStepsResponse struct {
|
|
PipelineNumber int64 `json:"pipeline_number"`
|
|
URL string `json:"url"`
|
|
Steps []PipelineStepResponse `json:"steps"`
|
|
}
|
|
|
|
// GetPipelineSteps returns detailed step information for a pipeline.
|
|
// GET /projects/{id}/pipelines/{number}/steps
|
|
func (h *InfrastructureHandler) GetPipelineSteps(w http.ResponseWriter, r *http.Request) {
|
|
projectID := chi.URLParam(r, "id")
|
|
numberStr := chi.URLParam(r, "number")
|
|
ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
|
|
defer cancel()
|
|
|
|
if err := validateProjectID(projectID); err != nil {
|
|
api.WriteBadRequest(w, r, err.Error())
|
|
return
|
|
}
|
|
|
|
number, err := strconv.ParseInt(numberStr, 10, 64)
|
|
if err != nil {
|
|
api.WriteBadRequest(w, r, "invalid pipeline number")
|
|
return
|
|
}
|
|
|
|
if h.ciProvider == nil {
|
|
api.WriteInternalError(w, r, "CI provider not configured")
|
|
return
|
|
}
|
|
|
|
steps, err := h.ciProvider.GetPipelineSteps(ctx, h.defaultGitOwner, projectID, number)
|
|
if err != nil {
|
|
api.WriteNotFound(w, r, fmt.Sprintf("pipeline steps not found: %v", err))
|
|
return
|
|
}
|
|
|
|
// Map to response
|
|
respSteps := make([]PipelineStepResponse, len(steps.Steps))
|
|
for i, s := range steps.Steps {
|
|
respSteps[i] = PipelineStepResponse{
|
|
ID: s.ID,
|
|
Name: s.Name,
|
|
Status: s.Status,
|
|
ExitCode: s.ExitCode,
|
|
Duration: s.Duration,
|
|
Error: s.Error,
|
|
Log: s.Log,
|
|
}
|
|
}
|
|
|
|
api.WriteSuccess(w, r, PipelineStepsResponse{
|
|
PipelineNumber: steps.PipelineNumber,
|
|
URL: steps.URL,
|
|
Steps: respSteps,
|
|
})
|
|
}
|