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