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