github.com/adacta-ru/mattermost-server/v5@v5.31.1/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/v5/mlog"
    13  	"github.com/adacta-ru/mattermost-server/v5/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  }