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