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