package httpserver import ( "encoding/json" "fmt" "net/http" "strings" "time" "github.com/toki/oto/services/core/internal/cicdstate" otopb "github.com/toki/oto/services/core/oto" ) func jobToJSON(j *cicdstate.Job) map[string]interface{} { out := map[string]interface{}{ "id": j.ID, "name": j.Name, "state": j.State, "created_at": j.CreatedAt.Format(time.RFC3339), "updated_at": j.UpdatedAt.Format(time.RFC3339), "execution_id": j.ExecutionID, } if j.RunInput != nil { out["run_request"] = runInputToJSON(j.RunInput) } return out } // validateRunInput enforces the remote claim contract: exactly one of // pipeline_yaml or pipeline_yaml_path must be provided. func validateRunInput(in *cicdstate.RunInput) error { hasYAML := strings.TrimSpace(in.PipelineYAML) != "" hasPath := strings.TrimSpace(in.PipelineYAMLPath) != "" if hasYAML == hasPath { return fmt.Errorf("run request must set exactly one of pipeline_yaml or pipeline_yaml_path") } return nil } // runRequestFromJSON converts an incoming otopb.RunRequest decoded from the job // create body into a store-level RunInput. It returns nil when no run request // was supplied, keeping job creation backward compatible. func runRequestFromJSON(rr *otopb.RunRequest) *cicdstate.RunInput { if rr == nil { return nil } in := &cicdstate.RunInput{ PipelineYAMLPath: rr.GetPipelineYamlPath(), PipelineYAML: rr.GetPipelineYaml(), CommandTypes: append([]string(nil), rr.GetCommandTypes()...), } if len(rr.GetVariables()) > 0 { in.Variables = make(map[string]string, len(rr.GetVariables())) for k, v := range rr.GetVariables() { in.Variables[k] = v } } return in } // runRequestToProto builds the claim response RunRequest from the stored input, // filling in the runner/job/execution identifiers resolved at claim time. func runRequestToProto(in *cicdstate.RunInput, runnerID, jobID, execID string) *otopb.RunRequest { if in == nil { return nil } rr := &otopb.RunRequest{ RunnerId: runnerID, JobId: jobID, ExecutionId: execID, PipelineYamlPath: in.PipelineYAMLPath, PipelineYaml: in.PipelineYAML, CommandTypes: append([]string(nil), in.CommandTypes...), } if len(in.Variables) > 0 { rr.Variables = make(map[string]string, len(in.Variables)) for k, v := range in.Variables { rr.Variables[k] = v } } return rr } func runInputToJSON(in *cicdstate.RunInput) map[string]interface{} { return map[string]interface{}{ "pipeline_yaml_path": in.PipelineYAMLPath, "pipeline_yaml": in.PipelineYAML, "variables": in.Variables, "command_types": in.CommandTypes, } } func execToJSON(e *cicdstate.Execution) map[string]interface{} { return map[string]interface{}{ "id": e.ID, "job_id": e.JobID, "state": e.State, "created_at": e.CreatedAt.Format(time.RFC3339), "updated_at": e.UpdatedAt.Format(time.RFC3339), "execution_id": e.ID, } } func logsToJSON(logs []cicdstate.LogEntry) map[string]interface{} { return map[string]interface{}{ "logs": logs, } } func artifactsToJSON(artifacts []cicdstate.ArtifactEntry) map[string]interface{} { return map[string]interface{}{ "artifacts": artifacts, } } func writeResponse(w http.ResponseWriter, status int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(data) }