cerrado @ 058274fcc304279b1f4fe5effb683bee1a67f494

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