-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathstorage.go
160 lines (130 loc) · 3.51 KB
/
storage.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package configuration
import (
"context"
"encoding/json"
"os"
"path/filepath"
"sync"
"time"
"github.com/gofrs/flock"
"github.com/snyk/go-application-framework/internal/utils"
)
//go:generate $GOPATH/bin/mockgen -source=storage.go -destination ../mocks/config_storage.go -package mocks -self_package github.com/snyk/go-application-framework/pkg/configuration/
type Storage interface {
Set(key string, value any) error
Refresh(config Configuration, key string) error
Lock(ctx context.Context, retryDelay time.Duration) error
Unlock() error
}
type EmptyStorage struct{}
func (*EmptyStorage) Set(string, any) error {
return nil
}
func (*EmptyStorage) Refresh(Configuration, string) error {
return nil
}
func (*EmptyStorage) Lock(context.Context, time.Duration) error {
return nil
}
func (*EmptyStorage) Unlock() error {
return nil
}
// keyDeleted is a marker value which, when set, causes a key to be deleted from
// stored configuration.
var keyDeleted = struct{}{}
type JsonStorage struct {
path string
config Configuration
fileLock *flock.Flock
mutex sync.Mutex
}
type JsonOption func(*JsonStorage)
func WithConfiguration(c Configuration) JsonOption {
return func(storage *JsonStorage) {
storage.config = c
}
}
func NewJsonStorage(path string, options ...JsonOption) *JsonStorage {
storage := &JsonStorage{
path: path,
fileLock: flock.New(path + ".lock"),
}
for _, opt := range options {
opt(storage)
}
return storage
}
// This function deals with the fact that not every key can or shall be written to the config. Keys that belong to
// Environment Variables need to be matched to their alternative names in the config.
// For example "SNYK_TOKEN" in the config file would be "api"
// The logic should in the future be moved closer to the configuration as it might be needed there as well.
func (s *JsonStorage) getNonEnvVarKey(key string) string {
if s.config == nil {
return ""
}
keys := []string{key}
keys = append(keys, s.config.GetAlternativeKeys(key)...)
for _, k := range keys {
if s.config.GetKeyType(k) != EnvVarKeyType {
return k
}
}
return ""
}
func (s *JsonStorage) Set(key string, value any) error {
s.mutex.Lock()
defer s.mutex.Unlock()
// Check if path to file exists
err := os.MkdirAll(filepath.Dir(s.path), utils.FILEPERM_755)
if err != nil {
return err
}
fileBytes, err := os.ReadFile(s.path)
if len(fileBytes) == 0 || err != nil {
const emptyJson = "{}"
fileBytes = []byte(emptyJson)
}
config := make(map[string]any)
err = json.Unmarshal(fileBytes, &config)
if err != nil {
return err
}
if tmpKey := s.getNonEnvVarKey(key); len(tmpKey) > 0 {
key = tmpKey
}
if _, ok := value.(struct{}); ok {
// See implementation of Configuration.Unset; when marker value is set,
// key is deleted from config before writing.
delete(config, key)
} else {
config[key] = value
}
configJson, err := json.Marshal(config)
if err != nil {
return err
}
err = os.WriteFile(s.path, configJson, utils.FILEPERM_666)
return err
}
func (s *JsonStorage) Refresh(config Configuration, key string) error {
contents, err := os.ReadFile(s.path)
if err != nil {
return err
}
doc := map[string]interface{}{}
err = json.Unmarshal(contents, &doc)
if err != nil {
return err
}
if value, ok := doc[key]; ok {
config.Set(key, value)
}
return nil
}
func (s *JsonStorage) Lock(ctx context.Context, retryDelay time.Duration) error {
_, err := s.fileLock.TryLockContext(ctx, retryDelay)
return err
}
func (s *JsonStorage) Unlock() error {
return s.fileLock.Unlock()
}