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  }