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 wg := sync.WaitGroup{}
52 for i, cp := range ps {
53 wg.Add(1)
54 go func(i int, cp Plugin) {
55 defer wg.Done()
56 content, err := os.ReadFile(pm.PathWasm(cp))
57 if err != nil {
58 pm.logger.Error("can't read plugin", err, logger.NewLoggerPair("filename", cp.Name), logger.NewLoggerPair("path", pm.PathWasm(cp)))
59 return
60 }
61 compiled, err := pm.runtimes.Compile(ctx, content)
62 if err != nil {
63 pm.logger.Error("can't compile plugin", err, logger.NewLoggerPair("filename", cp.Name), logger.NewLoggerPair("path", pm.PathWasm(cp)))
64 return
65 }
66 if cp.Name == "" {
67 cp = cp.DetermineNameAndVersionFromUrl()
68 }
69 if len(cp.Run) == 0 {
70 conf, err := pm.runtimes.Conf(ctx, Plugin{Name: cp.Name, Version: cp.Version, compiledModule: compiled})
71 if err != nil {
72 pm.logger.Error("can't get default conf", err, logger.NewLoggerPair("filename", cp.Name))
73 return
74 }
75 run, err := pluginLibToPlugin(conf.DefaultRun)
76 if err != nil {
77 pm.logger.Error("can't pluginLibToPlugin", err, logger.NewLoggerPair("filename", cp.Name))
78 return
79 } else {
80 cp.Run = run
81 }
82 if conf.SdkVersion != "0.2.0" {
83 pm.logger.Error("bad sdk version need update", err, logger.NewLoggerPair("plugin", cp.Name), logger.NewLoggerPair("sdkVersion", conf.SdkVersion))
84 return
85 }
86 }
87 commiter, err := pm.userManager.NewCommiter(cp.Name)
88 if err != nil {
89 pm.logger.Error("NewCommiter", err, logger.NewLoggerPair("pluginName", cp.Name))
90 }
91
92 pm.plugins = append(pm.plugins, Plugin{
93 Url: cp.Url,
94 Checksum: cp.Checksum,
95 Active: cp.Active,
96 Name: cp.Name,
97 Version: cp.Version,
98 Run: cp.Run,
99 compiledModule: compiled,
100 commiter: commiter,
101 })
102 pm.logger.Info("available plugin", logger.NewLoggerPair("name", cp.Name))
103 }(i, cp)
104 }
105 wg.Wait()
106 pm.pluginsLock.Unlock()
107 }
108 pm.pluginsLock.RLock()
109 defer pm.pluginsLock.RUnlock()
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) usable(ctx context.Context, repoName string) ([]Plugin, error) {
117 repo, err := pm.repoManager.Open(logger.AddCaller(ctx, "plugin.usable"), repoName)
118 if err != nil {
119 return nil, oops.Wrapf(err, "can't open repo")
120 }
121 defer repo.Close()
122 filecontent, err := repo.ContentPluginsConf()
123 if err != nil {
124 return nil, oops.Wrapf(err, "can't read plugins from repo")
125 }
126 confPlugins, err := ParsePlugins(filecontent, true)
127 if err != nil {
128 return nil, oops.Wrapf(err, "can't parse plugins from repo")
129 }
130 if len(confPlugins) == 0 {
131 pm.logger.Debug("no usable plugin in conf")
132 }
133 plugins, err := pm.Availables(ctx)
134 if err != nil {
135 return nil, oops.Wrapf(err, "can't get availables plugin from forgerepo")
136 }
137 usablePlugins := make([]Plugin, 0)
138 for _, p := range plugins {
139 for _, cp := range confPlugins {
140 if p.Name == cp.Name {
141 if cp.Active {
142 goodP := p.OverrideWith(cp).SetActive(cp.Active).SetRun(cp.Run)
143 usablePlugins = append(usablePlugins, goodP)
144 pm.logger.Info("usable plugin", logger.NewLoggerPair("name", cp.Name))
145 }
146 break
147 }
148 }
149 }
150 return usablePlugins, nil
151}