cerrado @ c6bdde9c63758cb9b61c7a5559dd30d141aee289

  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		Scan            *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	if config.Scan.Path != "" {
 86		err = repo.expandOnScanPath(config.Scan.Path, config.Scan.Public)
 87		if err != nil {
 88			return nil, err
 89		}
 90	}
 91
 92	return repo, nil
 93}
 94
 95// GetRootReadme returns root read path
 96func (c *ConfigurationRepository) GetRootReadme() string {
 97	return c.rootReadme
 98}
 99
100func (c *ConfigurationRepository) GetSyntaxHighlight() string {
101	return c.syntaxHighlight
102}
103
104func (c *ConfigurationRepository) GetListenAddr() string {
105	return c.listenAddr
106}
107
108func (c *ConfigurationRepository) GetPassphrase() []byte {
109	return c.passphrase
110}
111
112func (c *ConfigurationRepository) GetBase64AesKey() []byte {
113	return c.aesKey
114}
115
116func (c *ConfigurationRepository) IsAuthEnabled() bool {
117	return len(c.passphrase) != 0
118}
119
120// GetByName returns configuration of repository for a given name.
121// It returns nil if there is not match for it.
122func (c *ConfigurationRepository) GetByName(name string) *GitRepositoryConfiguration {
123	for _, r := range c.repositories {
124		if r.Name == name {
125			return r
126		}
127	}
128	return nil
129}
130
131// List returns all the configuration for all repositories.
132func (c *ConfigurationRepository) List() []*GitRepositoryConfiguration {
133	return c.repositories
134}
135
136// expandOnScanPath scans the scanPath for folders taking them as repositories
137// and applying them default configuration.
138func (c *ConfigurationRepository) expandOnScanPath(scanPath string, public bool) error {
139	if !u.FileExist(scanPath) {
140		return ErrScanPath
141	}
142
143	entries, err := os.ReadDir(scanPath)
144	if err != nil {
145		return err
146	}
147
148	for _, e := range entries {
149		if !e.IsDir() {
150			continue
151		}
152
153		fullPath := path.Join(scanPath, e.Name())
154		if !c.repoExits(fullPath) {
155			c.repositories = append(c.repositories, &GitRepositoryConfiguration{
156				Name:   e.Name(),
157				Path:   fullPath,
158				Public: public,
159			})
160		}
161	}
162	return nil
163}
164
165func (c *ConfigurationRepository) repoExits(path string) bool {
166	for _, r := range c.repositories {
167		if path == r.Path {
168			return true
169		}
170	}
171	return false
172}
173
174func parse(r io.Reader) (*configuration, error) {
175	block, err := scfg.Read(r)
176	if err != nil {
177		return nil, err
178	}
179
180	config := defaultConfiguration()
181
182	err = setScan(block, config.Scan)
183	if err != nil {
184		return nil, err
185	}
186
187	err = setRootReadme(block, &config.RootReadme)
188	if err != nil {
189		return nil, err
190	}
191
192	err = setListenAddr(block, &config.ListenAddr)
193	if err != nil {
194		return nil, err
195	}
196
197	err = setPassphrase(block, &config.Passphrase)
198	if err != nil {
199		return nil, err
200	}
201
202	err = setAESKey(block, &config.AESKey)
203	if err != nil {
204		return nil, err
205	}
206
207	err = setSyntaxHighlight(block, &config.SyntaxHighlight)
208	if err != nil {
209		return nil, err
210	}
211
212	err = setRepositories(block, &config.Repositories)
213	if err != nil {
214		return nil, err
215	}
216
217	return config, nil
218}
219
220func setRepositories(block scfg.Block, repositories *[]*GitRepositoryConfiguration) error {
221	blocks := block.GetAll("repository")
222
223	for _, r := range blocks {
224		if len(r.Params) != 1 {
225			return fmt.Errorf(
226				"Invlid number of params for repository: %w",
227				ErrInvalidProperty,
228			)
229		}
230
231		path := u.FirstOrZero(r.Params)
232		repository := defaultRepisotryConfiguration(path)
233
234		for _, d := range r.Children {
235			// under repository there is only single param properties
236			if len(d.Params) != 1 {
237				return fmt.Errorf(
238					"Invlid number of params for %s: %w",
239					d.Name,
240					ErrInvalidProperty,
241				)
242			}
243
244			switch d.Name {
245			case "name":
246				if err := setString(d, &repository.Name); err != nil {
247					return err
248				}
249			case "description":
250				if err := setString(d, &repository.Description); err != nil {
251					return err
252				}
253			case "public":
254				if err := setBool(d, &repository.Public); err != nil {
255					return err
256				}
257			case "about":
258				if err := setString(d, &repository.About); err != nil {
259					return err
260				}
261			}
262		}
263
264		*repositories = append(*repositories, repository)
265	}
266
267	return nil
268}
269
270func defaultConfiguration() *configuration {
271	return &configuration{
272		Scan:         defaultScan(),
273		RootReadme:   "",
274		ListenAddr:   defaultAddr(),
275		Repositories: make([]*GitRepositoryConfiguration, 0),
276	}
277}
278
279func defaultScan() *scan {
280	return &scan{
281		Public: false,
282		Path:   "",
283	}
284}
285
286func defaultAddr() string {
287	return "tcp://localhost:8080"
288}
289
290func defaultRepisotryConfiguration(path string) *GitRepositoryConfiguration {
291	return &GitRepositoryConfiguration{
292		Path:        path,
293		Name:        filepath.Base(path),
294		Description: "",
295		Public:      false,
296		About:       "README.md",
297	}
298}
299
300func setRootReadme(block scfg.Block, readme *string) error {
301	scanDir := block.Get("root-readme")
302	return setString(scanDir, readme)
303}
304
305func setPassphrase(block scfg.Block, listenAddr *string) error {
306	scanDir := block.Get("passphrase")
307	return setString(scanDir, listenAddr)
308}
309
310func setAESKey(block scfg.Block, listenAddr *string) error {
311	scanDir := block.Get("aes-key")
312	return setString(scanDir, listenAddr)
313}
314
315func setSyntaxHighlight(block scfg.Block, listenAddr *string) error {
316	scanDir := block.Get("syntax-highlight")
317	return setString(scanDir, listenAddr)
318}
319
320func setListenAddr(block scfg.Block, listenAddr *string) error {
321	scanDir := block.Get("listen-addr")
322	return setString(scanDir, listenAddr)
323}
324
325func setScan(block scfg.Block, scan *scan) error {
326	scanDir := block.Get("scan")
327	if scanDir == nil {
328		return nil
329	}
330	err := setString(scanDir, &scan.Path)
331	if err != nil {
332		return err
333	}
334
335	public := scanDir.Children.Get("public")
336	return setBool(public, &scan.Public)
337}
338
339func setBool(dir *scfg.Directive, field *bool) error {
340	if dir != nil {
341
342		p1, _ := u.First(dir.Params)
343		v, err := strconv.ParseBool(p1)
344		if err != nil {
345			return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err)
346		}
347		*field = v
348	}
349	return nil
350}
351
352func setString(dir *scfg.Directive, field *string) error {
353	if dir != nil {
354		*field = u.FirstOrZero(dir.Params)
355	}
356	return nil
357}