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