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 func() {
 31			if err := forgeRepo.Close(); err != nil {
 32				pm.logger.Error("forgeRepo.Close", err)
 33			}
 34		}()
 35		if err != nil {
 36			return nil, errorHandler.Wrapf(err, "can't open forgeRepo")
 37		}
 38		filecontent, err := forgeRepo.ContentPluginsConf()
 39		if errors.Is(err, object.ErrFileNotFound) {
 40			return []Plugin{}, nil
 41		} else if err != nil {
 42			return nil, oops.Wrapf(err, "can't read plugins filecontent from repo")
 43		}
 44		ps, err := ParsePlugins(filecontent, false)
 45		if err != nil {
 46			return nil, oops.Wrapf(err, "can't parse plugins from repo")
 47		}
 48		pm.pluginsLock.Lock()
 49		pm.plugins = make([]Plugin, len(ps))
 50		//todo can be parallelized and compiled to wazero module
 51		for i, cp := range ps {
 52			content, err := os.ReadFile(pm.PathWasm(cp))
 53			if err != nil {
 54				return nil, errorHandler.With("filename", cp.Name).Wrapf(err, "can't read plugin")
 55			}
 56			if cp.Name == "" {
 57				cp = cp.DetermineNameAndVersionFromUrl()
 58			}
 59			if len(cp.Run) == 0 {
 60				conf, err := pm.runtimes.Conf(ctx, Plugin{Name: cp.Name, Version: cp.Version, content: content})
 61				if err != nil {
 62					pm.logger.Error("can't get default conf", err, logger.NewLoggerPair("filename", cp.Name))
 63				}
 64				run, err := pluginLibToPlugin(conf)
 65				if err != nil {
 66					pm.logger.Error("can't pluginLibToPlugin", err, logger.NewLoggerPair("filename", cp.Name))
 67				} else {
 68					cp.Run = run
 69				}
 70			}
 71			commiter, err := pm.userManager.NewCommiter(cp.Name)
 72			if err != nil {
 73				pm.logger.Error("NewCommiter", err, logger.NewLoggerPair("pluginName", cp.Name))
 74			}
 75
 76			pm.plugins[i] = Plugin{
 77				Url:      cp.Url,
 78				Crc:      cp.Crc,
 79				Active:   cp.Active,
 80				Name:     cp.Name,
 81				Version:  cp.Version,
 82				Run:      cp.Run,
 83				content:  content,
 84				commiter: commiter,
 85			}
 86			pm.logger.Info("available plugin", logger.NewLoggerPair("name", cp.Name))
 87		}
 88		pm.pluginsLock.Unlock()
 89	}
 90	pm.pluginsLock.RLock()
 91	defer pm.pluginsLock.RUnlock()
 92	if len(pm.plugins) == 0 {
 93		pm.logger.Info("no available plugin", logger.NewLoggerPair("in dir", pm.conf.PathDataPlugin()))
 94	}
 95	return pm.plugins, nil
 96}
 97
 98func (pm *Manager) usable(ctx context.Context, repoName string) ([]Plugin, error) {
 99	repo, err := pm.repoManager.Open(logger.AddCaller(ctx, "plugin.usable"), repoName)
100	if err != nil {
101		return nil, oops.Wrapf(err, "can't open repo")
102	}
103	defer repo.Close()
104	filecontent, err := repo.ContentPluginsConf()
105	if err != nil {
106		return nil, oops.Wrapf(err, "can't read plugins from repo")
107	}
108	confPlugins, err := ParsePlugins(filecontent, true)
109	if err != nil {
110		return nil, oops.Wrapf(err, "can't parse plugins from repo")
111	}
112	if len(confPlugins) == 0 {
113		pm.logger.Debug("no usable plugin in conf")
114	}
115	plugins, err := pm.Availables(ctx)
116	if err != nil {
117		return nil, oops.Wrapf(err, "can't get availables plugin from forgerepo")
118	}
119	usablePlugins := make([]Plugin, 0)
120	for _, p := range plugins {
121		for _, cp := range confPlugins {
122			if p.Name == cp.Name {
123				if cp.Active {
124					goodP := p.OverrideWith(cp).SetActive(cp.Active).SetRun(cp.Run)
125					usablePlugins = append(usablePlugins, goodP)
126					pm.logger.Info("usable plugin", logger.NewLoggerPair("name", cp.Name))
127				}
128				break
129			}
130		}
131	}
132	return usablePlugins, nil
133}