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