+14
hook/hook.go
+14
hook/hook.go
···
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
+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