1package config
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "os"
8 "path"
9 "path/filepath"
10 "strconv"
11
12 "git.gabrielgio.me/cerrado/pkg/u"
13 "git.sr.ht/~emersion/go-scfg"
14)
15
16var (
17 ErrScanPath = errors.New("Scan path does not exist")
18 ErrRepoPath = errors.New("Repository path does not exist")
19 ErrInvalidProperty = errors.New("Invalid property")
20)
21
22type (
23
24 // scan represents piece of the scan from the configuration file.
25 scan struct {
26 Path string
27 Public bool
28 }
29
30 // configuration represents file configuration.
31 // fields needs to be exported to cmp to work
32 configuration struct {
33 Scans []*scan
34 RootReadme string
35 ListenAddr string
36 Passphrase string
37 SyntaxHighlight string
38 AESKey string
39 Repositories []*GitRepositoryConfiguration
40 }
41
42 // This is a per repository configuration.
43 GitRepositoryConfiguration struct {
44 Name string
45 Path string
46 Description string
47 Public bool
48 About string
49 }
50
51 // ConfigurationRepository represents the configuration repository (as in
52 // database repositories).
53 // This holds all the function necessary to ask for configuration
54 // information.
55 ConfigurationRepository struct {
56 rootReadme string
57 listenAddr string
58 passphrase []byte
59 aesKey []byte
60 syntaxHighlight string
61 repositories []*GitRepositoryConfiguration
62 }
63)
64
65func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, error) {
66 f, err := os.Open(configPath)
67 if err != nil {
68 return nil, err
69 }
70
71 config, err := parse(f)
72 if err != nil {
73 return nil, err
74 }
75
76 repo := &ConfigurationRepository{
77 aesKey: []byte(config.AESKey),
78 listenAddr: config.ListenAddr,
79 passphrase: []byte(config.Passphrase),
80 repositories: config.Repositories,
81 rootReadme: config.RootReadme,
82 syntaxHighlight: config.SyntaxHighlight,
83 }
84
85 for _, scan := range config.Scans {
86 if scan.Path != "" {
87 err = repo.expandOnScanPath(scan.Path, scan.Public)
88 if err != nil {
89 return nil, err
90 }
91 }
92 }
93
94 return repo, nil
95}
96
97// GetRootReadme returns root read path
98func (c *ConfigurationRepository) GetRootReadme() string {
99 return c.rootReadme
100}
101
102func (c *ConfigurationRepository) GetSyntaxHighlight() string {
103 return c.syntaxHighlight
104}
105
106func (c *ConfigurationRepository) GetListenAddr() string {
107 return c.listenAddr
108}
109
110func (c *ConfigurationRepository) GetPassphrase() []byte {
111 return c.passphrase
112}
113
114func (c *ConfigurationRepository) GetBase64AesKey() []byte {
115 return c.aesKey
116}
117
118func (c *ConfigurationRepository) IsAuthEnabled() bool {
119 return len(c.passphrase) != 0
120}
121
122// GetByName returns configuration of repository for a given name.
123// It returns nil if there is not match for it.
124func (c *ConfigurationRepository) GetByName(name string) *GitRepositoryConfiguration {
125 for _, r := range c.repositories {
126 if r.Name == name {
127 return r
128 }
129 }
130 return nil
131}
132
133// List returns all the configuration for all repositories.
134func (c *ConfigurationRepository) List() []*GitRepositoryConfiguration {
135 return c.repositories
136}
137
138// expandOnScanPath scans the scanPath for folders taking them as repositories
139// and applying them default configuration.
140func (c *ConfigurationRepository) expandOnScanPath(scanPath string, public bool) error {
141 if !u.FileExist(scanPath) {
142 return ErrScanPath
143 }
144
145 entries, err := os.ReadDir(scanPath)
146 if err != nil {
147 return err
148 }
149
150 for _, e := range entries {
151 if !e.IsDir() {
152 continue
153 }
154
155 fullPath := path.Join(scanPath, e.Name())
156 if !c.repoExits(fullPath) {
157 c.repositories = append(c.repositories, &GitRepositoryConfiguration{
158 Name: e.Name(),
159 Path: fullPath,
160 Public: public,
161 })
162 }
163 }
164 return nil
165}
166
167func (c *ConfigurationRepository) repoExits(path string) bool {
168 for _, r := range c.repositories {
169 if path == r.Path {
170 return true
171 }
172 }
173 return false
174}
175
176func parse(r io.Reader) (*configuration, error) {
177 block, err := scfg.Read(r)
178 if err != nil {
179 return nil, err
180 }
181
182 config := defaultConfiguration()
183
184 err = setScan(block, &config.Scans)
185 if err != nil {
186 return nil, err
187 }
188
189 err = setRootReadme(block, &config.RootReadme)
190 if err != nil {
191 return nil, err
192 }
193
194 err = setListenAddr(block, &config.ListenAddr)
195 if err != nil {
196 return nil, err
197 }
198
199 err = setPassphrase(block, &config.Passphrase)
200 if err != nil {
201 return nil, err
202 }
203
204 err = setAESKey(block, &config.AESKey)
205 if err != nil {
206 return nil, err
207 }
208
209 err = setSyntaxHighlight(block, &config.SyntaxHighlight)
210 if err != nil {
211 return nil, err
212 }
213
214 err = setRepositories(block, &config.Repositories)
215 if err != nil {
216 return nil, err
217 }
218
219 return config, nil
220}
221
222func setRepositories(block scfg.Block, repositories *[]*GitRepositoryConfiguration) error {
223 blocks := block.GetAll("repository")
224
225 for _, r := range blocks {
226 if len(r.Params) != 1 {
227 return fmt.Errorf(
228 "Invlid number of params for repository: %w",
229 ErrInvalidProperty,
230 )
231 }
232
233 path := u.FirstOrZero(r.Params)
234 repository := defaultRepisotryConfiguration(path)
235
236 for _, d := range r.Children {
237 // under repository there is only single param properties
238 if len(d.Params) != 1 {
239 return fmt.Errorf(
240 "Invlid number of params for %s: %w",
241 d.Name,
242 ErrInvalidProperty,
243 )
244 }
245
246 switch d.Name {
247 case "name":
248 if err := setString(d, &repository.Name); err != nil {
249 return err
250 }
251 case "description":
252 if err := setString(d, &repository.Description); err != nil {
253 return err
254 }
255 case "public":
256 if err := setBool(d, &repository.Public); err != nil {
257 return err
258 }
259 case "about":
260 if err := setString(d, &repository.About); err != nil {
261 return err
262 }
263 }
264 }
265
266 *repositories = append(*repositories, repository)
267 }
268
269 return nil
270}
271
272func defaultConfiguration() *configuration {
273 return &configuration{
274 Scans: defaultScans(),
275 RootReadme: "",
276 ListenAddr: defaultAddr(),
277 Repositories: make([]*GitRepositoryConfiguration, 0),
278 }
279}
280
281func defaultScans() []*scan {
282 return []*scan{}
283}
284
285func defaultAddr() string {
286 return "tcp://localhost:8080"
287}
288
289func defaultRepisotryConfiguration(path string) *GitRepositoryConfiguration {
290 return &GitRepositoryConfiguration{
291 Path: path,
292 Name: filepath.Base(path),
293 Description: "",
294 Public: false,
295 About: "README.md",
296 }
297}
298
299func setRootReadme(block scfg.Block, readme *string) error {
300 scanDir := block.Get("root-readme")
301 return setString(scanDir, readme)
302}
303
304func setPassphrase(block scfg.Block, listenAddr *string) error {
305 scanDir := block.Get("passphrase")
306 return setString(scanDir, listenAddr)
307}
308
309func setAESKey(block scfg.Block, listenAddr *string) error {
310 scanDir := block.Get("aes-key")
311 return setString(scanDir, listenAddr)
312}
313
314func setSyntaxHighlight(block scfg.Block, listenAddr *string) error {
315 scanDir := block.Get("syntax-highlight")
316 return setString(scanDir, listenAddr)
317}
318
319func setListenAddr(block scfg.Block, listenAddr *string) error {
320 scanDir := block.Get("listen-addr")
321 return setString(scanDir, listenAddr)
322}
323
324func setScan(block scfg.Block, scans *[]*scan) error {
325 for _, scanDir := range block.GetAll("scan") {
326 s := &scan{}
327 if scanDir == nil {
328 return nil
329 }
330 err := setString(scanDir, &s.Path)
331 if err != nil {
332 return err
333 }
334
335 public := scanDir.Children.Get("public")
336 err = setBool(public, &s.Public)
337 if err != nil {
338 return err
339 }
340
341 *scans = append(*scans, s)
342 }
343
344 return nil
345}
346
347func setBool(dir *scfg.Directive, field *bool) error {
348 if dir != nil {
349
350 p1, _ := u.First(dir.Params)
351 v, err := strconv.ParseBool(p1)
352 if err != nil {
353 return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err)
354 }
355 *field = v
356 }
357 return nil
358}
359
360func setString(dir *scfg.Directive, field *string) error {
361 if dir != nil {
362 *field = u.FirstOrZero(dir.Params)
363 }
364 return nil
365}