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 [deleted fork]: push-options
Changed files
+59 -5
hook
knotserver
+14
hook/hook.go
··· 3 3 import ( 4 4 "bufio" 5 5 "context" 6 + "encoding/json" 6 7 "fmt" 7 8 "net/http" 8 9 "os" ··· 11 12 "github.com/urfave/cli/v3" 12 13 ) 13 14 15 + type HookResponse struct { 16 + Messages []string `json:"messages"` 17 + } 18 + 14 19 // The hook command is nested like so: 15 20 // 16 21 // knot hook --[flags] [hook] ··· 88 93 return fmt.Errorf("unexpected status code: %d", resp.StatusCode) 89 94 } 90 95 96 + var data HookResponse 97 + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 98 + return fmt.Errorf("failed to decode response: %w", err) 99 + } 100 + 101 + for _, message := range data.Messages { 102 + fmt.Println(message) 103 + } 104 + 91 105 return nil 92 106 }
+45 -5
knotserver/internal.go
··· 13 13 "github.com/go-chi/chi/v5" 14 14 "github.com/go-chi/chi/v5/middleware" 15 15 "tangled.sh/tangled.sh/core/api/tangled" 16 + "tangled.sh/tangled.sh/core/hook" 16 17 "tangled.sh/tangled.sh/core/knotserver/config" 17 18 "tangled.sh/tangled.sh/core/knotserver/db" 18 19 "tangled.sh/tangled.sh/core/knotserver/git" ··· 65 66 } 66 67 67 68 type PushOptions struct { 68 - skipCi bool 69 + skipCi bool 70 + verboseCi bool 69 71 } 70 72 71 73 func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) { ··· 101 103 if option == "skip-ci" || option == "ci-skip" { 102 104 pushOptions.skipCi = true 103 105 } 106 + if option == "verbose-ci" || option == "ci-verbose" { 107 + pushOptions.verboseCi = true 108 + } 109 + } 110 + 111 + resp := hook.HookResponse{ 112 + Messages: make([]string, 0), 104 113 } 105 114 106 115 for _, line := range lines { ··· 110 119 // non-fatal 111 120 } 112 121 113 - err = h.triggerPipeline(line, gitUserDid, repoDid, repoName, pushOptions) 122 + err = h.triggerPipeline(&resp.Messages, line, gitUserDid, repoDid, repoName, pushOptions) 114 123 if err != nil { 115 124 l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 116 125 // non-fatal 117 126 } 118 127 } 128 + 129 + writeJSON(w, resp) 119 130 } 120 131 121 132 func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error { ··· 161 172 return h.db.InsertEvent(event, h.n) 162 173 } 163 174 164 - func (h *InternalHandle) triggerPipeline(line git.PostReceiveLine, gitUserDid, repoDid, repoName string, pushOptions PushOptions) error { 175 + func (h *InternalHandle) triggerPipeline(clientMsgs *[]string, line git.PostReceiveLine, gitUserDid, repoDid, repoName string, pushOptions PushOptions) error { 165 176 if pushOptions.skipCi { 166 177 return nil 167 178 } ··· 186 197 return err 187 198 } 188 199 200 + pipelineParseErrors := []string{} 201 + 189 202 var pipeline workflow.Pipeline 190 203 for _, e := range workflowDir { 191 204 if !e.IsFile { ··· 200 213 201 214 wf, err := workflow.FromFile(e.Name, contents) 202 215 if err != nil { 203 - // TODO: log here, respond to client that is pushing 204 216 h.l.Error("failed to parse workflow", "err", err, "path", fpath) 217 + pipelineParseErrors = append(pipelineParseErrors, fmt.Sprintf("- at %s: %s\n", fpath, err)) 205 218 continue 206 219 } 207 220 ··· 226 239 }, 227 240 } 228 241 229 - // TODO: send the diagnostics back to the user here via stderr 230 242 cp := compiler.Compile(pipeline) 231 243 eventJson, err := json.Marshal(cp) 232 244 if err != nil { 233 245 return err 234 246 } 235 247 248 + if pushOptions.verboseCi { 249 + hasDiagnostics := false 250 + if len(pipelineParseErrors) > 0 { 251 + hasDiagnostics = true 252 + *clientMsgs = append(*clientMsgs, "error: failed to parse workflow(s):") 253 + for _, error := range pipelineParseErrors { 254 + *clientMsgs = append(*clientMsgs, error) 255 + } 256 + } 257 + if len(compiler.Diagnostics.Errors) > 0 { 258 + hasDiagnostics = true 259 + *clientMsgs = append(*clientMsgs, "error(s) on pipeline:") 260 + for _, error := range compiler.Diagnostics.Errors { 261 + *clientMsgs = append(*clientMsgs, fmt.Sprintf("- %s:\n", error)) 262 + } 263 + } 264 + if len(compiler.Diagnostics.Warnings) > 0 { 265 + hasDiagnostics = true 266 + *clientMsgs = append(*clientMsgs, "warning(s) on pipeline:") 267 + for _, warning := range compiler.Diagnostics.Warnings { 268 + *clientMsgs = append(*clientMsgs, fmt.Sprintf("- at %s: %s: %s\n", warning.Path, warning.Type, warning.Reason)) 269 + } 270 + } 271 + if !hasDiagnostics { 272 + *clientMsgs = append(*clientMsgs, fmt.Sprintf("pipeline compiled with no diagnostics\n")) 273 + } 274 + } 275 + 236 276 // do not run empty pipelines 237 277 if cp.Workflows == nil { 238 278 return nil