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 "context"
9 "errors"
10 "os"
11 "sync"
12
13 "github.com/go-git/go-git/v5/plumbing/object"
14 "github.com/samber/oops"
15 "gitroot.dev/server/logger"
16)
17
18func (pm *Manager) reloadPlugins() {
19 pm.pluginsLock.Lock()
20 pm.plugins = nil
21 pm.pluginsLock.Unlock()
22 pm.Availables(context.Background()) //TODO pass good context
23}
24
25func (pm *Manager) Availables(ctx context.Context) ([]Plugin, error) {
26 pm.pluginsLock.Lock()
27 defer pm.pluginsLock.Unlock()
28 needLoad := pm.plugins == nil
29 if needLoad {
30 pm.logger.Info("availables needLoad")
31 errorHandler := oops.With("rootData", pm.conf.PathDataPlugin())
32 forgeRepo, err := pm.repoManager.OpenForgeRepo(logger.AddCaller(ctx, "plugin.Availables"))
33 defer func() {
34 if err := forgeRepo.Close(); err != nil {
35 pm.logger.Error("forgeRepo.Close", err)
36 }
37 }()
38 if err != nil {
39 return nil, errorHandler.Wrapf(err, "can't open forgeRepo")
40 }
41 filecontent, err := forgeRepo.ContentPluginsConf()
42 if errors.Is(err, object.ErrFileNotFound) {
43 return []Plugin{}, nil
44 } else if err != nil {
45 return nil, oops.Wrapf(err, "can't read plugins filecontent from repo")
46 }
47 ps, err := ParsePlugins(filecontent, false)
48 if err != nil {
49 return nil, oops.Wrapf(err, "can't parse plugins from repo")
50 }
51 pm.plugins = make([]Plugin, 0)
52 timeLoading := pm.logger.Time("loading wasm files", logger.NewLoggerPairNamedSlice("plugins", ps))
53 wg := sync.WaitGroup{}
54 for i, cp := range ps {
55 wg.Add(1)
56 go func(i int, cp Plugin) {
57 defer wg.Done()
58 content, err := os.ReadFile(pm.PathWasm(cp))
59 if err != nil {
60 pm.logger.Error("can't read plugin", err, logger.NewLoggerPair("filename", cp.Name), logger.NewLoggerPair("path", pm.PathWasm(cp)))
61 return
62 }
63 compiled, err := pm.runtimes.Compile(ctx, content)
64 if err != nil {
65 pm.logger.Error("can't compile plugin", err, logger.NewLoggerPair("filename", cp.Name), logger.NewLoggerPair("path", pm.PathWasm(cp)))
66 return
67 }
68 if cp.Name == "" {
69 cp = cp.DetermineNameAndVersionFromUrl()
70 }
71 if len(cp.Run) == 0 {
72 conf, err := pm.runtimes.Conf(ctx, Plugin{Name: cp.Name, Version: cp.Version, compiledModule: compiled})
73 if err != nil {
74 pm.logger.Error("can't get default conf", err, logger.NewLoggerPair("filename", cp.Name))
75 return
76 }
77 run, err := pluginLibToPlugin(conf.DefaultRun)
78 if err != nil {
79 pm.logger.Error("can't pluginLibToPlugin", err, logger.NewLoggerPair("filename", cp.Name))
80 return
81 } else {
82 cp.Run = run
83 }
84 if conf.SdkVersion != "0.4.0" {
85 pm.logger.Error("bad sdk version need update", err, logger.NewLoggerPair("plugin", cp.Name), logger.NewLoggerPair("sdkVersion", conf.SdkVersion))
86 return
87 }
88 }
89 commiter, err := pm.userManager.NewCommiter(cp.Name)
90 if err != nil {
91 pm.logger.Error("NewCommiter", err, logger.NewLoggerPair("pluginName", cp.Name))
92 }
93
94 pm.plugins = append(pm.plugins, Plugin{
95 Url: cp.Url,
96 Checksum: cp.Checksum,
97 Active: cp.Active,
98 Name: cp.Name,
99 Version: cp.Version,
100 Run: cp.Run,
101 compiledModule: compiled,
102 commiter: commiter,
103 })
104 pm.logger.Info("available plugin", logger.NewLoggerPair("name", cp.Name))
105 }(i, cp)
106 }
107 wg.Wait()
108 timeLoading()
109 }
110 if len(pm.plugins) == 0 {
111 pm.logger.Info("no available plugin", logger.NewLoggerPair("in dir", pm.conf.PathDataPlugin()))
112 }
113 return pm.plugins, nil
114}
115
116func (pm *Manager) usableFromDefaultBranch(ctx context.Context, repoName string) ([]Plugin, error) {
117 pm.logger.Debug("usableFromDefaultBranch before open", logger.NewLoggerPair("repoName", repoName))
118 repo, err := pm.repoManager.Open(logger.AddCaller(ctx, "plugin.usable"), repoName)
119 if err != nil {
120 return nil, oops.Wrapf(err, "can't open repo")
121 }
122 defer repo.Close()
123 pm.logger.Debug("usableFromDefaultBranch before ContentPluginsConf", logger.NewLoggerPair("repoName", repoName))
124 filecontent, err := repo.ContentPluginsConf()
125 if err != nil {
126 return nil, oops.Wrapf(err, "can't read plugins from repo")
127 }
128 confPlugins, err := ParsePlugins(filecontent, true)
129 if err != nil {
130 return nil, oops.Wrapf(err, "can't parse plugins from repo")
131 }
132 if len(confPlugins) == 0 {
133 pm.logger.Debug("no usable plugin in conf")
134 }
135 pm.logger.Debug("usableFromDefaultBranch before Availables", logger.NewLoggerPair("repoName", repoName))
136 plugins, err := pm.Availables(ctx)
137 if err != nil {
138 return nil, oops.Wrapf(err, "can't get availables plugin from forgerepo")
139 }
140 pm.logger.Debug("availables plugins", logger.NewLoggerPairNamedSlice("names", plugins))
141 pm.logger.Debug("project plugins", logger.NewLoggerPairNamedSlice("names", confPlugins))
142 usablePlugins := make([]Plugin, 0)
143 for _, cp := range confPlugins {
144 for _, p := range plugins {
145 if p.Name == cp.Name {
146 if cp.Active {
147 goodP := p.OverrideWith(cp).SetActive(cp.Active).SetRun(cp.Run)
148 usablePlugins = append(usablePlugins, goodP)
149 pm.logger.Debug("usable plugin", logger.NewLoggerPair("name", cp.Name))
150 }
151 break
152 }
153 }
154 }
155 return usablePlugins, nil
156}