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 background
  6
  7import (
  8	"bytes"
  9	"errors"
 10	"os"
 11
 12	"github.com/go-git/go-git/v5/plumbing"
 13	"github.com/go-git/go-git/v5/plumbing/protocol/packp"
 14	"github.com/samber/oops"
 15	"gitroot.dev/server/logger"
 16	"gitroot.dev/server/plugin"
 17	"gitroot.dev/server/user"
 18)
 19
 20type checkPluginsTaskInput struct {
 21	repoName         string
 22	isRootRepo       bool
 23	availablePlugins []plugin.Plugin
 24}
 25
 26func (m *Manager) syncPluginsInExistingRepos() {
 27	plugins, err := m.pluginManager.Availables(m.ctx)
 28	if err != nil {
 29		m.logger.Error("can't get availables plugins", err)
 30		return
 31	}
 32
 33	entries, err := os.ReadDir(m.conf.PathRepositories())
 34	if err != nil {
 35		m.logger.Error("can't read dir of repositories", err)
 36		return
 37	}
 38
 39	for _, repoName := range entries {
 40		m.Start(Task{
 41			call: m.checkPlugins,
 42			t: checkPluginsTaskInput{
 43				repoName:         repoName.Name(),
 44				isRootRepo:       m.conf.IsForgeRepo(repoName.Name()),
 45				availablePlugins: plugins,
 46			},
 47		})
 48	}
 49}
 50
 51func (m *Manager) checkPlugins(input interface{}) error {
 52	checkInput := input.(checkPluginsTaskInput)
 53	repoName := checkInput.repoName
 54	isRootRepo := checkInput.isRootRepo
 55	availablePlugins := checkInput.availablePlugins
 56
 57	repo, err := m.repoManager.Open(logger.AddCaller(m.ctx, "checkPlugins"), repoName)
 58	if err != nil {
 59		return oops.Wrapf(err, "can't open repo")
 60	}
 61	defer repo.Close()
 62
 63	status, err := repo.Status()
 64	if errors.Is(err, plumbing.ErrReferenceNotFound) { //repo has no head == it is empty == nothing todo
 65		return nil
 66	}
 67	if err != nil {
 68		return oops.Wrapf(err, "can't status")
 69	}
 70
 71	conf, err := repo.Configuration()
 72	if err != nil {
 73		return oops.Wrapf(err, "can't get conf")
 74	}
 75
 76	repoWriter, err := repo.WillWrite(conf.DefaultBranch)
 77	if err != nil {
 78		return oops.Wrapf(err, "can't will write")
 79	}
 80
 81	pluginsContent, err := repo.ContentPluginsConf()
 82	if err != nil {
 83		return oops.Wrapf(err, "can't get plugin conf")
 84	}
 85	plugins, err := plugin.ParsePlugins(pluginsContent, false)
 86	if err != nil {
 87		return oops.Wrapf(err, "can't parse plugin conf")
 88	}
 89
 90	usersToAdd := make([]user.SimpleUser, 0)
 91	usersToDel := make([]user.SimpleUser, 0)
 92
 93	for _, ap := range availablePlugins {
 94		found := false
 95		for i, p := range plugins {
 96			if ap.Name == p.Name {
 97				found = true
 98				plugins[i] = p.OverrideWith(ap) //replace plugin in project by fullloaded one
 99				break
100			}
101		}
102		if !found {
103			plugins = append(plugins, ap)
104		}
105		usersToAdd = append(usersToAdd, ap.Commiter().SimpleUser)
106	}
107
108	for i, p := range plugins {
109		found := false
110		for _, ap := range availablePlugins {
111			if ap.Name == p.Name {
112				found = true
113				break
114			}
115		}
116		if !found {
117			plugins = append(plugins[:i], plugins[i+1:]...)
118			usersToDel = append(usersToDel, p.Commiter().SimpleUser)
119		}
120	}
121
122	newPluginsContent, _ := m.pluginManager.ToNewRepo(isRootRepo, false, plugins)
123
124	if err := repoWriter.Write(m.conf.PathFilePlugins(), newPluginsContent); err != nil {
125		return oops.Wrapf(err, "can't write new plugin conf")
126	}
127
128	fileUsersContent, err := repo.AppendUserToGroup("owner", usersToAdd...)
129	if err != nil {
130		return oops.With("userToAdd", usersToAdd).Wrapf(err, "can't add user to users file")
131	}
132	err = repoWriter.Write(m.conf.PathFileUsers(), fileUsersContent)
133	if err != nil {
134		return oops.Wrapf(err, "can't write users file")
135	}
136
137	allowedSignersContent, err := repo.Content(m.conf.ForPathConfig("allowed_signers"))
138	if err != nil {
139		return oops.Wrapf(err, "can't read allowed_signers file")
140	}
141
142	sshString := bytes.NewBuffer(allowedSignersContent)
143	for _, u := range usersToAdd {
144		if !bytes.Contains(allowedSignersContent, []byte(u.Ssh)) {
145			sshString.WriteString(u.Email)
146			sshString.WriteString(" ")
147			sshString.WriteString(u.Ssh)
148			sshString.WriteString("\n")
149		}
150	}
151
152	if err := repoWriter.Write(m.conf.ForPathConfig("allowed_signers"), sshString.Bytes()); err != nil {
153		return oops.Wrapf(err, "can't write allowed_signers file")
154	}
155
156	// TODO delete
157
158	m.logger.Debug("sync plugins", logger.NewLoggerPair("repo", repoName))
159	newHash, err := repoWriter.CommitAll("sync plugins", m.userManager.RootCommiter())
160	if err != nil {
161		return err
162	}
163
164	m.PostPush(m.userManager.RootCommiter().SimpleUser, repoName, []*packp.Command{{Name: status.Name(), Old: status.Hash(), New: newHash}})
165
166	return nil
167}