GitRoot
Craft your forge, Build your project, Grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package plugin
6
7import (
8 "bytes"
9 "context"
10 "crypto/rand"
11 "encoding/json"
12 "errors"
13 "fmt"
14 "io"
15 "io/fs"
16 "net/http"
17 "os"
18 "path/filepath"
19 "slices"
20 "strings"
21 "time"
22 "unicode/utf16"
23
24 "github.com/go-git/go-git/v6/plumbing"
25 "github.com/go-git/go-git/v6/plumbing/object"
26 "github.com/go-git/go-git/v6/plumbing/protocol/packp"
27 "github.com/samber/oops"
28 "github.com/tetratelabs/wazero"
29 "github.com/tetratelabs/wazero/api"
30 "github.com/tetratelabs/wazero/imports/assemblyscript"
31 "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
32 "github.com/tetratelabs/wazero/sys"
33 "gitroot.dev/libs/golang/plugin/model"
34 pluginLib "gitroot.dev/libs/golang/plugin/model"
35 "gitroot.dev/server/logger"
36 "gitroot.dev/server/repository"
37 "gitroot.dev/server/user"
38)
39
40type runtimeInputsKind int
41
42const (
43 runtimeInputsKindDiff runtimeInputsKind = iota
44 runtimeInputsKindWorktree runtimeInputsKind = iota
45)
46
47type runtimeInputs struct {
48 ctx context.Context
49 repoName string
50 kind runtimeInputsKind
51 plugins []Plugin
52 command CommandForDiff
53 close func()
54}
55
56type memoryGarbage struct {
57 module api.Module
58 ptrSize ptrSize
59}
60
61type runtime struct {
62 ctx context.Context
63 manager *Manager
64 logger *logger.Logger
65 repo *repository.GitRootRepository
66 repoWriter *repository.GitRootRepositoryWrite
67 plugin Plugin
68 pluginRun PluginRun
69 command *CommandForDiff
70 commit *commitForDiffCommit
71 wazRun wazero.Runtime
72 commitHook func(h plumbing.Hash)
73 mergeHook func(cmd *repository.MergeRes, pusher user.SimpleUser, toDeleteBranchName string)
74 reports []pluginLib.Report
75 memoryToGarbage []memoryGarbage
76 modulesToGarbage []api.Module
77 httpClient *http.Client
78}
79
80func newRuntime(ctx context.Context, manager *Manager, logger *logger.Logger, runtimeConfig wazero.RuntimeConfig) (*runtime, error) {
81 r := &runtime{
82 ctx: ctx,
83 manager: manager,
84 logger: logger,
85 wazRun: wazero.NewRuntimeWithConfig(ctx, runtimeConfig),
86 commitHook: nil,
87 mergeHook: nil,
88 memoryToGarbage: []memoryGarbage{},
89 modulesToGarbage: []api.Module{},
90 httpClient: &http.Client{
91 Jar: nil,
92 CheckRedirect: nil,
93 Transport: &http.Transport{
94 MaxIdleConns: 10,
95 IdleConnTimeout: 30 * time.Second,
96 },
97 Timeout: 10 * time.Second,
98 },
99 }
100
101 _, err := assemblyscript.Instantiate(ctx, r.wazRun)
102 if err != nil {
103 return nil, err
104 }
105
106 _, err = r.wazRun.
107 NewHostModuleBuilder("gitroot").
108 NewFunctionBuilder().WithFunc(r.ForgeConf).Export("forgeConf").
109 NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(false)).Export("modifyContent").
110 NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(true)).Export("modifyContentAS").
111 NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(false)).Export("modifyWebContent").
112 NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(true)).Export("modifyWebContentAS").
113 NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(false)).Export("replaceWebContent").
114 NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(true)).Export("replaceWebContentAS").
115 NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(false)).Export("modifyCacheContent").
116 NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(true)).Export("modifyCacheContentAS").
117 NewFunctionBuilder().WithFunc(r.ReplaceContent(false)).Export("replaceContent").
118 NewFunctionBuilder().WithFunc(r.ReplaceContent(true)).Export("replaceContentAS").
119 NewFunctionBuilder().WithFunc(r.WriteContent(false)).Export("writeContent").
120 NewFunctionBuilder().WithFunc(r.WriteContent(true)).Export("writeContentAS").
121 NewFunctionBuilder().WithFunc(r.CopyFileBuilder(false)).Export("copyFile").
122 NewFunctionBuilder().WithFunc(r.CopyFileBuilder(true)).Export("copyFileAS").
123 NewFunctionBuilder().WithFunc(r.DeleteFileBuilder(false)).Export("deleteFile").
124 NewFunctionBuilder().WithFunc(r.DeleteFileBuilder(true)).Export("deleteFileAS").
125 NewFunctionBuilder().WithFunc(r.MoveFileBuilder(false)).Export("moveFile").
126 NewFunctionBuilder().WithFunc(r.MoveFileBuilder(true)).Export("moveFileAS").
127 NewFunctionBuilder().WithFunc(r.CommitAllBuilder(false)).Export("commitAll").
128 NewFunctionBuilder().WithFunc(r.CommitAllBuilder(true)).Export("commitAllAS").
129 NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(false)).Export("diffWithParent").
130 NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(true)).Export("diffWithParentAS").
131 NewFunctionBuilder().WithFunc(r.LogBuilder(false)).Export("log").
132 NewFunctionBuilder().WithFunc(r.LogBuilder(true)).Export("logAS").
133 NewFunctionBuilder().WithFunc(r.LogErrorBuilder(false)).Export("logError").
134 NewFunctionBuilder().WithFunc(r.LogErrorBuilder(true)).Export("logErrorAS").
135 NewFunctionBuilder().WithFunc(r.MergeBuilder(false)).Export("merge").
136 NewFunctionBuilder().WithFunc(r.MergeBuilder(true)).Export("mergeAS").
137 NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(false)).Export("commits").
138 NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(true)).Export("commitsAS").
139 NewFunctionBuilder().WithFunc(r.ExecBuilder(false)).Export("exec").
140 NewFunctionBuilder().WithFunc(r.ExecBuilder(true)).Export("execAS").
141 NewFunctionBuilder().WithFunc(r.ReportBuilder(false)).Export("report").
142 NewFunctionBuilder().WithFunc(r.ReportBuilder(true)).Export("reportAS").
143 NewFunctionBuilder().WithFunc(r.CanCallBuilder(false)).Export("canCall").
144 NewFunctionBuilder().WithFunc(r.CanCallBuilder(true)).Export("canCallAS").
145 NewFunctionBuilder().WithFunc(r.CallBuilder(false)).Export("call").
146 NewFunctionBuilder().WithFunc(r.CallBuilder(true)).Export("callAS").
147 NewFunctionBuilder().WithFunc(r.HttpClientBuilder(false)).Export("httpClient").
148 NewFunctionBuilder().WithFunc(r.HttpClientBuilder(true)).Export("httpClientAS").
149 Instantiate(ctx)
150 if err != nil {
151 return nil, err
152 }
153 _, err = wasi_snapshot_preview1.Instantiate(ctx, r.wazRun)
154 if err != nil {
155 return nil, err
156 }
157 return r, nil
158}
159
160type hasBeenMerged struct {
161 cmd *repository.MergeRes
162 pusher user.SimpleUser
163 toDeleteBranchName string
164}
165
166func (r *runtime) listen(c chan runtimeInputs) {
167 postMergeActions := []hasBeenMerged{}
168 for i := range c {
169 r.logger.Debug("listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree))
170 repo, err := r.manager.repoManager.Open(logger.AddCaller(r.ctx, "runtime.listen"), i.repoName)
171 if err != nil {
172 r.logger.Error("open error in listen", err)
173 i.close()
174 continue
175 }
176 repoWriter, err := repo.WillWrite(i.command.branch) //TODO should mount good branch directly inside i.commands
177 if err != nil {
178 r.logger.Error("will write error in listen", err, logger.NewLoggerPair("repo", repo.Name()))
179 repo.Close()
180 i.close()
181 continue
182 }
183
184 switch i.kind {
185 case runtimeInputsKindDiff:
186 timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins", repo.Name()))
187 err := r.start(i.ctx, repo, repoWriter, i.plugins, i.command, func(cmd *repository.MergeRes, pusher user.SimpleUser, toDeleteBranchName string) {
188 if cmd != nil {
189 postMergeActions = append(postMergeActions, hasBeenMerged{
190 cmd: cmd,
191 pusher: pusher,
192 toDeleteBranchName: toDeleteBranchName,
193 })
194 }
195 })
196 if err != nil {
197 r.logger.Error("start error", err)
198 }
199 timerStop()
200 case runtimeInputsKindWorktree:
201 r.logger.Info("start worktree", logger.NewLoggerPair("repo", repo.Name()))
202 timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins worktree", repo.Name()))
203 if err := r.worktree(i.ctx, repo, repoWriter, i.plugins, i.command); err != nil {
204 r.logger.Error("start error", err)
205 }
206 timerStop()
207 }
208 r.sendReports(i.ctx, repo, repoWriter, i.plugins)
209 r.garbage()
210 repo.Close()
211 for _, postMerge := range postMergeActions {
212 r.manager.backgroundManager.DeleteBranch(i.repoName, postMerge.toDeleteBranchName)
213 r.manager.backgroundManager.PostPush(postMerge.pusher, i.repoName, []*packp.Command{postMerge.cmd.Cmd}, map[plumbing.ReferenceName][]plumbing.Hash{postMerge.cmd.Cmd.Name: postMerge.cmd.Commits}) //TODO need to find all commits of this branch
214 }
215 postMergeActions = nil
216 r.logger.Debug("finish listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree))
217 i.close()
218 }
219}
220
221func (r *runtime) loadModule(ctx context.Context, plugin Plugin, withFs fs.FS) (api.Module, error) {
222 m := r.wazRun.Module(plugin.uuid())
223 if m != nil && m.IsClosed() {
224 r.logger.Debug("module exist but closed", logger.NewLoggerPair("name", plugin.Log()))
225 m = nil
226 }
227 if m == nil {
228 r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Log()), logger.NewLoggerPair("with external fs", withFs != nil))
229 config := wazero.NewModuleConfig().
230 WithStdout(os.Stdout).WithStderr(os.Stderr).
231 WithSysWalltime().
232 WithSysNanotime().
233 WithRandSource(rand.Reader).
234 WithName(plugin.uuid()).
235 WithStartFunctions("_initialize", "install")
236
237 if withFs != nil {
238 config = config.WithFS(withFs)
239 } else {
240 r.logger.Warn("no fs mounted", logger.NewLoggerPair("plugin", plugin.Log()), logger.NewLoggerPair("repo is nil", r.repo == nil), logger.NewLoggerPair("repoWrite is nil", r.repoWriter == nil))
241 }
242
243 mod, err := r.wazRun.InstantiateModule(ctx, plugin.compiledModule, config)
244 if err != nil {
245 if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
246 fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
247 return nil, err
248 } else if !ok {
249 return nil, err
250 }
251 }
252 m = mod
253 } else {
254 r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Log()))
255 }
256 r.modulesToGarbage = append(r.modulesToGarbage, m)
257 return m, nil
258}
259
260func (r *runtime) garbageMemory() {
261 byModule := make(map[api.Module][]ptrSize)
262 for _, d := range r.memoryToGarbage {
263 if all, ok := byModule[d.module]; ok {
264 byModule[d.module] = append(all, d.ptrSize)
265 } else {
266 byModule[d.module] = []ptrSize{d.ptrSize}
267 }
268 }
269 for m, ptr := range byModule {
270 r.free(m, ptr)
271 }
272 r.memoryToGarbage = []memoryGarbage{}
273}
274
275func (r *runtime) garbage() {
276 r.garbageMemory()
277 r.reports = []pluginLib.Report{}
278 for _, m := range r.modulesToGarbage {
279 if err := m.Close(r.ctx); err != nil {
280 r.logger.Error("close module fail", err, logger.NewLoggerPair("name", m.Name()))
281 }
282 }
283 r.modulesToGarbage = []api.Module{}
284}
285
286func (r *runtime) ForgeConf(_ context.Context, m api.Module) uint64 {
287 forgeConf, err := json.Marshal(r.manager.conf.ForgeConf())
288 if err != nil {
289 r.logger.Error("ForgeConf can't be serialized", err)
290 return 0
291 }
292 ptrSize, err := r.sendData(m, string(forgeConf))
293 if err != nil {
294 r.logger.Error("ForgeConf can't send data", err)
295 return 0
296 }
297 return ptrSize
298}
299
300func (r *runtime) modifyContent(filename string, content string) error {
301 if ok, err := checkWrite(r.pluginRun.write.git, r.repoWriter.ToFs(r.ctx), filename); !ok {
302 r.logger.Error("plugin can't write in git", err, logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("path", filename))
303 return err
304 }
305 r.logger.Debug("modifyContent", logger.NewLoggerPair("file", filename))
306 if err := r.repoWriter.Write(filename, []byte(content)); err != nil {
307 r.logger.Error("modifyContent can't open file", err, logger.NewLoggerPair("filepath", filename))
308 return err
309 }
310 return nil
311}
312
313// TODO delete after v0.4 is released
314// Deprecated: Use WriteContent(fs, filepath, content string) instead
315func (r *runtime) ModifyContentBuilder(forAS bool) interface{} {
316 if !forAS {
317 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
318 filename, err := r.readString(m, "ModifyContent filename", filenamePtr, filenameSize)
319 if err != nil {
320 return
321 }
322 content, err := r.readString(m, "ModifyContent content", contentPtr, contentSize)
323 if err != nil {
324 return
325 }
326 r.modifyContent(filename, content)
327 }
328 } else {
329 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
330 filename, err := r.readASString(m, "ModifyContent filename", filenamePtr)
331 if err != nil {
332 return
333 }
334 content, err := r.readASString(m, "ModifyContent content", contentPtr)
335 if err != nil {
336 return
337 }
338 r.modifyContent(filename, content)
339 }
340 }
341}
342
343func (r *runtime) modifyWebContent(filename string, content string) error {
344 if r.repo == nil { //in forconf scenario their is no repo
345 r.logger.Error("ModifyWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("filepath", filename))
346 return errors.New("no repo")
347 }
348 if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok {
349 r.logger.Warn("plugin can't write in web", logger.NewLoggerPair("err", err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("branch", r.command.branch), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("path", filename))
350 return errors.New("can't write in web")
351 }
352 fullPath := r.repo.PathDataWeb(filename)
353 dir, _ := filepath.Split(fullPath)
354 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
355 r.logger.Error("modifyWebContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
356 return err
357 }
358 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
359 r.logger.Error("modifyWebContent can't open file", err, logger.NewLoggerPair("filepath", filename))
360 return err
361 }
362 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
363 return nil
364}
365
366// TODO delete after v0.4 is released
367// Deprecated: Use WriteContent(fs, filepath, content string) instead
368func (r *runtime) ModifyWebContentBuilder(forAS bool) interface{} {
369 if !forAS {
370 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
371 filename, err := r.readString(m, "ModifyWebContent filename", filenamePtr, filenameSize)
372 if err != nil {
373 return
374 }
375 content, err := r.readString(m, "ModifyWebContent content", contentPtr, contentSize)
376 if err != nil {
377 return
378 }
379 r.modifyWebContent(filename, content)
380 }
381 } else {
382 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
383 filename, err := r.readASString(m, "ModifyWebContent filename", filenamePtr)
384 if err != nil {
385 return
386 }
387 content, err := r.readASString(m, "ModifyWebContent content", contentPtr)
388 if err != nil {
389 return
390 }
391 r.modifyWebContent(filename, content)
392 }
393 }
394}
395
396func (r *runtime) replaceGitContent(filename string, oldContent string, content string) error {
397 if r.repo == nil { //in forconf scenario their is no repo
398 r.logger.Error("ReplaceWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("filepath", filename))
399 return errors.New("no repo")
400 }
401 if ok, err := checkWrite(r.pluginRun.write.git, r.repoWriter.ToFs(r.ctx), filename); !ok {
402 r.logger.Warn("plugin can't write in git for replace", logger.NewLoggerPair("err", err), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("path", filename))
403 return err
404 }
405 fileContent, err := r.repo.Content(filename)
406 if err != nil {
407 r.logger.Error("replaceGitContent can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
408 return err
409 }
410 if err := r.repoWriter.Write(filename, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1)); err != nil {
411 r.logger.Error("replaceGitContent can't open file", err, logger.NewLoggerPair("filepath", filename))
412 return err
413 }
414 r.logger.Debug("Write in git", logger.NewLoggerPair("fullPath", filename))
415 return nil
416}
417
418func (r *runtime) replaceWebContent(filename string, oldContent string, content string) error {
419 if r.repo == nil { //in forconf scenario their is no repo
420 r.logger.Error("ReplaceWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("filepath", filename))
421 return errors.New("no repo")
422 }
423 if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok {
424 r.logger.Warn("plugin can't write in web for replace", logger.NewLoggerPair("err", err), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("path", filename))
425 return err
426 }
427 fullPath := r.repo.PathDataWeb(filename)
428 fileContent, err := os.ReadFile(fullPath)
429 if err != nil {
430 r.logger.Error("ReplaceWebContentBuilder can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
431 return err
432 }
433 if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
434 r.logger.Error("ReplaceWebContentBuilder can't open file", err, logger.NewLoggerPair("filepath", filename))
435 return err
436 }
437 r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
438 return nil
439}
440
441func (r *runtime) replaceCacheContent(filename string, oldContent string, content string) error {
442 if r.repo == nil { //in forconf scenario their is no repo
443 r.logger.Error("replaceCacheContent can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("filepath", filename))
444 return errors.New("no repo")
445 }
446 fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.NamespaceAndName()), filename)
447 fileContent, err := os.ReadFile(fullPath)
448 if err != nil {
449 r.logger.Error("replaceCacheContent can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
450 return err
451 }
452 if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
453 r.logger.Error("replaceCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
454 return err
455 }
456 r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
457 return nil
458}
459
460// TODO delete after v0.4 is released
461// Deprecated: Use ReplaceContent(fs, filepath, content string) instead
462func (r *runtime) ReplaceWebContentBuilder(forAS bool) interface{} {
463 if !forAS {
464 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) {
465 filename, err := r.readString(m, "ReplaceWebContentBuilder filename", filenamePtr, filenameSize)
466 if err != nil {
467 return
468 }
469 oldContent, err := r.readString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr, oldContentSize)
470 if err != nil {
471 return
472 }
473 content, err := r.readString(m, "ReplaceWebContentBuilder content", contentPtr, contentSize)
474 if err != nil {
475 return
476 }
477 r.replaceWebContent(filename, oldContent, content)
478 }
479 } else {
480 return func(_ context.Context, m api.Module, filenamePtr, oldContentPtr, contentPtr uint32) {
481 filename, err := r.readASString(m, "ReplaceWebContentBuilder filename", filenamePtr)
482 if err != nil {
483 return
484 }
485 oldContent, err := r.readASString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr)
486 if err != nil {
487 return
488 }
489 content, err := r.readASString(m, "ReplaceWebContentBuilder content", contentPtr)
490 if err != nil {
491 return
492 }
493 r.replaceWebContent(filename, oldContent, content)
494 }
495 }
496}
497
498func (r *runtime) modifyCacheContent(filename string, content string) error {
499 r.logger.Debug("ModifyCacheContent", logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("file", filename))
500 if r.repo == nil { //in forconf scenario their is no repo
501 r.logger.Error("ModifyCacheContent can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("filepath", filename))
502 return errors.New("no repo")
503 }
504 fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.NamespaceAndName()), filename)
505 dir, _ := filepath.Split(fullPath)
506 if err := os.MkdirAll(dir, os.ModePerm); err != nil {
507 r.logger.Error("ModifyCacheContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
508 return err
509 }
510 if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
511 r.logger.Error("ModifyCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
512 return err
513 }
514 r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
515 return nil
516}
517
518// TODO delete after v0.4 is released
519// Deprecated: Use WriteContent(fs, filepath, content string) instead
520func (r *runtime) ModifyCacheContentBuilder(forAS bool) interface{} {
521 if !forAS {
522 return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
523 filename, err := r.readString(m, "ModifyCacheContent filename", filenamePtr, filenameSize)
524 if err != nil {
525 return
526 }
527 content, err := r.readString(m, "ModifyCacheContent content", contentPtr, contentSize)
528 if err != nil {
529 return
530 }
531 r.modifyCacheContent(filename, content)
532 }
533 } else {
534 return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
535 filename, err := r.readASString(m, "ModifyCacheContent filename", filenamePtr)
536 if err != nil {
537 return
538 }
539 content, err := r.readASString(m, "ModifyCacheContent content", contentPtr)
540 if err != nil {
541 return
542 }
543 r.modifyCacheContent(filename, content)
544 }
545 }
546}
547
548func (r *runtime) replaceContent(fromFs string, path string, oldContent string, content string) error {
549 switch fromFs {
550 case "worktree":
551 return r.replaceGitContent(path, oldContent, content)
552 case "webcontent":
553 return r.replaceWebContent(path, oldContent, content)
554 case "cache":
555 return r.replaceCacheContent(path, oldContent, content)
556 }
557 return nil
558}
559
560func (r *runtime) ReplaceContent(forAS bool) interface{} {
561 if !forAS {
562 return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) uint64 {
563 fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
564 if err != nil {
565 return r.sendEmptyOrError(m, err)
566 }
567 fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
568 if err != nil {
569 return r.sendEmptyOrError(m, err)
570 }
571 oldContent, err := r.readString(m, "MoveFile oldContent", oldContentPtr, oldContentSize)
572 if err != nil {
573 return r.sendEmptyOrError(m, err)
574 }
575 content, err := r.readString(m, "MoveFile content", contentPtr, contentSize)
576 if err != nil {
577 return r.sendEmptyOrError(m, err)
578 }
579 return r.sendEmptyOrError(m, r.replaceContent(fromFs, fromPath, oldContent, content))
580 }
581 } else {
582 return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, oldContentPtr, contentPtr uint32) uint64 {
583 fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
584 if err != nil {
585 return r.sendEmptyOrError(m, err)
586 }
587 fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
588 if err != nil {
589 return r.sendEmptyOrError(m, err)
590 }
591 oldContent, err := r.readASString(m, "MoveFileAS oldContent", oldContentPtr)
592 if err != nil {
593 return r.sendEmptyOrError(m, err)
594 }
595 content, err := r.readASString(m, "MoveFileAS content", contentPtr)
596 if err != nil {
597 return r.sendEmptyOrError(m, err)
598 }
599 return r.sendEmptyOrError(m, r.replaceContent(fromFs, fromPath, oldContent, content))
600 }
601 }
602}
603
604func (r *runtime) writeContent(toFs string, path string, content string) error {
605 switch toFs {
606 case "worktree":
607 return r.modifyContent(path, content)
608 case "webcontent":
609 return r.modifyWebContent(path, content)
610 case "cache":
611 return r.modifyCacheContent(path, content)
612 }
613 return nil
614}
615
616func (r *runtime) WriteContent(forAS bool) interface{} {
617 if !forAS {
618 return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, contentPtr, contentSize uint32) uint64 {
619 fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
620 if err != nil {
621 return r.sendEmptyOrError(m, err)
622 }
623 fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
624 if err != nil {
625 return r.sendEmptyOrError(m, err)
626 }
627 content, err := r.readString(m, "MoveFile content", contentPtr, contentSize)
628 if err != nil {
629 return r.sendEmptyOrError(m, err)
630 }
631 return r.sendEmptyOrError(m, r.writeContent(fromFs, fromPath, content))
632 }
633 } else {
634 return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, contentPtr uint32) uint64 {
635 fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
636 if err != nil {
637 return r.sendEmptyOrError(m, err)
638 }
639 fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
640 if err != nil {
641 return r.sendEmptyOrError(m, err)
642 }
643 content, err := r.readASString(m, "MoveFileAS content", contentPtr)
644 if err != nil {
645 return r.sendEmptyOrError(m, err)
646 }
647 return r.sendEmptyOrError(m, r.writeContent(fromFs, fromPath, content))
648 }
649 }
650}
651
652func (r *runtime) copyFile(fromFs, fromPath, toFs, toPath string) error {
653 contentFrom := []byte("")
654 var err error
655 switch fromFs {
656 case "worktree":
657 contentFrom, err = r.repo.Content(fromPath)
658 case "webcontent":
659 fullPath := r.repo.PathDataWeb(fromPath)
660 contentFrom, err = os.ReadFile(fullPath)
661 case "cache":
662 fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.NamespaceAndName()), fromPath)
663 contentFrom, err = os.ReadFile(fullPath)
664 }
665 if err != nil {
666 return err
667 }
668 return r.writeContent(toFs, toPath, string(contentFrom))
669}
670
671func (r *runtime) CopyFileBuilder(forAS bool) interface{} {
672 if !forAS {
673 return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, toFsPtr, toFsSize, toPathPtr, toPathSize uint32) uint64 {
674 fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
675 if err != nil {
676 return r.sendEmptyOrError(m, err)
677 }
678 fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
679 if err != nil {
680 return r.sendEmptyOrError(m, err)
681 }
682 toFs, err := r.readString(m, "MoveFile toFs", toFsPtr, toFsSize)
683 if err != nil {
684 return r.sendEmptyOrError(m, err)
685 }
686 toPath, err := r.readString(m, "MoveFile toPath", toPathPtr, toPathSize)
687 if err != nil {
688 return r.sendEmptyOrError(m, err)
689 }
690 return r.sendEmptyOrError(m, r.copyFile(fromFs, fromPath, toFs, toPath))
691 }
692 } else {
693 return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, toFsPtr, toPathPtr uint32) uint64 {
694 fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
695 if err != nil {
696 return r.sendEmptyOrError(m, err)
697 }
698 fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
699 if err != nil {
700 return r.sendEmptyOrError(m, err)
701 }
702 toFs, err := r.readASString(m, "MoveFileAS toFs", toFsPtr)
703 if err != nil {
704 return r.sendEmptyOrError(m, err)
705 }
706 toPath, err := r.readASString(m, "MoveFileAS toPath", toPathPtr)
707 if err != nil {
708 return r.sendEmptyOrError(m, err)
709 }
710 return r.sendEmptyOrError(m, r.copyFile(fromFs, fromPath, toFs, toPath))
711 }
712 }
713}
714
715func (r *runtime) deleteFile(fromFs, filename string) error {
716 r.logger.Debug("delete", logger.NewLoggerPair("file", filename), logger.NewLoggerPair("fs", fromFs))
717 switch fromFs {
718 case "worktree":
719 if ok, err := checkDelete(r.pluginRun.write.git, filename); !ok {
720 r.logger.Error("plugin can't delete in git", err, logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("path", filename))
721 return err
722 }
723 return r.repoWriter.Remove(filename)
724 case "webcontent":
725 if ok, err := checkDelete(r.pluginRun.write.web, filename); !ok {
726 r.logger.Error("plugin can't delete in web", err, logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("path", filename))
727 return err
728 }
729 fullPath := r.repo.PathDataWeb(filename)
730 return os.Remove(fullPath)
731 case "cache":
732 fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.NamespaceAndName()), filename)
733 return os.Remove(fullPath)
734 }
735 return fmt.Errorf("unknown fs %s", fromFs)
736}
737
738func (r *runtime) DeleteFileBuilder(forAS bool) interface{} {
739 if !forAS {
740 return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize uint32) uint64 {
741 fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
742 if err != nil {
743 return r.sendEmptyOrError(m, err)
744 }
745 fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
746 if err != nil {
747 return r.sendEmptyOrError(m, err)
748 }
749 return r.sendEmptyOrError(m, r.deleteFile(fromFs, fromPath))
750 }
751 } else {
752 return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr uint32) uint64 {
753 fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
754 if err != nil {
755 return r.sendEmptyOrError(m, err)
756 }
757 fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
758 if err != nil {
759 return r.sendEmptyOrError(m, err)
760 }
761 return r.sendEmptyOrError(m, r.deleteFile(fromFs, fromPath))
762 }
763 }
764}
765
766func (r *runtime) moveFile(fromFs, fromPath, toFs, toPath string) error {
767 r.logger.Debug("moveFile", logger.NewLoggerPair("file", fromPath), logger.NewLoggerPair("fs", fromFs))
768 if err := r.copyFile(fromFs, fromPath, toFs, toPath); err != nil {
769 return err
770 }
771 r.logger.Debug("moveFile 2", logger.NewLoggerPair("file", fromPath), logger.NewLoggerPair("fs", fromFs))
772 return r.deleteFile(fromFs, fromPath)
773}
774
775func (r *runtime) MoveFileBuilder(forAS bool) interface{} {
776 if !forAS {
777 return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, toFsPtr, toFsSize, toPathPtr, toPathSize uint32) uint64 {
778 fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize)
779 if err != nil {
780 return r.sendEmptyOrError(m, err)
781 }
782 fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize)
783 if err != nil {
784 return r.sendEmptyOrError(m, err)
785 }
786 toFs, err := r.readString(m, "MoveFile toFs", toFsPtr, toFsSize)
787 if err != nil {
788 return r.sendEmptyOrError(m, err)
789 }
790 toPath, err := r.readString(m, "MoveFile toPath", toPathPtr, toPathSize)
791 if err != nil {
792 return r.sendEmptyOrError(m, err)
793 }
794 return r.sendEmptyOrError(m, r.moveFile(fromFs, fromPath, toFs, toPath))
795 }
796 } else {
797 return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, toFsPtr, toPathPtr uint32) uint64 {
798 fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr)
799 if err != nil {
800 return r.sendEmptyOrError(m, err)
801 }
802 fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr)
803 if err != nil {
804 return r.sendEmptyOrError(m, err)
805 }
806 toFs, err := r.readASString(m, "MoveFileAS toFs", toFsPtr)
807 if err != nil {
808 return r.sendEmptyOrError(m, err)
809 }
810 toPath, err := r.readASString(m, "MoveFileAS toPath", toPathPtr)
811 if err != nil {
812 return r.sendEmptyOrError(m, err)
813 }
814 return r.sendEmptyOrError(m, r.moveFile(fromFs, fromPath, toFs, toPath))
815 }
816 }
817}
818
819func (r *runtime) CommitAllBuilder(forAS bool) interface{} {
820 f := func(msg string, err error) {
821 if err != nil {
822 return
823 }
824 if h, err := r.repoWriter.CommitAll(msg, r.plugin.commiter); err != nil {
825 r.logger.Error("commitAll can't commit", err)
826 return
827 } else {
828 r.logger.Debug("Commit", logger.NewLoggerPair("message", msg), logger.NewLoggerPair("hash", h.String()))
829 if r.commitHook != nil && !h.IsZero() {
830 r.commitHook(h)
831 }
832 }
833 }
834 if !forAS {
835 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
836 f(r.readString(m, "commitAll", offset, byteCount))
837 }
838 } else {
839 return func(_ context.Context, m api.Module, offset uint32) {
840 f(r.readASString(m, "commitAll", offset))
841 }
842 }
843}
844
845func (r *runtime) DiffWithParentBuilder(forAS bool) interface{} {
846 f := func(m api.Module, hash string, oldfile string, newfile string) uint64 {
847 r.logger.Info("In diffWithParent before found ancestor")
848 diffStr, err := r.repoWriter.GetDiff(plumbing.NewHash(string(hash)), string(oldfile), string(newfile))
849 if err != nil {
850 r.logger.Error("GetDiff", err)
851 return 0
852 }
853 r.logger.Debug("In diffWithParent send diff", logger.NewLoggerPair("diff", diffStr))
854 ptrSize, err := r.sendData(m, diffStr)
855 if err != nil {
856 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", diffStr))
857 return 0
858 }
859 return ptrSize
860 }
861 if !forAS {
862 return func(_ context.Context, m api.Module, hashPtr, hashSize, oldFilenamePtr, oldFilenameSize, newFilenamePtr, newFilenameSize uint32) uint64 {
863 hash, err := r.readString(m, "DiffWithParent hash", hashPtr, hashSize)
864 if err != nil {
865 return 0
866 }
867 oldFilename, err := r.readString(m, "DiffWithParent oldFilename", oldFilenamePtr, oldFilenameSize)
868 if err != nil {
869 return 0
870 }
871 newFilename, err := r.readString(m, "DiffWithParent newFilename", newFilenamePtr, newFilenameSize)
872 if err != nil {
873 return 0
874 }
875 return f(m, hash, oldFilename, newFilename)
876 }
877 } else {
878 return func(_ context.Context, m api.Module, hashPtr, oldFilenamePtr, newFilenamePtr uint32) uint64 {
879 hash, err := r.readASString(m, "DiffWithParent hash", hashPtr)
880 if err != nil {
881 return 0
882 }
883 oldFilename, err := r.readASString(m, "DiffWithParent oldFilenamePtr", oldFilenamePtr)
884 if err != nil {
885 return 0
886 }
887 newFilename, err := r.readASString(m, "DiffWithParent newFilename", newFilenamePtr)
888 if err != nil {
889 return 0
890 }
891 return f(m, hash, oldFilename, newFilename)
892 }
893 }
894}
895
896func (r *runtime) LogBuilder(forAS bool) interface{} {
897 f := func(msg string, err error) {
898 if err != nil {
899 return
900 }
901 repoName := ""
902 if r.repo != nil { //in forconf scenario their is no repo
903 repoName = r.repo.Name()
904 }
905 r.logger.Debug(msg, logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Log()))
906 }
907 if !forAS {
908 return func(_ context.Context, m api.Module, offset, byteCount uint32) {
909 f(r.readString(m, "log", offset, byteCount))
910 }
911 } else {
912 return func(_ context.Context, m api.Module, offset uint32) {
913 f(r.readASString(m, "log", offset))
914 }
915 }
916}
917
918func (r *runtime) LogErrorBuilder(forAS bool) interface{} {
919 f := func(msg string, err string) {
920 repoName := ""
921 if r.repo != nil { //in forconf scenario their is no repo
922 repoName = r.repo.Name()
923 }
924 r.logger.Error(msg, errors.New(err), logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Log()))
925 }
926 if !forAS {
927 return func(_ context.Context, m api.Module, offset, byteCount, errPtr, errSize uint32) {
928 msg, err := r.readString(m, "LogError msg", offset, byteCount)
929 if err != nil {
930 return
931 }
932 errMsg, err := r.readString(m, "LogError err", errPtr, errSize)
933 if err != nil {
934 return
935 }
936 f(msg, errMsg)
937 }
938 } else {
939 return func(_ context.Context, m api.Module, offset, errPtr uint32) {
940 msg, err := r.readASString(m, "LogError msg", offset)
941 if err != nil {
942 return
943 }
944 errMsg, err := r.readASString(m, "LogError err", errPtr)
945 if err != nil {
946 return
947 }
948 f(msg, errMsg)
949 }
950 }
951}
952
953func (r *runtime) MergeBuilder(forAS bool) interface{} {
954 f := func(from string, to string) {
955 r.logger.Debug("try to merge", logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
956 cmd, err := r.repoWriter.Merge(from, to, r.plugin.commiter, r.command.pusher)
957 if err != nil {
958 r.logger.Error("can't Merge", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
959 }
960 if r.mergeHook != nil {
961 r.mergeHook(cmd, r.command.pusher, to)
962 }
963 }
964 if !forAS {
965 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) {
966 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
967 if err != nil {
968 return
969 }
970 to, err := r.readString(m, "Merge to", toPtr, toSize)
971 if err != nil {
972 return
973 }
974 f(from, to)
975 }
976 } else {
977 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) {
978 from, err := r.readASString(m, "Merge from", fromPtr)
979 if err != nil {
980 return
981 }
982 to, err := r.readASString(m, "Merge to", toPtr)
983 if err != nil {
984 return
985 }
986 f(from, to)
987 }
988 }
989}
990
991func (r *runtime) GetCommitsBuilder(forAS bool) interface{} {
992 f := func(m api.Module, from string, to string) uint64 {
993 groups, err := user.LoadGroup(r.repo)
994 if err != nil {
995 r.logger.Error("can't load group", err)
996 return 0
997 }
998 commits := []commitForDiffCommit{}
999 if err := r.repoWriter.WalkCommit(plumbing.NewHash(to), plumbing.NewHash(from), func(c *object.Commit) error {
1000 r.logger.Debug("found commit", logger.NewLoggerPair("hash", c.Hash.String()), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
1001 commits = append(commits, commitToCommitForDiff(c, nil, groups))
1002 return nil
1003 }); err != nil {
1004 r.logger.Error("can't WalkCommit", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
1005 }
1006 r.logger.Debug("found commits", logger.NewLoggerPair("nb", len(commits)), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
1007 j, err := Marshall(r.command.branch.Short(), commits)
1008 if err != nil {
1009 r.logger.Error("can't Marshall", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
1010 }
1011 ptrSize, err := r.sendData(m, j)
1012 if err != nil {
1013 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
1014 return 0
1015 }
1016 return ptrSize
1017 }
1018 if !forAS {
1019 return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) uint64 {
1020 from, err := r.readString(m, "Merge from", fromPtr, fromSize)
1021 if err != nil {
1022 return 0
1023 }
1024 to, err := r.readString(m, "Merge to", toPtr, toSize)
1025 if err != nil {
1026 return 0
1027 }
1028 return f(m, from, to)
1029 }
1030 } else {
1031 return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) uint64 {
1032 from, err := r.readASString(m, "Merge from", fromPtr)
1033 if err != nil {
1034 return 0
1035 }
1036 to, err := r.readASString(m, "Merge to", toPtr)
1037 if err != nil {
1038 return 0
1039 }
1040 return f(m, from, to)
1041 }
1042 }
1043}
1044
1045func (r *runtime) ExecBuilder(forAS bool) interface{} {
1046 f := func(m api.Module, cmd string) uint64 {
1047 exec := pluginLib.Exec{}
1048 err := json.Unmarshal([]byte(cmd), &exec)
1049 if err != nil {
1050 r.logger.Error("can't exec bad format", err)
1051 return 0
1052 }
1053 if ok := checkExec(r.pluginRun.write.exec, exec); !ok {
1054 r.logger.Warn("plugin can't exec", logger.NewLoggerPair("err", err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("branch", r.command.branch), logger.NewLoggerPair("plugin", r.plugin.Log()))
1055 return 0
1056 }
1057 j, err := r.manager.execManager.Exec(r.repo, r.command.branch.Short(), r.plugin.NamespaceAndName(), exec)
1058 if err != nil {
1059 r.logger.Error("can't exec", err)
1060 return 0
1061 }
1062 jjson, err := json.Marshal(j)
1063 if err != nil {
1064 r.logger.Error("can't marshal execStatus", err)
1065 return 0
1066 }
1067 ptrSize, err := r.sendData(m, string(jjson))
1068 if err != nil {
1069 r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j))
1070 return 0
1071 }
1072 return ptrSize
1073 }
1074 if !forAS {
1075 return func(_ context.Context, m api.Module, commandPtr, commandSize uint32) uint64 {
1076 cmd, err := r.readString(m, "exec cmd", commandPtr, commandSize)
1077 if err != nil {
1078 return 0
1079 }
1080 return f(m, cmd)
1081 }
1082 } else {
1083 return func(_ context.Context, m api.Module, commandPtr uint32) uint64 {
1084 cmd, err := r.readASString(m, "exec cmd", commandPtr)
1085 if err != nil {
1086 return 0
1087 }
1088 return f(m, cmd)
1089 }
1090 }
1091}
1092
1093func (r *runtime) ReportBuilder(forAS bool) interface{} {
1094 f := func(reportJson string) {
1095 if r.command == nil || r.commit == nil {
1096 r.logger.Error("can't report in conf stage", errors.New("report not allowed"), logger.NewLoggerPair("plugin", r.plugin.Log()))
1097 return
1098 }
1099 report := pluginLib.ReportToGitroot{}
1100 err := json.Unmarshal([]byte(reportJson), &report)
1101 if err != nil {
1102 r.logger.Error("can't unmarshal reportJson bad format", err)
1103 return
1104 }
1105 re := pluginLib.Report{
1106 Level: report.Level,
1107 Content: report.Content,
1108 FromPlugin: r.plugin.uuid(),
1109 FromBranch: r.command.branch.Short(),
1110 FromCommit: r.commit.hash.String(),
1111 }
1112 reJson, err := json.Marshal(re)
1113 if err != nil {
1114 r.logger.Error("can't marshal reJson bad format", err)
1115 return
1116 }
1117 r.logger.Debug("report added", logger.NewLoggerPair("report", reJson))
1118 r.reports = append(r.reports, re)
1119 }
1120 if !forAS {
1121 return func(_ context.Context, m api.Module, reportPtr, reportSize uint32) {
1122 reportJson, err := r.readString(m, "report json", reportPtr, reportSize)
1123 if err != nil {
1124 return
1125 }
1126 f(reportJson)
1127 }
1128 } else {
1129 return func(_ context.Context, m api.Module, reportPtr uint32) {
1130 reportJson, err := r.readASString(m, "report json", reportPtr)
1131 if err != nil {
1132 return
1133 }
1134 f(reportJson)
1135 }
1136 }
1137}
1138
1139func (r *runtime) CanCallBuilder(forAS bool) interface{} {
1140 f := func(callJson string) uint32 {
1141 repo := "no repo in conf mode"
1142 if r.repo != nil {
1143 repo = r.repo.Name()
1144 }
1145 r.logger.Info("can call json", logger.NewLoggerPair("repo", repo), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("data", string(callJson)))
1146 call := pluginLib.Call{}
1147 err := json.Unmarshal([]byte(callJson), &call)
1148 if err != nil {
1149 r.logger.Error("can't unmarshal canCallJson bad format", err)
1150 return 0
1151 }
1152 canCall := slices.ContainsFunc(r.pluginRun.write.callFunc, func(cf pluginLib.PluginCallFuncRight) bool {
1153 return cf.PluginPURL == call.Plugin && cf.FuncName == call.Name
1154 })
1155 // TODO find a way to check call.Args
1156 if !canCall {
1157 r.logger.Info("can't canCall func", logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("callPlugin", call.Plugin), logger.NewLoggerPair("func", call.Name), logger.NewLoggerPair("authorized", r.pluginRun.write.callFunc))
1158 return 0
1159 }
1160 return 1
1161 }
1162 if !forAS {
1163 return func(_ context.Context, m api.Module, callPtr, callSize uint32) uint32 {
1164 callJson, err := r.readString(m, "call json", callPtr, callSize)
1165 if err != nil {
1166 r.logger.Error("can't readString canCall", err)
1167 return 0
1168 }
1169 return f(callJson)
1170 }
1171 } else {
1172 return func(_ context.Context, m api.Module, callPtr uint32) uint32 {
1173 callJson, err := r.readASString(m, "call json", callPtr)
1174 if err != nil {
1175 r.logger.Error("can't readASString canCall", err)
1176 return 0
1177 }
1178 return f(callJson)
1179 }
1180 }
1181}
1182
1183func (r *runtime) CallBuilder(forAS bool) interface{} {
1184 f := func(callJson string) (uint64, api.Module) {
1185 r.logger.Info("call json", logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("data", string(callJson)))
1186 // TODO duplicated in previous (function canCall check rights)
1187 call := pluginLib.Call{}
1188 err := json.Unmarshal([]byte(callJson), &call)
1189 if err != nil {
1190 r.logger.Error("can't unmarshal callJson bad format", err)
1191 return 0, nil
1192 }
1193 canCall := slices.ContainsFunc(r.pluginRun.write.callFunc, func(cf pluginLib.PluginCallFuncRight) bool {
1194 return cf.PluginPURL == call.Plugin && cf.FuncName == call.Name
1195 })
1196 if !canCall {
1197 r.logger.Error("can't call func", errors.New("not authorized in plugins file"), logger.NewLoggerPair("plugin", r.plugin.Log()), logger.NewLoggerPair("callPlugin", call.Plugin), logger.NewLoggerPair("func", call.Name), logger.NewLoggerPair("authorized", r.pluginRun.write.callFunc))
1198 return 0, nil
1199 }
1200 r.logger.Debug("call usableFromDefaultBranch from CallBuilder", logger.NewLoggerPair("repo", r.repo.Name()))
1201 plugins, err := r.manager.usableFromDefaultBranch(r.ctx, r.repo.Name())
1202 if err != nil {
1203 r.logger.Error("can't get availables plugins", err)
1204 return 0, nil
1205 }
1206 for _, plugin := range plugins {
1207 if plugin.Match(call.Plugin) {
1208 m, err := r.loadModule(r.ctx, plugin, nil) //TODO where to find fs?
1209 if err != nil {
1210 r.logger.Error("can't loadModule", err, logger.NewLoggerPair("plugin", plugin.Log()))
1211 return 0, nil
1212 }
1213 funcToCall := m.ExportedFunction("call")
1214 malloc := m.ExportedFunction("gitrootAlloc")
1215 if malloc == nil {
1216 malloc = m.ExportedFunction("malloc")
1217 }
1218 callResPtrSize, err := r.writeMemoryAndCallWithRes(m, funcToCall, malloc, callJson)
1219 if err != nil {
1220 r.logger.Error("can't writeMemoryAndCallWithRes", err, logger.NewLoggerPair("plugin", plugin.Log()))
1221 return 0, nil
1222 }
1223 return callResPtrSize, m
1224 }
1225 }
1226 r.logger.Error("plugin not found", err, logger.NewLoggerPair("plugin", call.Plugin), logger.NewLoggerPair("method", call.Name))
1227 return 0, nil
1228 }
1229 callResJson, _ := json.Marshal(pluginLib.CallRes{Err: "plugin or method not found"})
1230 if !forAS {
1231 return func(_ context.Context, m api.Module, callPtr, callSize uint32) uint64 {
1232 callJson, err := r.readString(m, "call json", callPtr, callSize)
1233 if err != nil {
1234 r.logger.Error("can't readString call", err)
1235 return 0
1236 }
1237 callResPtrSize, newModule := f(callJson)
1238 if callResPtrSize > 0 && newModule != nil {
1239 ptrCall := uint32(callResPtrSize >> 32)
1240 sizeCall := uint32(callResPtrSize)
1241 callResJsonStr, err := r.readString(newModule, "can't read string in call", ptrCall, sizeCall)
1242 if err != nil {
1243 r.logger.Error("can't readString callRes", err)
1244 return 0
1245 }
1246 callResJson = []byte(callResJsonStr)
1247 }
1248 ptrSize, err := r.sendData(m, string(callResJson))
1249 if err != nil {
1250 r.logger.Error("can't sendData callRes", err)
1251 return 0
1252 }
1253 return ptrSize
1254 }
1255 } else {
1256 return func(_ context.Context, m api.Module, callPtr uint32) uint64 {
1257 callJson, err := r.readASString(m, "call json", callPtr)
1258 if err != nil {
1259 r.logger.Error("can't readASString call", err)
1260 return 0
1261 }
1262 callResPtrSize, newModule := f(callJson)
1263 if callResPtrSize > 0 && newModule != nil {
1264 ptrCall := uint32(callResPtrSize >> 32)
1265 sizeCall := uint32(callResPtrSize)
1266 // TODO here we readString and not readASString because most of plugins with callFunc are not as, but need to find a solution, maybe knowing what the plugin is made of
1267 callResJsonStr, err := r.readString(newModule, "can't read asstring in call", ptrCall, sizeCall)
1268 if err != nil {
1269 r.logger.Error("can't readASString callRes", err)
1270 return 0
1271 }
1272 callResJson = []byte(callResJsonStr)
1273 }
1274 r.logger.Debug("call res", logger.NewLoggerPair("res", string(callResJson)))
1275 ptrSize, err := r.sendData(m, string(callResJson))
1276 if err != nil {
1277 r.logger.Error("can't sendData callRes", err)
1278 return 0
1279 }
1280 return ptrSize
1281 }
1282 }
1283}
1284
1285func (r *runtime) HttpClientBuilder(forAS bool) interface{} {
1286 f := func(m api.Module, httpRequestJson string) uint64 {
1287 httpRequest := model.HttpRequest{}
1288 err := json.Unmarshal([]byte(httpRequestJson), &httpRequest)
1289 if err != nil {
1290 r.logger.Error("can't unmarshal httpRequestJson", err)
1291 return 0
1292 }
1293 var body io.Reader
1294 if httpRequest.Body == "" {
1295 body = strings.NewReader(httpRequest.Body)
1296 }
1297 req, err := http.NewRequest(httpRequest.Method, httpRequest.Url, body)
1298 if err != nil {
1299 r.logger.Error("bad request httpRequestJson", err)
1300 return 0
1301 }
1302 req.Header = httpRequest.Header
1303 resp, err := r.httpClient.Do(req)
1304 if err != nil {
1305 r.logger.Error("can't do request httpRequestJson", err)
1306 return 0
1307 }
1308 bodyContent, err := io.ReadAll(resp.Body)
1309 if err != nil {
1310 r.logger.Error("body can't read httpResponse", err)
1311 return 0
1312 }
1313 httpResponse := model.HttpResponse{
1314 StatusCode: resp.StatusCode,
1315 Body: string(bodyContent),
1316 }
1317 httpResponseJson, err := json.Marshal(httpResponse)
1318 if err != nil {
1319 r.logger.Error("can't marshal httpResponse", err)
1320 return 0
1321 }
1322 ptrSize, err := r.sendData(m, string(httpResponseJson))
1323 if err != nil {
1324 r.logger.Error("can't sendData httpResponseJson", err)
1325 return 0
1326 }
1327 return ptrSize
1328 }
1329 if !forAS {
1330 return func(_ context.Context, m api.Module, httpRequestPtr, httpRequestSize uint32) uint64 {
1331 httpRequestJson, err := r.readString(m, "httpRequest json", httpRequestPtr, httpRequestSize)
1332 if err != nil {
1333 r.logger.Error("can't readString httpRequestJson", err)
1334 return 0
1335 }
1336 return f(m, httpRequestJson)
1337 }
1338 } else {
1339 return func(_ context.Context, m api.Module, httpRequestPtr uint32) uint64 {
1340 httpRequestJson, err := r.readASString(m, "httpRequest json", httpRequestPtr)
1341 if err != nil {
1342 r.logger.Error("can't readString httpRequestJson", err)
1343 return 0
1344 }
1345 return f(m, httpRequestJson)
1346 }
1347 }
1348}
1349
1350func (r *runtime) Close() error {
1351 return r.wazRun.Close(r.ctx)
1352}
1353
1354func (r *runtime) sendData(module api.Module, message string) (ptrSizeToSend uint64, err error) {
1355 malloc := module.ExportedFunction("gitrootAlloc")
1356 if malloc == nil {
1357 malloc = module.ExportedFunction("malloc")
1358 }
1359
1360 s := uint64(len(message))
1361
1362 results, err := malloc.Call(r.ctx, s)
1363 if err != nil {
1364 return 0, oops.Wrapf(err, "can't malloc memory")
1365 }
1366 ptrA := results[0]
1367
1368 // The pointer is a linear memory offset, which is where we write the name.
1369 if !module.Memory().WriteString(uint32(ptrA), message) {
1370 return 0, oops.Errorf("can't write memory")
1371 }
1372
1373 r.memoryToGarbage = append(r.memoryToGarbage, memoryGarbage{module: module, ptrSize: ptrSize{ptr: ptrA, size: s}})
1374
1375 return (ptrA << uint64(32)) | s, nil
1376}
1377
1378func (r *runtime) sendEmptyOrError(module api.Module, err error) uint64 {
1379 if err == nil {
1380 r.logger.Debug("no error to send")
1381 return 0
1382 }
1383 ptrSize, err := r.sendData(module, err.Error())
1384 if err != nil {
1385 r.logger.Error("can't sendError", err, logger.NewLoggerPair("originalErr", err.Error()))
1386 return 0
1387 }
1388 return ptrSize
1389}
1390
1391func (r *runtime) readString(m api.Module, errorMsg string, ptr uint32, size uint32) (string, error) {
1392 mem := m.Memory()
1393 if mem == nil {
1394 err := errors.New("memory not exist")
1395 r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
1396 return "", err
1397 }
1398 txt, ok := mem.Read(ptr, size)
1399 if !ok {
1400 err := errors.New("memory read fail")
1401 r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
1402 return "", err
1403 }
1404 return string(txt), nil
1405}
1406
1407func (r *runtime) readASString(m api.Module, errorMsg string, offset uint32) (string, error) {
1408 // Length is four bytes before pointer.
1409 byteCount, ok := m.Memory().ReadUint32Le(offset - 4)
1410 if !ok || byteCount%2 != 0 {
1411 err := errors.New("Memory.ReadUint32Le fail")
1412 r.logger.Error(errorMsg, err, logger.NewLoggerPair("byteCount", byteCount), logger.NewLoggerPair("offset", offset))
1413 return "", err
1414 }
1415 buf, ok := m.Memory().Read(offset, byteCount)
1416 if !ok {
1417 err := errors.New("Memory.Read fail")
1418 r.logger.Error(errorMsg, err, logger.NewLoggerPair("offset", offset), logger.NewLoggerPair("byteCount", byteCount))
1419 return "", err
1420 }
1421 u16s := make([]uint16, len(buf)/2)
1422
1423 lb := len(buf)
1424 for i := 0; i < lb; i += 2 {
1425 u16s[i/2] = uint16(buf[i]) + (uint16(buf[i+1]) << 8)
1426 }
1427 return string(utf16.Decode(u16s)), nil
1428}
1429
1430type ptrSize struct {
1431 ptr uint64
1432 size uint64
1433}
1434
1435func (r *runtime) free(module api.Module, ptrSize []ptrSize) error {
1436 free := module.ExportedFunction("gitrootFree")
1437 if free == nil {
1438 free = module.ExportedFunction("free")
1439 }
1440
1441 if free != nil && len(free.Definition().ParamTypes()) == 1 {
1442 for _, p := range ptrSize {
1443 _, err := free.Call(r.ctx, p.ptr)
1444 if err != nil {
1445 r.logger.Error("can't free pluginConf 1 param", err, logger.NewLoggerPair("plugin", module.Name()))
1446 }
1447 }
1448 } else if free != nil {
1449 for _, p := range ptrSize {
1450 _, err := free.Call(r.ctx, p.ptr, p.size)
1451 if err != nil {
1452 r.logger.Error("can't free pluginConf", err, logger.NewLoggerPair("plugin", module.Name()))
1453 }
1454 }
1455 }
1456
1457 return nil
1458}