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