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