forked from tangled.org/core
Monorepo for Tangled

knotserver,hook: print messages to the git client, implement verbose-ci push option

Signed-off-by: dusk <y.bera003.06@protonmail.com>

authored by ptr.pet and committed by Tangled c21b68ec 9b88f855

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" ··· 10 11 11 12 "github.com/urfave/cli/v3" 12 13 ) 14 + 15 + type HookResponse struct { 16 + Messages []string `json:"messages"` 17 + } 13 18 14 19 // The hook command is nested like so: 15 20 // ··· 86 91 87 92 if resp.StatusCode != http.StatusOK { 88 93 return fmt.Errorf("unexpected status code: %d", resp.StatusCode) 94 + } 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) 89 103 } 90 104 91 105 return nil
+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) { ··· 100 102 for _, option := range pushOptionsRaw { 101 103 if option == "skip-ci" || option == "ci-skip" { 102 104 pushOptions.skipCi = true 105 + } 106 + if option == "verbose-ci" || option == "ci-verbose" { 107 + pushOptions.verboseCi = true 103 108 } 104 109 } 105 110 111 + resp := hook.HookResponse{ 112 + Messages: make([]string, 0), 113 + } 114 + 106 115 for _, line := range lines { 107 116 err := h.insertRefUpdate(line, gitUserDid, repoDid, repoName) 108 117 if err != nil { ··· 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 } ··· 185 196 if err != nil { 186 197 return err 187 198 } 199 + 200 + pipelineParseErrors := []string{} 188 201 189 202 var pipeline workflow.Pipeline 190 203 for _, e := range workflowDir { ··· 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 246 + } 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:", 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", warning.Path, warning.Type, warning.Reason)) 269 + } 270 + } 271 + if !hasDiagnostics { 272 + *clientMsgs = append(*clientMsgs, "success: pipeline compiled with no diagnostics") 273 + } 234 274 } 235 275 236 276 // do not run empty pipelines