Monorepo for Tangled tangled.org

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 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "net/http" 8 "os" ··· 10 11 "github.com/urfave/cli/v3" 12 ) 13 14 // The hook command is nested like so: 15 // ··· 86 87 if resp.StatusCode != http.StatusOK { 88 return fmt.Errorf("unexpected status code: %d", resp.StatusCode) 89 } 90 91 return nil
··· 3 import ( 4 "bufio" 5 "context" 6 + "encoding/json" 7 "fmt" 8 "net/http" 9 "os" ··· 11 12 "github.com/urfave/cli/v3" 13 ) 14 + 15 + type HookResponse struct { 16 + Messages []string `json:"messages"` 17 + } 18 19 // The hook command is nested like so: 20 // ··· 91 92 if resp.StatusCode != http.StatusOK { 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) 103 } 104 105 return nil
+45 -5
knotserver/internal.go
··· 13 "github.com/go-chi/chi/v5" 14 "github.com/go-chi/chi/v5/middleware" 15 "tangled.sh/tangled.sh/core/api/tangled" 16 "tangled.sh/tangled.sh/core/knotserver/config" 17 "tangled.sh/tangled.sh/core/knotserver/db" 18 "tangled.sh/tangled.sh/core/knotserver/git" ··· 65 } 66 67 type PushOptions struct { 68 - skipCi bool 69 } 70 71 func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) { ··· 100 for _, option := range pushOptionsRaw { 101 if option == "skip-ci" || option == "ci-skip" { 102 pushOptions.skipCi = true 103 } 104 } 105 106 for _, line := range lines { 107 err := h.insertRefUpdate(line, gitUserDid, repoDid, repoName) 108 if err != nil { ··· 110 // non-fatal 111 } 112 113 - err = h.triggerPipeline(line, gitUserDid, repoDid, repoName, pushOptions) 114 if err != nil { 115 l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 116 // non-fatal 117 } 118 } 119 } 120 121 func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error { ··· 161 return h.db.InsertEvent(event, h.n) 162 } 163 164 - func (h *InternalHandle) triggerPipeline(line git.PostReceiveLine, gitUserDid, repoDid, repoName string, pushOptions PushOptions) error { 165 if pushOptions.skipCi { 166 return nil 167 } ··· 185 if err != nil { 186 return err 187 } 188 189 var pipeline workflow.Pipeline 190 for _, e := range workflowDir { ··· 200 201 wf, err := workflow.FromFile(e.Name, contents) 202 if err != nil { 203 - // TODO: log here, respond to client that is pushing 204 h.l.Error("failed to parse workflow", "err", err, "path", fpath) 205 continue 206 } 207 ··· 226 }, 227 } 228 229 - // TODO: send the diagnostics back to the user here via stderr 230 cp := compiler.Compile(pipeline) 231 eventJson, err := json.Marshal(cp) 232 if err != nil { 233 return err 234 } 235 236 // do not run empty pipelines
··· 13 "github.com/go-chi/chi/v5" 14 "github.com/go-chi/chi/v5/middleware" 15 "tangled.sh/tangled.sh/core/api/tangled" 16 + "tangled.sh/tangled.sh/core/hook" 17 "tangled.sh/tangled.sh/core/knotserver/config" 18 "tangled.sh/tangled.sh/core/knotserver/db" 19 "tangled.sh/tangled.sh/core/knotserver/git" ··· 66 } 67 68 type PushOptions struct { 69 + skipCi bool 70 + verboseCi bool 71 } 72 73 func (h *InternalHandle) PostReceiveHook(w http.ResponseWriter, r *http.Request) { ··· 102 for _, option := range pushOptionsRaw { 103 if option == "skip-ci" || option == "ci-skip" { 104 pushOptions.skipCi = true 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), 113 + } 114 + 115 for _, line := range lines { 116 err := h.insertRefUpdate(line, gitUserDid, repoDid, repoName) 117 if err != nil { ··· 119 // non-fatal 120 } 121 122 + err = h.triggerPipeline(&resp.Messages, line, gitUserDid, repoDid, repoName, pushOptions) 123 if err != nil { 124 l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) 125 // non-fatal 126 } 127 } 128 + 129 + writeJSON(w, resp) 130 } 131 132 func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error { ··· 172 return h.db.InsertEvent(event, h.n) 173 } 174 175 + func (h *InternalHandle) triggerPipeline(clientMsgs *[]string, line git.PostReceiveLine, gitUserDid, repoDid, repoName string, pushOptions PushOptions) error { 176 if pushOptions.skipCi { 177 return nil 178 } ··· 196 if err != nil { 197 return err 198 } 199 + 200 + pipelineParseErrors := []string{} 201 202 var pipeline workflow.Pipeline 203 for _, e := range workflowDir { ··· 213 214 wf, err := workflow.FromFile(e.Name, contents) 215 if err != nil { 216 h.l.Error("failed to parse workflow", "err", err, "path", fpath) 217 + pipelineParseErrors = append(pipelineParseErrors, fmt.Sprintf("- at %s: %s\n", fpath, err)) 218 continue 219 } 220 ··· 239 }, 240 } 241 242 cp := compiler.Compile(pipeline) 243 eventJson, err := json.Marshal(cp) 244 if err != nil { 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 + } 274 } 275 276 // do not run empty pipelines