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}