knotserver,hook: send messages from the knot to the hook, and print it to the git client #355

deleted
opened by ptr.pet targeting master from ptr.pet/core: push-options
Changed files
+59 -5
hook
knotserver
+14
hook/hook.go
···
import (
"bufio"
"context"
+
"encoding/json"
"fmt"
"net/http"
"os"
···
"github.com/urfave/cli/v3"
)
+
type HookResponse struct {
+
Messages []string `json:"messages"`
+
}
+
// The hook command is nested like so:
//
// knot hook --[flags] [hook]
···
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
}
+45 -5
knotserver/internal.go
···
"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"
···
}
type PushOptions struct {
-
skipCi bool
+
skipCi bool
+
verboseCi bool
}
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 {
···
// 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 {
···
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
}
···
return err
}
+
pipelineParseErrors := []string{}
+
var pipeline workflow.Pipeline
for _, e := range workflowDir {
if !e.IsFile {
···
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
}
···
},
}
-
// 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:\n", 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\n", warning.Path, warning.Type, warning.Reason))
+
}
+
}
+
if !hasDiagnostics {
+
*clientMsgs = append(*clientMsgs, fmt.Sprintf("pipeline compiled with no diagnostics\n"))
+
}
+
}
+
// do not run empty pipelines
if cp.Workflows == nil {
return nil