cerrado @ dcc9a54fc8a1c52bb8cded28c4f445248bc597a6

  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}