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