diff --git a/config/feature_flags.go b/config/feature_flags.go index bfc93e9893..235a95eb6a 100644 --- a/config/feature_flags.go +++ b/config/feature_flags.go @@ -6,6 +6,8 @@ package config import ( "math" "reflect" + "strconv" + "strings" "github.com/mattermost/mattermost-server/v5/mlog" "github.com/mattermost/mattermost-server/v5/model" @@ -73,7 +75,8 @@ func (f *FeatureFlagSynchronizer) Close() { // featureFlagsFromMap sets the feature flags from a map[string]string. // It starts with baseFeatureFlags and only sets values that are // given by the upstream management system. -// Makes the assumption that all feature flags are strings for now. +// Makes the assumption that all feature flags are strings or booleans. +// 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. func featureFlagsFromMap(featuresMap map[string]string, baseFeatureFlags model.FeatureFlags) model.FeatureFlags { refStruct := reflect.ValueOf(&baseFeatureFlags).Elem() for fieldName, fieldValue := range featuresMap { @@ -83,13 +86,20 @@ func featureFlagsFromMap(featuresMap map[string]string, baseFeatureFlags model.F continue } - refField.Set(reflect.ValueOf(fieldValue)) + switch refField.Type().Kind() { + case reflect.Bool: + parsedBoolValue, _ := strconv.ParseBool(fieldValue) + refField.Set(reflect.ValueOf(strings.ToLower(fieldValue) == "on" || parsedBoolValue)) + default: + refField.Set(reflect.ValueOf(fieldValue)) + } + } return baseFeatureFlags } // featureFlagsToMap returns the feature flags as a map[string]string -// Currently assumes that all feature flags are strings for now. +// Supports boolean and string feature flags. func featureFlagsToMap(featureFlags *model.FeatureFlags) map[string]string { refStructVal := reflect.ValueOf(*featureFlags) refStructType := reflect.TypeOf(*featureFlags) @@ -100,7 +110,12 @@ func featureFlagsToMap(featureFlags *model.FeatureFlags) map[string]string { if !refFieldVal.IsValid() { continue } - ret[refFieldType.Name] = refFieldVal.String() + switch refFieldType.Type.Kind() { + case reflect.Bool: + ret[refFieldType.Name] = strconv.FormatBool(refFieldVal.Bool()) + default: + ret[refFieldType.Name] = refFieldVal.String() + } } return ret diff --git a/config/feature_flags_test.go b/config/feature_flags_test.go index 3acf2372e9..8d5fe141b6 100644 --- a/config/feature_flags_test.go +++ b/config/feature_flags_test.go @@ -67,6 +67,41 @@ func TestFeatureFlagsFromMap(t *testing.T) { Base: model.FeatureFlags{}, ExpectedTestValue: "", }, + "bool on": { + FeatureMap: map[string]string{"TestBoolFeature": "on"}, + Base: model.FeatureFlags{TestBoolFeature: true}, + ExpectedTestValue: "", + }, + "bool true": { + FeatureMap: map[string]string{"TestBoolFeature": "true"}, + Base: model.FeatureFlags{TestBoolFeature: true}, + ExpectedTestValue: "", + }, + "bool True": { + FeatureMap: map[string]string{"TestBoolFeature": "True"}, + Base: model.FeatureFlags{TestBoolFeature: true}, + ExpectedTestValue: "", + }, + "bool 1": { + FeatureMap: map[string]string{"TestBoolFeature": "1"}, + Base: model.FeatureFlags{TestBoolFeature: true}, + ExpectedTestValue: "", + }, + "bool off": { + FeatureMap: map[string]string{"TestBoolFeature": "off"}, + Base: model.FeatureFlags{}, + ExpectedTestValue: "", + }, + "bool false": { + FeatureMap: map[string]string{"TestBoolFeature": "false"}, + Base: model.FeatureFlags{}, + ExpectedTestValue: "", + }, + "bool other value": { + FeatureMap: map[string]string{"TestBoolFeature": "someotherbadvalue"}, + Base: model.FeatureFlags{}, + ExpectedTestValue: "", + }, } { t.Run(name, func(t *testing.T) { require.Equal(t, tc.ExpectedTestValue, featureFlagsFromMap(tc.FeatureMap, tc.Base).TestFeature) @@ -97,3 +132,23 @@ func TestFeatureFlagsToMap(t *testing.T) { }) } } + +func TestFeatureFlagsToMapBool(t *testing.T) { + for name, tc := range map[string]struct { + Flags model.FeatureFlags + TestFeatureValue string + }{ + "false": { + TestFeatureValue: "false", + Flags: model.FeatureFlags{}, + }, + "true": { + TestFeatureValue: "true", + Flags: model.FeatureFlags{TestBoolFeature: true}, + }, + } { + t.Run(name, func(t *testing.T) { + require.Equal(t, tc.TestFeatureValue, featureFlagsToMap(&tc.Flags)["TestBoolFeature"]) + }) + } +} diff --git a/model/feature_flags.go b/model/feature_flags.go index 316c7ffb9a..e260c877be 100644 --- a/model/feature_flags.go +++ b/model/feature_flags.go @@ -7,6 +7,9 @@ type FeatureFlags struct { // Exists only for unit and manual testing. // When set to a value, will be returned by the ping endpoint. TestFeature string + // Exists only for testing bool functionality. Boolean feature flags interprate "on" or "true" as true and + // all other values as false. + TestBoolFeature bool // Toggle on and off scheduled jobs for cloud user limit emails see MM-29999 CloudDelinquentEmailJobsEnabled bool @@ -14,5 +17,6 @@ type FeatureFlags struct { func (f *FeatureFlags) SetDefaults() { f.TestFeature = "off" + f.TestBoolFeature = false f.CloudDelinquentEmailJobsEnabled = false }