-
-
Notifications
You must be signed in to change notification settings - Fork 133
/
feature_flag.go
169 lines (147 loc) · 4.71 KB
/
feature_flag.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
161
162
163
164
165
166
167
168
169
package ffclient
import (
"fmt"
"log"
"sync"
"time"
"github.com/thomaspoignant/go-feature-flag/notifier/logsnotifier"
"github.com/thomaspoignant/go-feature-flag/internal/cache"
"github.com/thomaspoignant/go-feature-flag/internal/dataexporter"
"github.com/thomaspoignant/go-feature-flag/internal/fflog"
)
// Init the feature flag component with the configuration of ffclient.Config
//
// func main() {
// err := ffclient.Init(ffclient.Config{
// PollingInterval: 3 * time.Second,
// Retriever: &httpretriever.Retriever{
// URL: "http://example.com/flag-config.yaml",
// },
// })
// defer ffclient.Close()
func Init(config Config) error {
var err error
onceFF.Do(func() {
ff, err = New(config)
})
return err
}
// Close the component by stopping the background refresh and clean the cache.
func Close() {
ff.Close()
}
// GoFeatureFlag is the main object of the library
// it contains the cache, the config and the update.
type GoFeatureFlag struct {
cache cache.Manager
config Config
bgUpdater backgroundUpdater
dataExporter *dataexporter.Scheduler
}
// ff is the default object for go-feature-flag
var ff *GoFeatureFlag
var onceFF sync.Once
// New creates a new go-feature-flag instance that retrieve the config from a YAML file
// and return everything you need to manage your flags.
func New(config Config) (*GoFeatureFlag, error) {
switch {
case config.PollingInterval == 0:
// The default value for poll interval is 60 seconds
config.PollingInterval = 60 * time.Second
case config.PollingInterval < 0:
// Check that value is not negative
return nil, fmt.Errorf("%d is not a valid PollingInterval value, it need to be > 0", config.PollingInterval)
case config.PollingInterval < time.Second:
// the minimum value for the polling policy is 1 second
config.PollingInterval = time.Second
default:
// do nothing
}
goFF := &GoFeatureFlag{
config: config,
}
if !config.Offline {
notifiers := config.Notifiers
if config.Logger != nil {
notifiers = append(notifiers, &logsnotifier.Notifier{Logger: config.Logger})
}
notificationService := cache.NewNotificationService(notifiers)
goFF.bgUpdater = newBackgroundUpdater(config.PollingInterval)
goFF.cache = cache.New(notificationService)
err := retrieveFlagsAndUpdateCache(goFF.config, goFF.cache)
if err != nil && !config.StartWithRetrieverError {
return nil, fmt.Errorf("impossible to retrieve the flags, please check your configuration: %v", err)
}
go goFF.startFlagUpdaterDaemon()
if goFF.config.DataExporter.Exporter != nil {
// init the data exporter
goFF.dataExporter = dataexporter.NewScheduler(goFF.config.Context, goFF.config.DataExporter.FlushInterval,
goFF.config.DataExporter.MaxEventInMemory, goFF.config.DataExporter.Exporter, goFF.config.Logger)
// we start the daemon only if we have a bulk exporter
if goFF.config.DataExporter.Exporter.IsBulk() {
go goFF.dataExporter.StartDaemon()
}
}
}
return goFF, nil
}
// Close wait until thread are done
func (g *GoFeatureFlag) Close() {
onceFF = sync.Once{}
if g != nil {
if g.cache != nil {
// clear the cache
g.cache.Close()
}
if g.bgUpdater.updaterChan != nil && g.bgUpdater.ticker != nil {
g.bgUpdater.close()
}
if g.dataExporter != nil {
g.dataExporter.Close()
}
}
}
// startFlagUpdaterDaemon is the daemon that refresh the cache every X seconds.
func (g *GoFeatureFlag) startFlagUpdaterDaemon() {
for {
select {
case <-g.bgUpdater.ticker.C:
err := retrieveFlagsAndUpdateCache(g.config, g.cache)
if err != nil {
fflog.Printf(g.config.Logger, "error while updating the cache: %v\n", err)
}
case <-g.bgUpdater.updaterChan:
return
}
}
}
// retrieveFlagsAndUpdateCache is called every X seconds to refresh the cache flag.
func retrieveFlagsAndUpdateCache(config Config, cache cache.Manager) error {
retriever, err := config.GetRetriever()
if err != nil {
log.Printf("error while getting the file retriever: %v", err)
return err
}
loadedFlags, err := retriever.Retrieve(config.Context)
if err != nil {
log.Printf("error: impossible to retrieve flags from the config file: %v", err)
return err
}
err = cache.UpdateCache(loadedFlags, config.FileFormat, config.Logger)
if err != nil {
log.Printf("error: impossible to update the cache of the flags: %v", err)
return err
}
return nil
}
// GetCacheRefreshDate gives the date of the latest refresh of the cache
func (g *GoFeatureFlag) GetCacheRefreshDate() time.Time {
if g.config.Offline {
return time.Time{}
}
return g.cache.GetLatestUpdateDate()
}
// GetCacheRefreshDate gives the date of the latest refresh of the cache
func GetCacheRefreshDate() time.Time {
return ff.GetCacheRefreshDate()
}