github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/app/featureflag/feature_flags_sync.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package featureflag 5 6 import ( 7 "math" 8 "reflect" 9 "strconv" 10 "strings" 11 12 "github.com/pkg/errors" 13 "github.com/splitio/go-client/v6/splitio/client" 14 "github.com/splitio/go-client/v6/splitio/conf" 15 16 "github.com/masterhung0112/hk_server/v5/model" 17 "github.com/masterhung0112/hk_server/v5/shared/mlog" 18 ) 19 20 type SyncParams struct { 21 ServerID string 22 SplitKey string 23 SyncIntervalSeconds int 24 Log *mlog.Logger 25 Attributes map[string]interface{} 26 } 27 28 type Synchronizer struct { 29 SyncParams 30 31 client *client.SplitClient 32 stop chan struct{} 33 stopped chan struct{} 34 } 35 36 var featureNames = getStructFields(model.FeatureFlags{}) 37 38 func NewSynchronizer(params SyncParams) (*Synchronizer, error) { 39 cfg := conf.Default() 40 if params.Log != nil { 41 cfg.Logger = &splitLogger{wrappedLog: params.Log.With(mlog.String("service", "split"))} 42 } else { 43 cfg.LoggerConfig.LogLevel = math.MinInt32 44 } 45 factory, err := client.NewSplitFactory(params.SplitKey, cfg) 46 if err != nil { 47 return nil, errors.Wrap(err, "unable to create split factory") 48 } 49 50 return &Synchronizer{ 51 SyncParams: params, 52 client: factory.Client(), 53 stop: make(chan struct{}), 54 stopped: make(chan struct{}), 55 }, nil 56 } 57 58 // EnsureReady blocks until the syncronizer is ready to update feature flag values 59 func (f *Synchronizer) EnsureReady() error { 60 if err := f.client.BlockUntilReady(10); err != nil { 61 return errors.Wrap(err, "split.io client could not initialize") 62 } 63 64 return nil 65 } 66 67 func (f *Synchronizer) UpdateFeatureFlagValues(base model.FeatureFlags) model.FeatureFlags { 68 featuresMap := f.client.Treatments(f.ServerID, featureNames, f.Attributes) 69 ffm := featureFlagsFromMap(featuresMap, base) 70 return ffm 71 } 72 73 func (f *Synchronizer) Close() { 74 f.client.Destroy() 75 } 76 77 // featureFlagsFromMap sets the feature flags from a map[string]string. 78 // It starts with baseFeatureFlags and only sets values that are 79 // given by the upstream management system. 80 // Makes the assumption that all feature flags are strings or booleans. 81 // Strings are converted to booleans by considering case insensitive "on" or any value considered by strconv.ParseBool as true and any other value as false. 82 func featureFlagsFromMap(featuresMap map[string]string, baseFeatureFlags model.FeatureFlags) model.FeatureFlags { 83 refStruct := reflect.ValueOf(&baseFeatureFlags).Elem() 84 for fieldName, fieldValue := range featuresMap { 85 refField := refStruct.FieldByName(fieldName) 86 // "control" is returned by split.io if the treatment is not found, in this case we should use the default value. 87 if !refField.IsValid() || !refField.CanSet() || fieldValue == "control" { 88 continue 89 } 90 91 switch refField.Type().Kind() { 92 case reflect.Bool: 93 parsedBoolValue, _ := strconv.ParseBool(fieldValue) 94 refField.Set(reflect.ValueOf(strings.ToLower(fieldValue) == "on" || parsedBoolValue)) 95 default: 96 refField.Set(reflect.ValueOf(fieldValue)) 97 } 98 99 } 100 return baseFeatureFlags 101 } 102 103 func getStructFields(s interface{}) []string { 104 structType := reflect.TypeOf(s) 105 fieldNames := make([]string, 0, structType.NumField()) 106 for i := 0; i < structType.NumField(); i++ { 107 fieldNames = append(fieldNames, structType.Field(i).Name) 108 } 109 110 return fieldNames 111 }