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