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