diff --git a/README.md b/README.md
index 705a20e8147..6b5fd6288b3 100644
--- a/README.md
+++ b/README.md
@@ -118,6 +118,7 @@ ffclient.Init(ffclient.Config{
},
},
StartWithRetrieverError: false,
+ Environment: os.Getenv("MYAPP_ENV"),
})
```
### Configuration fields
@@ -126,6 +127,7 @@ ffclient.Init(ffclient.Config{
|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Retriever` | The configuration retriever you want to use to get your flag file.
*See [Store your flag file](https://thomaspoignant.github.io/go-feature-flag/latest/flag_file/) for the configuration details*. |
| `Context` | *(optional)*
The context used by the retriever.
Default: `context.Background()` |
+| `Environment` | *(optional)*
The environment the app is running under, can be checked in feature flag rules.
Default: `""` |
| `DataExporter` | *(optional)*
DataExporter defines how to export data on how your flags are used.
*see [export data section](https://thomaspoignant.github.io/go-feature-flag/latest/data_collection/) for more details*. |
| `FileFormat` | *(optional)*
Format of your configuration file. Available formats are `yaml`, `toml` and `json`, if you omit the field it will try to unmarshal the file as a `yaml` file.
Default: `YAML` |
| `Logger` | *(optional)*
Logger used to log what `go-feature-flag` is doing.
If no logger is provided the module will not log anything.
Default: No log |
diff --git a/config.go b/config.go
index 70f0085fbc3..d4e537265da 100644
--- a/config.go
+++ b/config.go
@@ -28,6 +28,10 @@ type Config struct {
// Default: context.Background()
Context context.Context
+ // Environment (optional), can be checked in feature flag rules
+ // Default: ""
+ Environment string
+
// Retriever is the component in charge to retrieve your flag file
Retriever Retriever
diff --git a/docs/configuration.md b/docs/configuration.md
index 588ebfd8c37..9e4d9fd21a2 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -10,6 +10,7 @@ During the initialization you must give a [`ffclient.Config{}`](https://pkg.go.d
|---|---|
|`Retriever` | The configuration retriever you want to use to get your flag file
*See [Store your flag file](flag_file/index.md) for the configuration details*.|
|`Context` | *(optional)*
The context used by the retriever.
Default: `context.Background()`|
+|`Environment` | *(optional)*
The environment the app is running under, can be checked in feature flag rules.
Default: `""`|
|`DataExporter` | *(optional)*
DataExporter defines how to export data on how your flags are used.
*see [export data section](data_collection/index.md) for more details*.|
|`FileFormat`| *(optional)*
Format of your configuration file. Available formats are `yaml`, `toml` and `json`, if you omit the field it will try to unmarshal the file as a `yaml` file.
Default: `YAML`|
|`Logger` | *(optional)*
Logger used to log what `go-feature-flag` is doing.
If no logger is provided the module will not log anything.
Default: No log|
@@ -24,6 +25,7 @@ ffclient.Init(ffclient.Config{
PollingInterval: 3 * time.Second,
Logger: log.New(file, "/tmp/log", 0),
Context: context.Background(),
+ Environment: os.Getenv("MYAPP_ENV"),
Retriever: &ffclient.FileRetriever{Path: "testdata/flag-config.yaml"},
FileFormat: "yaml",
Notifiers: []ffclient.NotifierConfig{
diff --git a/internal/flag/flag.go b/internal/flag/flag.go
index 10f077ec71d..74974d60428 100644
--- a/internal/flag/flag.go
+++ b/internal/flag/flag.go
@@ -6,8 +6,8 @@ import (
type Flag interface {
// Value is returning the Value associate to the flag (True / False / Default ) based
- // if the flag apply to the user or not.
- Value(flagName string, user ffuser.User) (interface{}, string)
+ // if the flag apply to the user and environment or not.
+ Value(flagName string, user ffuser.User, environment string) (interface{}, string)
// String display correctly a flag with the right formatting
String() string
diff --git a/internal/flagv1/flag_data.go b/internal/flagv1/flag_data.go
index 1d295e6786c..b72eb2edf27 100644
--- a/internal/flagv1/flag_data.go
+++ b/internal/flagv1/flag_data.go
@@ -56,14 +56,14 @@ type FlagData struct {
// Value is returning the Value associate to the flag (True / False / Default ) based
// if the toggle apply to the user or not.
-func (f *FlagData) Value(flagName string, user ffuser.User) (interface{}, string) {
+func (f *FlagData) Value(flagName string, user ffuser.User, environment string) (interface{}, string) {
f.updateFlagStage()
if f.isExperimentationOver() {
// if we have an experimentation that has not started or that is finished we use the default value.
return f.getDefault(), VariationDefault
}
- if f.evaluateRule(user) {
+ if f.evaluateRule(user, environment) {
if f.isInPercentage(flagName, user) {
// Rule applied and user in the cohort.
return f.getTrue(), VariationTrue
@@ -102,7 +102,7 @@ func (f *FlagData) isInPercentage(flagName string, user ffuser.User) bool {
}
// evaluateRule is checking if the rule can apply to a specific user.
-func (f *FlagData) evaluateRule(user ffuser.User) bool {
+func (f *FlagData) evaluateRule(user ffuser.User, environment string) bool {
// Flag disable we cannot apply it.
if f.GetDisable() {
return false
@@ -114,7 +114,11 @@ func (f *FlagData) evaluateRule(user ffuser.User) bool {
}
// Evaluate the rule on the user.
- return parser.Evaluate(f.getRule(), utils.UserToMap(user))
+ userMap := utils.UserToMap(user)
+ if environment != "" {
+ userMap["env"] = environment
+ }
+ return parser.Evaluate(f.getRule(), userMap)
}
// string display correctly a flag
diff --git a/internal/flagv1/flag_priv_test.go b/internal/flagv1/flag_priv_test.go
index 2c9bf9d56e0..c9fcf626c11 100644
--- a/internal/flagv1/flag_priv_test.go
+++ b/internal/flagv1/flag_priv_test.go
@@ -20,6 +20,7 @@ func TestFlag_evaluateRule(t *testing.T) {
}
type args struct {
user ffuser.User
+ env string
}
tests := []struct {
name string
@@ -77,6 +78,28 @@ func TestFlag_evaluateRule(t *testing.T) {
},
want: false,
},
+ {
+ name: "Rolled out to environment",
+ fields: fields{
+ Rule: "env == \"staging\"",
+ },
+ args: args{
+ user: ffuser.NewAnonymousUser(""),
+ env: "staging",
+ },
+ want: true,
+ },
+ {
+ name: "Not rolled out to environment",
+ fields: fields{
+ Rule: "env != \"production\"",
+ },
+ args: args{
+ user: ffuser.NewAnonymousUser(""),
+ env: "production",
+ },
+ want: false,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -88,7 +111,7 @@ func TestFlag_evaluateRule(t *testing.T) {
False: testconvert.Interface(tt.fields.False),
}
- got := f.evaluateRule(tt.args.user)
+ got := f.evaluateRule(tt.args.user, tt.args.env)
assert.Equal(t, tt.want, got)
})
}
diff --git a/internal/flagv1/flag_pub_test.go b/internal/flagv1/flag_pub_test.go
index 8ec9fb1785e..ec6cb38a38c 100644
--- a/internal/flagv1/flag_pub_test.go
+++ b/internal/flagv1/flag_pub_test.go
@@ -337,7 +337,7 @@ func TestFlag_value(t *testing.T) {
Rollout: &tt.fields.Rollout,
}
- got, variationType := f.Value(tt.args.flagName, tt.args.user)
+ got, variationType := f.Value(tt.args.flagName, tt.args.user, "")
assert.Equal(t, tt.want.value, got)
assert.Equal(t, tt.want.variationType, variationType)
})
@@ -362,15 +362,15 @@ func TestFlag_ProgressiveRollout(t *testing.T) {
flagName := "test-flag"
// We evaluate the same flag multiple time overtime.
- v, _ := f.Value(flagName, user)
+ v, _ := f.Value(flagName, user, "")
assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v)
time.Sleep(1 * time.Second)
- v2, _ := f.Value(flagName, user)
+ v2, _ := f.Value(flagName, user, "")
assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v2)
time.Sleep(1 * time.Second)
- v3, _ := f.Value(flagName, user)
+ v3, _ := f.Value(flagName, user, "")
assert.Equal(t, f.GetVariationValue(flagv1.VariationTrue), v3)
}
@@ -447,43 +447,43 @@ func TestFlag_ScheduledRollout(t *testing.T) {
flagName := "test-flag"
// We evaluate the same flag multiple time overtime.
- v, _ := f.Value(flagName, user)
+ v, _ := f.Value(flagName, user, "")
assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v)
time.Sleep(1 * time.Second)
- v, _ = f.Value(flagName, user)
+ v, _ = f.Value(flagName, user, "")
assert.Equal(t, "True", v)
assert.Equal(t, 1.1, f.GetVersion())
time.Sleep(1 * time.Second)
- v, _ = f.Value(flagName, user)
+ v, _ = f.Value(flagName, user, "")
assert.Equal(t, "Default2", v)
time.Sleep(1 * time.Second)
- v, _ = f.Value(flagName, user)
+ v, _ = f.Value(flagName, user, "")
assert.Equal(t, "True2", v)
time.Sleep(1 * time.Second)
- v, _ = f.Value(flagName, user)
+ v, _ = f.Value(flagName, user, "")
assert.Equal(t, "Default2", v)
time.Sleep(1 * time.Second)
- v, _ = f.Value(flagName, user)
+ v, _ = f.Value(flagName, user, "")
assert.Equal(t, "Default2", v)
time.Sleep(1 * time.Second)
- v, _ = f.Value(flagName, user)
+ v, _ = f.Value(flagName, user, "")
assert.Equal(t, "True2", v)
time.Sleep(1 * time.Second)
- v, _ = f.Value(flagName, user)
+ v, _ = f.Value(flagName, user, "")
assert.Equal(t, "Default2", v)
}
diff --git a/variation.go b/variation.go
index d0af5d2f003..5ac4bdced26 100644
--- a/variation.go
+++ b/variation.go
@@ -153,7 +153,7 @@ func (g *GoFeatureFlag) AllFlagsState(user ffuser.User) flagstate.AllFlags {
allFlags := flagstate.NewAllFlags()
for key, currentFlag := range flags {
- flagValue, varType := currentFlag.Value(key, user)
+ flagValue, varType := currentFlag.Value(key, user, g.config.Environment)
switch v := flagValue; v.(type) {
case int, float64, bool, string, []interface{}, map[string]interface{}:
allFlags.AddFlag(key, flagstate.NewFlagState(currentFlag.GetTrackEvents(), v, varType, false))
@@ -191,7 +191,7 @@ func (g *GoFeatureFlag) boolVariation(flagKey string, user ffuser.User, sdkDefau
}, err
}
- flagValue, variationType := f.Value(flagKey, user)
+ flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(bool)
if !ok {
return model.BoolVarResult{
@@ -221,7 +221,7 @@ func (g *GoFeatureFlag) intVariation(flagKey string, user ffuser.User, sdkDefaul
}, err
}
- flagValue, variationType := f.Value(flagKey, user)
+ flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(int)
if !ok {
// if this is a float64 we convert it to int
@@ -259,7 +259,7 @@ func (g *GoFeatureFlag) float64Variation(flagKey string, user ffuser.User, sdkDe
}, err
}
- flagValue, variationType := f.Value(flagKey, user)
+ flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(float64)
if !ok {
return model.Float64VarResult{
@@ -289,7 +289,7 @@ func (g *GoFeatureFlag) stringVariation(flagKey string, user ffuser.User, sdkDef
}, err
}
- flagValue, variationType := f.Value(flagKey, user)
+ flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(string)
if !ok {
return model.StringVarResult{
@@ -319,7 +319,7 @@ func (g *GoFeatureFlag) jsonArrayVariation(flagKey string, user ffuser.User, sdk
}, err
}
- flagValue, variationType := f.Value(flagKey, user)
+ flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.([]interface{})
if !ok {
return model.JSONArrayVarResult{
@@ -349,7 +349,7 @@ func (g *GoFeatureFlag) jsonVariation(flagKey string, user ffuser.User, sdkDefau
}, err
}
- flagValue, variationType := f.Value(flagKey, user)
+ flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res, ok := flagValue.(map[string]interface{})
if !ok {
return model.JSONVarResult{
@@ -399,7 +399,7 @@ func (g *GoFeatureFlag) RawVariation(flagKey string, user ffuser.User, sdkDefaul
return res, err
}
- flagValue, variationType := f.Value(flagKey, user)
+ flagValue, variationType := f.Value(flagKey, user, g.config.Environment)
res := model.RawVarResult{
Value: flagValue,
VariationResult: computeVariationResult(f, variationType, false),