From 6438a9ecd5e41e22b0fa186e85c35e5a8b2a926b Mon Sep 17 00:00:00 2001 From: dusk Date: Mon, 28 Jul 2025 18:24:53 +0300 Subject: [PATCH] knotserver,hook: print messages to the git client, implement verbose-ci push option Change-Id: xmwwpzwkwqklwrqvpsutrmpovzzzmupr Signed-off-by: dusk --- hook/hook.go | 14 ++++++++++++ knotserver/internal.go | 50 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/hook/hook.go b/hook/hook.go index 3383671..c53fdfb 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -3,6 +3,7 @@ package hook import ( "bufio" "context" + "encoding/json" "fmt" "net/http" "os" @@ -11,6 +12,10 @@ import ( "github.com/urfave/cli/v3" ) +type HookResponse struct { + Messages []string `json:"messages"` +} + // The hook command is nested like so: // // knot hook --[flags] [hook] @@ -88,5 +93,14 @@ func postRecieve(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("unexpected status code: %d", resp.StatusCode) } + var data HookResponse + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return fmt.Errorf("failed to decode response: %w", err) + } + + for _, message := range data.Messages { + fmt.Println(message) + } + return nil } diff --git a/knotserver/internal.go b/knotserver/internal.go index 143a517..45bfac1 100644 --- a/knotserver/internal.go +++ b/knotserver/internal.go @@ -13,6 +13,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "tangled.sh/tangled.sh/core/api/tangled" + "tangled.sh/tangled.sh/core/hook" "tangled.sh/tangled.sh/core/knotserver/config" "tangled.sh/tangled.sh/core/knotserver/db" "tangled.sh/tangled.sh/core/knotserver/git" @@ -65,7 +66,8 @@ func (h *InternalHandle) InternalKeys(w http.ResponseWriter, r *http.Request) { } type PushOptions struct { - skipCi bool + skipCi bool + verboseCi bool } func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) { @@ -101,6 +103,13 @@ func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) if option == "skip-ci" || option == "ci-skip" { pushOptions.skipCi = true } + if option == "verbose-ci" || option == "ci-verbose" { + pushOptions.verboseCi = true + } + } + + resp := hook.HookResponse{ + Messages: make([]string, 0), } for _, line := range lines { @@ -110,12 +119,14 @@ func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) // non-fatal } - err = h.triggerPipeline(line, gitUserDid, repoDid, repoName, pushOptions) + err = h.triggerPipeline(&resp.Messages, line, gitUserDid, repoDid, repoName, pushOptions) if err != nil { l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) // non-fatal } } + + writeJSON(w, resp) } func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error { @@ -161,7 +172,7 @@ func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, r return h.db.InsertEvent(event, h.n) } -func (h *InternalHandle) triggerPipeline(line git.PostReceiveLine, gitUserDid, repoDid, repoName string, pushOptions PushOptions) error { +func (h *InternalHandle) triggerPipeline(clientMsgs *[]string, line git.PostReceiveLine, gitUserDid, repoDid, repoName string, pushOptions PushOptions) error { if pushOptions.skipCi { return nil } @@ -186,6 +197,8 @@ func (h *InternalHandle) triggerPipeline(line git.PostReceiveLine, gitUserDid, r return err } + pipelineParseErrors := []string{} + var pipeline workflow.Pipeline for _, e := range workflowDir { if !e.IsFile { @@ -200,8 +213,8 @@ func (h *InternalHandle) triggerPipeline(line git.PostReceiveLine, gitUserDid, r wf, err := workflow.FromFile(e.Name, contents) if err != nil { - // TODO: log here, respond to client that is pushing h.l.Error("failed to parse workflow", "err", err, "path", fpath) + pipelineParseErrors = append(pipelineParseErrors, fmt.Sprintf("- at %s: %s\n", fpath, err)) continue } @@ -226,13 +239,40 @@ func (h *InternalHandle) triggerPipeline(line git.PostReceiveLine, gitUserDid, r }, } - // TODO: send the diagnostics back to the user here via stderr cp := compiler.Compile(pipeline) eventJson, err := json.Marshal(cp) if err != nil { return err } + if pushOptions.verboseCi { + hasDiagnostics := false + if len(pipelineParseErrors) > 0 { + hasDiagnostics = true + *clientMsgs = append(*clientMsgs, "error: failed to parse workflow(s):") + for _, error := range pipelineParseErrors { + *clientMsgs = append(*clientMsgs, error) + } + } + if len(compiler.Diagnostics.Errors) > 0 { + hasDiagnostics = true + *clientMsgs = append(*clientMsgs, "error(s) on pipeline:") + for _, error := range compiler.Diagnostics.Errors { + *clientMsgs = append(*clientMsgs, fmt.Sprintf("- %s:", error)) + } + } + if len(compiler.Diagnostics.Warnings) > 0 { + hasDiagnostics = true + *clientMsgs = append(*clientMsgs, "warning(s) on pipeline:") + for _, warning := range compiler.Diagnostics.Warnings { + *clientMsgs = append(*clientMsgs, fmt.Sprintf("- at %s: %s: %s", warning.Path, warning.Type, warning.Reason)) + } + } + if !hasDiagnostics { + *clientMsgs = append(*clientMsgs, fmt.Sprintf("success: pipeline compiled with no diagnostics\n")) + } + } + // do not run empty pipelines if cp.Workflows == nil { return nil -- 2.43.0