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}