factory.go 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package bbolt
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "os"
  8. "path/filepath"
  9. "github.com/TecharoHQ/anubis/lib/store"
  10. "go.etcd.io/bbolt"
  11. )
  12. var (
  13. ErrMissingPath = errors.New("bbolt: path is missing from config")
  14. ErrCantWriteToPath = errors.New("bbolt: can't write to path")
  15. )
  16. func init() {
  17. store.Register("bbolt", Factory{})
  18. }
  19. // Factory builds new instances of the bbolt storage backend according to
  20. // configuration passed via a json.RawMessage.
  21. type Factory struct{}
  22. // Build parses and validates the bbolt storage backend Config and creates
  23. // a new instance of it.
  24. func (Factory) Build(ctx context.Context, data json.RawMessage) (store.Interface, error) {
  25. var config Config
  26. if err := json.Unmarshal([]byte(data), &config); err != nil {
  27. return nil, fmt.Errorf("%w: %w", store.ErrBadConfig, err)
  28. }
  29. if err := config.Valid(); err != nil {
  30. return nil, fmt.Errorf("%w: %w", store.ErrBadConfig, err)
  31. }
  32. bdb, err := bbolt.Open(config.Path, 0600, nil)
  33. if err != nil {
  34. return nil, fmt.Errorf("can't open bbolt database %s: %w", config.Path, err)
  35. }
  36. result := &Store{
  37. bdb: bdb,
  38. }
  39. go result.cleanupThread(ctx)
  40. return result, nil
  41. }
  42. // Valid parses and validates the bbolt store Config or returns
  43. // an error.
  44. func (Factory) Valid(data json.RawMessage) error {
  45. var config Config
  46. if err := json.Unmarshal([]byte(data), &config); err != nil {
  47. return fmt.Errorf("%w: %w", store.ErrBadConfig, err)
  48. }
  49. if err := config.Valid(); err != nil {
  50. return fmt.Errorf("%w: %w", store.ErrBadConfig, err)
  51. }
  52. return nil
  53. }
  54. // Config is the bbolt storage backend configuration.
  55. type Config struct {
  56. // Path is the filesystem path of the database. The folder must be writable to Anubis.
  57. Path string `json:"path"`
  58. }
  59. // Valid validates the configuration including checking if its containing folder is writable.
  60. func (c Config) Valid() error {
  61. var errs []error
  62. if c.Path == "" {
  63. errs = append(errs, ErrMissingPath)
  64. } else {
  65. dir := filepath.Dir(c.Path)
  66. if err := os.WriteFile(filepath.Join(dir, ".test-file"), []byte(""), 0600); err != nil {
  67. errs = append(errs, ErrCantWriteToPath)
  68. }
  69. os.Remove(filepath.Join(dir, ".test-file"))
  70. }
  71. if len(errs) != 0 {
  72. return errors.Join(errs...)
  73. }
  74. return nil
  75. }