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).UpdateVersion(ap.Version) //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 if len(availablePlugins) > 0 {
109 for i, p := range plugins {
110 found := false
111 for _, ap := range availablePlugins {
112 if ap.Name == p.Name {
113 found = true
114 break
115 }
116 }
117 if !found {
118 plugins = append(plugins[:i], plugins[i+1:]...)
119 if p.Commiter() != nil {
120 usersToDel = append(usersToDel, p.Commiter().SimpleUser)
121 }
122 }
123 }
124 }
125
126 newPluginsContent, _ := m.pluginManager.ToNewRepo(isRootRepo, false, plugins)
127
128 if err := repoWriter.Write(m.conf.PathFilePlugins(), newPluginsContent); err != nil {
129 return oops.Wrapf(err, "can't write new plugin conf")
130 }
131
132 fileUsersContent, err := repo.AppendUserToGroup("owner", usersToAdd...)
133 if err != nil {
134 return oops.With("userToAdd", usersToAdd).Wrapf(err, "can't add user to users file")
135 }
136 err = repoWriter.Write(m.conf.PathFileUsers(), fileUsersContent)
137 if err != nil {
138 return oops.Wrapf(err, "can't write users file")
139 }
140
141 allowedSignersContent, err := repo.Content(m.conf.ForPathConfig("allowed_signers"))
142 if err != nil {
143 return oops.Wrapf(err, "can't read allowed_signers file")
144 }
145
146 sshString := bytes.NewBuffer(allowedSignersContent)
147 for _, u := range usersToAdd {
148 if !bytes.Contains(allowedSignersContent, []byte(u.Ssh)) {
149 sshString.WriteString(u.Email)
150 sshString.WriteString(" ")
151 sshString.WriteString(u.Ssh)
152 sshString.WriteString("\n")
153 }
154 }
155
156 if err := repoWriter.Write(m.conf.ForPathConfig("allowed_signers"), sshString.Bytes()); err != nil {
157 return oops.Wrapf(err, "can't write allowed_signers file")
158 }
159
160 // TODO delete
161
162 m.logger.Debug("sync plugins", logger.NewLoggerPair("repo", repoName))
163 newHash, err := repoWriter.CommitAll("sync plugins", m.userManager.RootCommiter())
164 if err != nil {
165 return err
166 }
167
168 if newHash == plumbing.ZeroHash { //nothing change == nothing todo
169 return nil
170 }
171
172 m.PostPush(m.userManager.RootCommiter().SimpleUser, repoName, []*packp.Command{{Name: status.Name(), Old: status.Hash(), New: newHash}})
173
174 return nil
175}