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