From e0984e9b3d67c28904a1c83c52b92433e27eee47 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Tue, 14 Feb 2023 09:26:13 +0000 Subject: [PATCH 01/23] basic merge strategy Signed-off-by: James Milligan --- pkg/eval/json_evaluator_test.go | 6 ++-- pkg/store/flags.go | 56 ++++++++++++++++----------------- pkg/store/flags_test.go | 16 ++++++++++ 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/pkg/eval/json_evaluator_test.go b/pkg/eval/json_evaluator_test.go index 87c52918f..9663cbfc5 100644 --- a/pkg/eval/json_evaluator_test.go +++ b/pkg/eval/json_evaluator_test.go @@ -908,7 +908,8 @@ func TestState_Evaluator(t *testing.T) { ] } } - } + }, + "flagSources":null } `, }, @@ -966,7 +967,8 @@ func TestState_Evaluator(t *testing.T) { ] } } - } + }, + "flagSources":null } `, }, diff --git a/pkg/store/flags.go b/pkg/store/flags.go index 8d2eea8be..df5f5514a 100644 --- a/pkg/store/flags.go +++ b/pkg/store/flags.go @@ -12,8 +12,24 @@ import ( ) type Flags struct { - mx sync.RWMutex - Flags map[string]model.Flag `json:"flags"` + mx sync.RWMutex + Flags map[string]model.Flag `json:"flags"` + FlagSources []string `json:"flagSources"` +} + +func (f *Flags) hasPriority(stored string, new string) bool { + if stored == new { + return true + } + for i := len(f.FlagSources) - 1; i >= 0; i-- { + if f.FlagSources[i] == stored { + return false + } else if f.FlagSources[i] == new { + + return true + } + } + return true } func NewFlags() *Flags { @@ -70,13 +86,8 @@ func (f *Flags) Add(logger *logger.Logger, source string, flags map[string]model for k, newFlag := range flags { storedFlag, ok := f.Get(k) - if ok && storedFlag.Source != source { - logger.Warn(fmt.Sprintf( - "flag with key %s from source %s already exist, overriding this with flag from source %s", - k, - storedFlag.Source, - source, - )) + if ok && !f.hasPriority(storedFlag.Source, source) { + continue } notifications[k] = map[string]interface{}{ @@ -106,13 +117,8 @@ func (f *Flags) Update(logger *logger.Logger, source string, flags map[string]mo continue } - if storedFlag.Source != source { - logger.Warn(fmt.Sprintf( - "flag with key %s from source %s already exist, overriding this with flag from source %s", - k, - storedFlag.Source, - source, - )) + if !f.hasPriority(storedFlag.Source, source) { + continue } notifications[k] = map[string]interface{}{ @@ -147,7 +153,8 @@ func (f *Flags) DeleteFlags(logger *logger.Logger, source string, flags map[stri for k := range flags { flag, ok := f.Get(k) if ok { - if flag.Source != source { + if !f.hasPriority(flag.Source, source) { + fmt.Println(flag.Source, source, "priority failing") continue } notifications[k] = map[string]interface{}{ @@ -191,22 +198,15 @@ func (f *Flags) Merge(logger *logger.Logger, source string, flags map[string]mod newFlag.Source = source storedFlag, ok := f.Get(k) + if ok && (!f.hasPriority(storedFlag.Source, source) || reflect.DeepEqual(storedFlag, newFlag)) { + continue + } if !ok { notifications[k] = map[string]interface{}{ "type": string(model.NotificationCreate), "source": source, } - } else if !reflect.DeepEqual(storedFlag, newFlag) { - if storedFlag.Source != source { - logger.Warn( - fmt.Sprintf( - "key value: %s is duplicated across multiple sources this can lead to unexpected behavior: %s, %s", - k, - storedFlag.Source, - source, - ), - ) - } + } else { notifications[k] = map[string]interface{}{ "type": string(model.NotificationUpdate), "source": source, diff --git a/pkg/store/flags_test.go b/pkg/store/flags_test.go index e3ba77828..f64962f0c 100644 --- a/pkg/store/flags_test.go +++ b/pkg/store/flags_test.go @@ -361,6 +361,10 @@ func TestFlags_Delete(t *testing.T) { "B": {Source: mockSource}, "C": {Source: mockSource2}, }, + FlagSources: []string{ + mockSource, + mockSource2, + }, }, deleteRequest: map[string]model.Flag{ "A": {Source: mockSource}, @@ -370,6 +374,10 @@ func TestFlags_Delete(t *testing.T) { "B": {Source: mockSource}, "C": {Source: mockSource2}, }, + FlagSources: []string{ + mockSource, + mockSource2, + }, }, expectedNotificationKeys: []string{"A"}, }, @@ -381,6 +389,10 @@ func TestFlags_Delete(t *testing.T) { "B": {Source: mockSource}, "C": {Source: mockSource2}, }, + FlagSources: []string{ + mockSource, + mockSource2, + }, }, deleteRequest: map[string]model.Flag{ "C": {Source: mockSource}, @@ -391,6 +403,10 @@ func TestFlags_Delete(t *testing.T) { "B": {Source: mockSource}, "C": {Source: mockSource2}, }, + FlagSources: []string{ + mockSource, + mockSource2, + }, }, expectedNotificationKeys: []string{}, }, From 68d5969cf6723efeadc55bfc6f1dc565c4de6cb0 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Tue, 14 Feb 2023 11:20:06 +0000 Subject: [PATCH 02/23] expose store in json evaluator Signed-off-by: James Milligan --- pkg/eval/fractional_evaluation_test.go | 8 ++++---- pkg/eval/json_evaluator.go | 28 +++++++++++++------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/eval/fractional_evaluation_test.go b/pkg/eval/fractional_evaluation_test.go index 3c23615a5..b19f915ff 100644 --- a/pkg/eval/fractional_evaluation_test.go +++ b/pkg/eval/fractional_evaluation_test.go @@ -283,10 +283,10 @@ func TestFractionalEvaluation(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { je := NewJSONEvaluator(logger.NewLogger(nil, false)) - je.store.Flags = tt.flags.Flags + je.Store.Flags = tt.flags.Flags value, variant, reason, err := resolve[string]( - reqID, tt.flagKey, tt.context, je.evaluateVariant, je.store.Flags[tt.flagKey].Variants, + reqID, tt.flagKey, tt.context, je.evaluateVariant, je.Store.Flags[tt.flagKey].Variants, ) if value != tt.expectedValue { @@ -415,10 +415,10 @@ func BenchmarkFractionalEvaluation(b *testing.B) { reqID := "test" for name, tt := range tests { b.Run(name, func(b *testing.B) { - je := JSONEvaluator{store: &store.Flags{Flags: tt.flags.Flags}} + je := JSONEvaluator{Store: &store.Flags{Flags: tt.flags.Flags}} for i := 0; i < b.N; i++ { value, variant, reason, err := resolve[string]( - reqID, tt.flagKey, tt.context, je.evaluateVariant, je.store.Flags[tt.flagKey].Variants, + reqID, tt.flagKey, tt.context, je.evaluateVariant, je.Store.Flags[tt.flagKey].Variants, ) if value != tt.expectedValue { diff --git a/pkg/eval/json_evaluator.go b/pkg/eval/json_evaluator.go index b8668ae54..3ed9e5105 100644 --- a/pkg/eval/json_evaluator.go +++ b/pkg/eval/json_evaluator.go @@ -28,7 +28,7 @@ func init() { } type JSONEvaluator struct { - store *store.Flags + Store *store.Flags Logger *logger.Logger } @@ -46,14 +46,14 @@ func NewJSONEvaluator(logger *logger.Logger) *JSONEvaluator { zap.String("component", "evaluator"), zap.String("evaluator", "json"), ), - store: store.NewFlags(), + Store: store.NewFlags(), } jsonlogic.AddOperator("fractionalEvaluation", ev.fractionalEvaluation) return &ev } func (je *JSONEvaluator) GetState() (string, error) { - return je.store.String() + return je.Store.String() } func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{}, error) { @@ -65,13 +65,13 @@ func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{} switch payload.Type { case sync.ALL: - return je.store.Merge(je.Logger, payload.Source, newFlags.Flags), nil + return je.Store.Merge(je.Logger, payload.Source, newFlags.Flags), nil case sync.ADD: - return je.store.Add(je.Logger, payload.Source, newFlags.Flags), nil + return je.Store.Add(je.Logger, payload.Source, newFlags.Flags), nil case sync.UPDATE: - return je.store.Update(je.Logger, payload.Source, newFlags.Flags), nil + return je.Store.Update(je.Logger, payload.Source, newFlags.Flags), nil case sync.DELETE: - return je.store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags), nil + return je.Store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags), nil default: return nil, fmt.Errorf("unsupported sync type: %d", payload.Type) } @@ -105,7 +105,7 @@ func (je *JSONEvaluator) ResolveAllValues(reqID string, context *structpb.Struct var variant string var reason string var err error - allFlags := je.store.GetAll() + allFlags := je.Store.GetAll() for flagKey, flag := range allFlags { defaultValue := flag.Variants[flag.DefaultVariant] switch defaultValue.(type) { @@ -158,7 +158,7 @@ func (je *JSONEvaluator) ResolveBooleanValue(reqID string, flagKey string, conte err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating boolean flag: %s", flagKey)) - flag, _ := je.store.Get(flagKey) + flag, _ := je.Store.Get(flagKey) return resolve[bool](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } @@ -169,7 +169,7 @@ func (je *JSONEvaluator) ResolveStringValue(reqID string, flagKey string, contex err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating string flag: %s", flagKey)) - flag, _ := je.store.Get(flagKey) + flag, _ := je.Store.Get(flagKey) return resolve[string](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } @@ -180,7 +180,7 @@ func (je *JSONEvaluator) ResolveFloatValue(reqID string, flagKey string, context err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating float flag: %s", flagKey)) - flag, _ := je.store.Get(flagKey) + flag, _ := je.Store.Get(flagKey) value, variant, reason, err = resolve[float64]( reqID, flagKey, context, je.evaluateVariant, flag.Variants) return @@ -193,7 +193,7 @@ func (je *JSONEvaluator) ResolveIntValue(reqID string, flagKey string, context * err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating int flag: %s", flagKey)) - flag, _ := je.store.Get(flagKey) + flag, _ := je.Store.Get(flagKey) var val float64 val, variant, reason, err = resolve[float64]( reqID, flagKey, context, je.evaluateVariant, flag.Variants) @@ -208,7 +208,7 @@ func (je *JSONEvaluator) ResolveObjectValue(reqID string, flagKey string, contex err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating object flag: %s", flagKey)) - flag, _ := je.store.Get(flagKey) + flag, _ := je.Store.Get(flagKey) return resolve[map[string]any](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } @@ -218,7 +218,7 @@ func (je *JSONEvaluator) evaluateVariant( flagKey string, context *structpb.Struct, ) (variant string, reason string, err error) { - flag, ok := je.store.Get(flagKey) + flag, ok := je.Store.Get(flagKey) if !ok { // flag not found je.Logger.DebugWithID(reqID, fmt.Sprintf("requested flag could not be found: %s", flagKey)) From f53002fba586c504cd4205183062013432dcb8ae Mon Sep 17 00:00:00 2001 From: James Milligan Date: Tue, 14 Feb 2023 11:35:39 +0000 Subject: [PATCH 03/23] implement flag souces in from_config Signed-off-by: James Milligan --- pkg/eval/fractional_evaluation_test.go | 10 ++++---- pkg/eval/json_evaluator.go | 30 +++++++++++----------- pkg/eval/json_evaluator_test.go | 35 +++++++++++++------------- pkg/runtime/from_config.go | 9 +++++-- pkg/sync/file/filepath_sync.go | 3 ++- pkg/sync/kubernetes/kubernetes_sync.go | 7 +++--- 6 files changed, 51 insertions(+), 43 deletions(-) diff --git a/pkg/eval/fractional_evaluation_test.go b/pkg/eval/fractional_evaluation_test.go index b19f915ff..e1aa5a214 100644 --- a/pkg/eval/fractional_evaluation_test.go +++ b/pkg/eval/fractional_evaluation_test.go @@ -282,11 +282,11 @@ func TestFractionalEvaluation(t *testing.T) { const reqID = "default" for name, tt := range tests { t.Run(name, func(t *testing.T) { - je := NewJSONEvaluator(logger.NewLogger(nil, false)) - je.Store.Flags = tt.flags.Flags + je := NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) + je.store.Flags = tt.flags.Flags value, variant, reason, err := resolve[string]( - reqID, tt.flagKey, tt.context, je.evaluateVariant, je.Store.Flags[tt.flagKey].Variants, + reqID, tt.flagKey, tt.context, je.evaluateVariant, je.store.Flags[tt.flagKey].Variants, ) if value != tt.expectedValue { @@ -415,10 +415,10 @@ func BenchmarkFractionalEvaluation(b *testing.B) { reqID := "test" for name, tt := range tests { b.Run(name, func(b *testing.B) { - je := JSONEvaluator{Store: &store.Flags{Flags: tt.flags.Flags}} + je := JSONEvaluator{store: &store.Flags{Flags: tt.flags.Flags}} for i := 0; i < b.N; i++ { value, variant, reason, err := resolve[string]( - reqID, tt.flagKey, tt.context, je.evaluateVariant, je.Store.Flags[tt.flagKey].Variants, + reqID, tt.flagKey, tt.context, je.evaluateVariant, je.store.Flags[tt.flagKey].Variants, ) if value != tt.expectedValue { diff --git a/pkg/eval/json_evaluator.go b/pkg/eval/json_evaluator.go index 3ed9e5105..bb1d8b9ee 100644 --- a/pkg/eval/json_evaluator.go +++ b/pkg/eval/json_evaluator.go @@ -28,7 +28,7 @@ func init() { } type JSONEvaluator struct { - Store *store.Flags + store *store.Flags Logger *logger.Logger } @@ -40,20 +40,20 @@ const ( Disabled = "DISABLED" ) -func NewJSONEvaluator(logger *logger.Logger) *JSONEvaluator { +func NewJSONEvaluator(logger *logger.Logger, s *store.Flags) *JSONEvaluator { ev := JSONEvaluator{ Logger: logger.WithFields( zap.String("component", "evaluator"), zap.String("evaluator", "json"), ), - Store: store.NewFlags(), + store: s, } jsonlogic.AddOperator("fractionalEvaluation", ev.fractionalEvaluation) return &ev } func (je *JSONEvaluator) GetState() (string, error) { - return je.Store.String() + return je.store.String() } func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{}, error) { @@ -65,13 +65,13 @@ func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{} switch payload.Type { case sync.ALL: - return je.Store.Merge(je.Logger, payload.Source, newFlags.Flags), nil + return je.store.Merge(je.Logger, payload.Source, newFlags.Flags), nil case sync.ADD: - return je.Store.Add(je.Logger, payload.Source, newFlags.Flags), nil + return je.store.Add(je.Logger, payload.Source, newFlags.Flags), nil case sync.UPDATE: - return je.Store.Update(je.Logger, payload.Source, newFlags.Flags), nil + return je.store.Update(je.Logger, payload.Source, newFlags.Flags), nil case sync.DELETE: - return je.Store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags), nil + return je.store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags), nil default: return nil, fmt.Errorf("unsupported sync type: %d", payload.Type) } @@ -105,7 +105,7 @@ func (je *JSONEvaluator) ResolveAllValues(reqID string, context *structpb.Struct var variant string var reason string var err error - allFlags := je.Store.GetAll() + allFlags := je.store.GetAll() for flagKey, flag := range allFlags { defaultValue := flag.Variants[flag.DefaultVariant] switch defaultValue.(type) { @@ -158,7 +158,7 @@ func (je *JSONEvaluator) ResolveBooleanValue(reqID string, flagKey string, conte err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating boolean flag: %s", flagKey)) - flag, _ := je.Store.Get(flagKey) + flag, _ := je.store.Get(flagKey) return resolve[bool](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } @@ -169,7 +169,7 @@ func (je *JSONEvaluator) ResolveStringValue(reqID string, flagKey string, contex err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating string flag: %s", flagKey)) - flag, _ := je.Store.Get(flagKey) + flag, _ := je.store.Get(flagKey) return resolve[string](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } @@ -180,7 +180,7 @@ func (je *JSONEvaluator) ResolveFloatValue(reqID string, flagKey string, context err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating float flag: %s", flagKey)) - flag, _ := je.Store.Get(flagKey) + flag, _ := je.store.Get(flagKey) value, variant, reason, err = resolve[float64]( reqID, flagKey, context, je.evaluateVariant, flag.Variants) return @@ -193,7 +193,7 @@ func (je *JSONEvaluator) ResolveIntValue(reqID string, flagKey string, context * err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating int flag: %s", flagKey)) - flag, _ := je.Store.Get(flagKey) + flag, _ := je.store.Get(flagKey) var val float64 val, variant, reason, err = resolve[float64]( reqID, flagKey, context, je.evaluateVariant, flag.Variants) @@ -208,7 +208,7 @@ func (je *JSONEvaluator) ResolveObjectValue(reqID string, flagKey string, contex err error, ) { je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating object flag: %s", flagKey)) - flag, _ := je.Store.Get(flagKey) + flag, _ := je.store.Get(flagKey) return resolve[map[string]any](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } @@ -218,7 +218,7 @@ func (je *JSONEvaluator) evaluateVariant( flagKey string, context *structpb.Struct, ) (variant string, reason string, err error) { - flag, ok := je.Store.Get(flagKey) + flag, ok := je.store.Get(flagKey) if !ok { // flag not found je.Logger.DebugWithID(reqID, fmt.Sprintf("requested flag could not be found: %s", flagKey)) diff --git a/pkg/eval/json_evaluator_test.go b/pkg/eval/json_evaluator_test.go index 9663cbfc5..213b9aeb7 100644 --- a/pkg/eval/json_evaluator_test.go +++ b/pkg/eval/json_evaluator_test.go @@ -11,6 +11,7 @@ import ( "github.com/open-feature/flagd/pkg/eval" "github.com/open-feature/flagd/pkg/logger" "github.com/open-feature/flagd/pkg/model" + "github.com/open-feature/flagd/pkg/store" "github.com/open-feature/flagd/pkg/sync" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/types/known/structpb" @@ -276,7 +277,7 @@ var Flags = fmt.Sprintf(`{ DisabledFlag) func TestGetState_Valid_ContainsFlag(t *testing.T) { - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags}) if err != nil { t.Fatalf("Expected no error") @@ -296,7 +297,7 @@ func TestGetState_Valid_ContainsFlag(t *testing.T) { } func TestSetState_Invalid_Error(t *testing.T) { - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) // set state with an invalid flag definition _, err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags}) @@ -306,7 +307,7 @@ func TestSetState_Invalid_Error(t *testing.T) { } func TestSetState_Valid_NoError(t *testing.T) { - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) // set state with a valid flag definition _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags}) @@ -316,7 +317,7 @@ func TestSetState_Valid_NoError(t *testing.T) { } func TestResolveAllValues(t *testing.T) { - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") @@ -376,7 +377,7 @@ func TestResolveBooleanValue(t *testing.T) { {DisabledFlag, nil, StaticBoolValue, model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") @@ -415,7 +416,7 @@ func BenchmarkResolveBooleanValue(b *testing.B) { {DisabledFlag, nil, StaticBoolValue, model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") @@ -459,7 +460,7 @@ func TestResolveStringValue(t *testing.T) { {DisabledFlag, nil, "", model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") @@ -499,7 +500,7 @@ func BenchmarkResolveStringValue(b *testing.B) { {DisabledFlag, nil, "", model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") @@ -543,7 +544,7 @@ func TestResolveFloatValue(t *testing.T) { {DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") @@ -583,7 +584,7 @@ func BenchmarkResolveFloatValue(b *testing.B) { {DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") @@ -627,7 +628,7 @@ func TestResolveIntValue(t *testing.T) { {DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") @@ -667,7 +668,7 @@ func BenchmarkResolveIntValue(b *testing.B) { {DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") @@ -711,7 +712,7 @@ func TestResolveObjectValue(t *testing.T) { {DisabledFlag, nil, "{}", model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") @@ -754,7 +755,7 @@ func BenchmarkResolveObjectValue(b *testing.B) { {DisabledFlag, nil, "{}", model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") @@ -837,7 +838,7 @@ func TestSetState_DefaultVariantValidation(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags}) @@ -1034,7 +1035,7 @@ func TestState_Evaluator(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState}) if err != nil { @@ -1134,7 +1135,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) + jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Type: sync.ADD}) if err != nil { diff --git a/pkg/runtime/from_config.go b/pkg/runtime/from_config.go index 8684fe6b9..4a3fcf7ad 100644 --- a/pkg/runtime/from_config.go +++ b/pkg/runtime/from_config.go @@ -6,6 +6,7 @@ import ( "regexp" "time" + "github.com/open-feature/flagd/pkg/store" "github.com/open-feature/flagd/pkg/sync/file" httpSync "github.com/open-feature/flagd/pkg/sync/http" @@ -31,10 +32,12 @@ func init() { } func FromConfig(logger *logger.Logger, config Config) (*Runtime, error) { + s := store.NewFlags() + s.FlagSources = config.SyncURI rt := Runtime{ config: config, Logger: logger.WithFields(zap.String("component", "runtime")), - Evaluator: eval.NewJSONEvaluator(logger), + Evaluator: eval.NewJSONEvaluator(logger, s), } if err := rt.setSyncImplFromConfig(logger); err != nil { return nil, err @@ -66,7 +69,8 @@ func (r *Runtime) setSyncImplFromConfig(logger *logger.Logger) error { switch uriB := []byte(uri); { case regFile.Match(uriB): r.SyncImpl = append(r.SyncImpl, &file.Sync{ - URI: regFile.ReplaceAllString(uri, ""), + URI: regFile.ReplaceAllString(uri, ""), + Source: uri, Logger: logger.WithFields( zap.String("component", "sync"), zap.String("sync", "filepath"), @@ -81,6 +85,7 @@ func (r *Runtime) setSyncImplFromConfig(logger *logger.Logger) error { zap.String("sync", "kubernetes"), ), URI: regCrd.ReplaceAllString(uri, ""), + Source: uri, ProviderArgs: r.config.ProviderArgs, }) rtLogger.Debug(fmt.Sprintf("using kubernetes sync-provider for: %s", uri)) diff --git a/pkg/sync/file/filepath_sync.go b/pkg/sync/file/filepath_sync.go index b7227991a..d73a55300 100644 --- a/pkg/sync/file/filepath_sync.go +++ b/pkg/sync/file/filepath_sync.go @@ -18,6 +18,7 @@ import ( type Sync struct { URI string + Source string Logger *logger.Logger ProviderArgs sync.ProviderArgs // FileType indicates the file type e.g., json, yaml/yml etc., @@ -109,7 +110,7 @@ func (fs *Sync) sendDataSync(ctx context.Context, syncType sync.Type, dataSync c } } - dataSync <- sync.DataSync{FlagData: msg, Source: fs.URI, Type: syncType} + dataSync <- sync.DataSync{FlagData: msg, Source: fs.Source, Type: syncType} } func (fs *Sync) fetch(_ context.Context) (string, error) { diff --git a/pkg/sync/kubernetes/kubernetes_sync.go b/pkg/sync/kubernetes/kubernetes_sync.go index f7f094cb1..9a0963722 100644 --- a/pkg/sync/kubernetes/kubernetes_sync.go +++ b/pkg/sync/kubernetes/kubernetes_sync.go @@ -30,6 +30,7 @@ type Sync struct { ProviderArgs sync.ProviderArgs client client.Client URI string + Source string } func (k *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { @@ -40,7 +41,7 @@ func (k *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { return err } - dataSync <- sync.DataSync{FlagData: fetch, Source: k.URI, Type: sync.ALL} + dataSync <- sync.DataSync{FlagData: fetch, Source: k.Source, Type: sync.ALL} notifies := make(chan INotify) @@ -60,7 +61,7 @@ func (k *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { continue } - dataSync <- sync.DataSync{FlagData: msg, Source: k.URI, Type: sync.ALL} + dataSync <- sync.DataSync{FlagData: msg, Source: k.Source, Type: sync.ALL} case DefaultEventTypeModify: k.Logger.Debug("Configuration modified") msg, err := k.fetch(ctx) @@ -69,7 +70,7 @@ func (k *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { continue } - dataSync <- sync.DataSync{FlagData: msg, Source: k.URI, Type: sync.ALL} + dataSync <- sync.DataSync{FlagData: msg, Source: k.Source, Type: sync.ALL} case DefaultEventTypeDelete: k.Logger.Debug("configuration deleted") case DefaultEventTypeReady: From d82bfc2bf8b030d00c45d9430f23dcb191322a3c Mon Sep 17 00:00:00 2001 From: James Milligan Date: Tue, 14 Feb 2023 13:50:08 +0000 Subject: [PATCH 04/23] introduce resync methods Signed-off-by: James Milligan --- pkg/eval/ievaluator.go | 2 +- pkg/eval/json_evaluator.go | 18 ++++++++----- pkg/eval/json_evaluator_test.go | 36 +++++++++++++------------- pkg/eval/mock/ievaluator.go | 7 ++--- pkg/runtime/runtime.go | 19 +++++++++++--- pkg/store/flags.go | 6 +++-- pkg/store/flags_test.go | 2 +- pkg/sync/file/filepath_sync.go | 5 ++++ pkg/sync/http/http_sync.go | 9 +++++++ pkg/sync/isync.go | 5 ++++ pkg/sync/kubernetes/kubernetes_sync.go | 9 +++++++ 11 files changed, 82 insertions(+), 36 deletions(-) diff --git a/pkg/eval/ievaluator.go b/pkg/eval/ievaluator.go index c1df1f146..640552182 100644 --- a/pkg/eval/ievaluator.go +++ b/pkg/eval/ievaluator.go @@ -27,7 +27,7 @@ do parsing and validation of the flag state and evaluate flags in response to ha */ type IEvaluator interface { GetState() (string, error) - SetState(payload sync.DataSync) (map[string]interface{}, error) + SetState(payload sync.DataSync) (map[string]interface{}, bool, error) ResolveBooleanValue( reqID string, diff --git a/pkg/eval/json_evaluator.go b/pkg/eval/json_evaluator.go index bb1d8b9ee..3ace41152 100644 --- a/pkg/eval/json_evaluator.go +++ b/pkg/eval/json_evaluator.go @@ -56,24 +56,28 @@ func (je *JSONEvaluator) GetState() (string, error) { return je.store.String() } -func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{}, error) { +func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{}, bool, error) { var newFlags Flags err := je.configToFlags(payload.FlagData, &newFlags) if err != nil { - return nil, err + return nil, false, err } switch payload.Type { case sync.ALL: - return je.store.Merge(je.Logger, payload.Source, newFlags.Flags), nil + n, resync := je.store.Merge(je.Logger, payload.Source, newFlags.Flags) + return n, resync, nil case sync.ADD: - return je.store.Add(je.Logger, payload.Source, newFlags.Flags), nil + return je.store.Add(je.Logger, payload.Source, newFlags.Flags), false, nil case sync.UPDATE: - return je.store.Update(je.Logger, payload.Source, newFlags.Flags), nil + return je.store.Update(je.Logger, payload.Source, newFlags.Flags), false, nil case sync.DELETE: - return je.store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags), nil + return je.store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags), true, nil + case sync.RESYNC: + n, _ := je.store.Merge(je.Logger, payload.Source, newFlags.Flags) + return n, false, nil default: - return nil, fmt.Errorf("unsupported sync type: %d", payload.Type) + return nil, false, fmt.Errorf("unsupported sync type: %d", payload.Type) } } diff --git a/pkg/eval/json_evaluator_test.go b/pkg/eval/json_evaluator_test.go index 213b9aeb7..2f7545ebb 100644 --- a/pkg/eval/json_evaluator_test.go +++ b/pkg/eval/json_evaluator_test.go @@ -278,7 +278,7 @@ var Flags = fmt.Sprintf(`{ func TestGetState_Valid_ContainsFlag(t *testing.T) { evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags}) if err != nil { t.Fatalf("Expected no error") } @@ -300,7 +300,7 @@ func TestSetState_Invalid_Error(t *testing.T) { evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) // set state with an invalid flag definition - _, err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags}) if err == nil { t.Fatalf("expected error") } @@ -310,7 +310,7 @@ func TestSetState_Valid_NoError(t *testing.T) { evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) // set state with a valid flag definition - _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags}) if err != nil { t.Fatalf("expected no error") } @@ -318,7 +318,7 @@ func TestSetState_Valid_NoError(t *testing.T) { func TestResolveAllValues(t *testing.T) { evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") } @@ -378,7 +378,7 @@ func TestResolveBooleanValue(t *testing.T) { } const reqID = "default" evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") } @@ -417,7 +417,7 @@ func BenchmarkResolveBooleanValue(b *testing.B) { } evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") } @@ -461,7 +461,7 @@ func TestResolveStringValue(t *testing.T) { } const reqID = "default" evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") } @@ -501,7 +501,7 @@ func BenchmarkResolveStringValue(b *testing.B) { } evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") } @@ -545,7 +545,7 @@ func TestResolveFloatValue(t *testing.T) { } const reqID = "default" evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") } @@ -585,7 +585,7 @@ func BenchmarkResolveFloatValue(b *testing.B) { } evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") } @@ -629,7 +629,7 @@ func TestResolveIntValue(t *testing.T) { } const reqID = "default" evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") } @@ -669,7 +669,7 @@ func BenchmarkResolveIntValue(b *testing.B) { } evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") } @@ -713,7 +713,7 @@ func TestResolveObjectValue(t *testing.T) { } const reqID = "default" evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { t.Fatalf("expected no error") } @@ -756,7 +756,7 @@ func BenchmarkResolveObjectValue(b *testing.B) { } evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) if err != nil { b.Fatalf("expected no error") } @@ -840,7 +840,7 @@ func TestSetState_DefaultVariantValidation(t *testing.T) { t.Run(name, func(t *testing.T) { jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags}) + _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags}) if tt.valid && err != nil { t.Error(err) @@ -1037,7 +1037,7 @@ func TestState_Evaluator(t *testing.T) { t.Run(name, func(t *testing.T) { jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState}) + _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState}) if err != nil { if !tt.expectedError { t.Error(err) @@ -1137,7 +1137,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) { t.Run(name, func(t *testing.T) { jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Type: sync.ADD}) + _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Type: sync.ADD}) if err != nil { t.Fatal(err) } @@ -1160,7 +1160,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) { errChan <- nil return default: - _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Type: tt.dataSyncType}) + _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Type: tt.dataSyncType}) if err != nil { errChan <- err return diff --git a/pkg/eval/mock/ievaluator.go b/pkg/eval/mock/ievaluator.go index 80ddd3a92..aac602f0b 100644 --- a/pkg/eval/mock/ievaluator.go +++ b/pkg/eval/mock/ievaluator.go @@ -151,12 +151,13 @@ func (mr *MockIEvaluatorMockRecorder) ResolveStringValue(reqID, flagKey, context } // SetState mocks base method. -func (m *MockIEvaluator) SetState(payload sync.DataSync) (map[string]interface{}, error) { +func (m *MockIEvaluator) SetState(payload sync.DataSync) (map[string]interface{}, bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetState", payload) ret0, _ := ret[0].(map[string]interface{}) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // SetState indicates an expected call of SetState. diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 3c441a0ee..b9287458d 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -62,7 +62,16 @@ func (r *Runtime) Start() error { for { select { case data := <-dataSync: - r.updateWithNotify(data) + if resyncRequired := r.updateWithNotify(data); resyncRequired { + for _, s := range r.SyncImpl { + p := s + go func() { + g.Go(func() error { + return p.ReSync(gCtx, dataSync) + }) + }() + } + } case <-gCtx.Done(): return nil } @@ -89,14 +98,14 @@ func (r *Runtime) Start() error { } // updateWithNotify helps to update state and notify listeners -func (r *Runtime) updateWithNotify(payload sync.DataSync) { +func (r *Runtime) updateWithNotify(payload sync.DataSync) bool { r.mu.Lock() defer r.mu.Unlock() - notifications, err := r.Evaluator.SetState(payload) + notifications, resyncRequired, err := r.Evaluator.SetState(payload) if err != nil { r.Logger.Error(err.Error()) - return + return false } r.Service.Notify(service.Notification{ @@ -105,4 +114,6 @@ func (r *Runtime) updateWithNotify(payload sync.DataSync) { "flags": notifications, }, }) + + return resyncRequired } diff --git a/pkg/store/flags.go b/pkg/store/flags.go index df5f5514a..afefd24dd 100644 --- a/pkg/store/flags.go +++ b/pkg/store/flags.go @@ -175,8 +175,9 @@ func (f *Flags) DeleteFlags(logger *logger.Logger, source string, flags map[stri } // Merge provided flags from source with currently stored flags. -func (f *Flags) Merge(logger *logger.Logger, source string, flags map[string]model.Flag) map[string]interface{} { +func (f *Flags) Merge(logger *logger.Logger, source string, flags map[string]model.Flag) (map[string]interface{}, bool) { notifications := map[string]interface{}{} + resyncRequired := false f.mx.Lock() for k, v := range f.Flags { @@ -188,6 +189,7 @@ func (f *Flags) Merge(logger *logger.Logger, source string, flags map[string]mod "type": string(model.NotificationDelete), "source": source, } + resyncRequired = true continue } } @@ -217,5 +219,5 @@ func (f *Flags) Merge(logger *logger.Logger, source string, flags map[string]mod f.Set(k, newFlag) } - return notifications + return notifications, resyncRequired } diff --git a/pkg/store/flags_test.go b/pkg/store/flags_test.go index f64962f0c..8a0ee8467 100644 --- a/pkg/store/flags_test.go +++ b/pkg/store/flags_test.go @@ -122,7 +122,7 @@ func TestMergeFlags(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - gotNotifs := tt.current.Merge(logger.NewLogger(nil, false), tt.newSource, tt.new) + gotNotifs, _ := tt.current.Merge(logger.NewLogger(nil, false), tt.newSource, tt.new) require.Equal(t, tt.want, tt.want) require.Equal(t, tt.wantNotifs, gotNotifs) }) diff --git a/pkg/sync/file/filepath_sync.go b/pkg/sync/file/filepath_sync.go index d73a55300..4d6f574eb 100644 --- a/pkg/sync/file/filepath_sync.go +++ b/pkg/sync/file/filepath_sync.go @@ -28,6 +28,11 @@ type Sync struct { // default state is used to prevent EOF errors when handling filepath delete events + empty files const defaultState = "{}" +func (fs *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error { + fs.sendDataSync(ctx, sync.RESYNC, dataSync) + return nil +} + //nolint:funlen func (fs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { fs.Logger.Info("Starting filepath sync notifier") diff --git a/pkg/sync/http/http_sync.go b/pkg/sync/http/http_sync.go index 3ce78fb80..603d09d6c 100644 --- a/pkg/sync/http/http_sync.go +++ b/pkg/sync/http/http_sync.go @@ -37,6 +37,15 @@ type Cron interface { Stop() } +func (hs *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error { + msg, err := hs.Fetch(ctx) + if err != nil { + return err + } + dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI, Type: sync.RESYNC} + return nil +} + func (hs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { // Initial fetch fetch, err := hs.Fetch(ctx) diff --git a/pkg/sync/isync.go b/pkg/sync/isync.go index 86ed7c2d8..06c3b1e1c 100644 --- a/pkg/sync/isync.go +++ b/pkg/sync/isync.go @@ -16,6 +16,7 @@ const ( UPDATE // DELETE - Delete for flag(s) previously provided DELETE + RESYNC ) func (t Type) String() string { @@ -28,6 +29,8 @@ func (t Type) String() string { return "UPDATE" case DELETE: return "DELETE" + case RESYNC: + return "RESYNC" default: return "UNKNOWN" } @@ -41,6 +44,8 @@ type ISync interface { // Sync is the contract between Runtime and sync implementation. // Note that, it is expected to return the first data sync as soon as possible to fill the store. Sync(ctx context.Context, dataSync chan<- DataSync) error + + ReSync(ctx context.Context, dataSync chan<- DataSync) error } // DataSync is the data contract between Runtime and sync implementations diff --git a/pkg/sync/kubernetes/kubernetes_sync.go b/pkg/sync/kubernetes/kubernetes_sync.go index 9a0963722..747ddc3ca 100644 --- a/pkg/sync/kubernetes/kubernetes_sync.go +++ b/pkg/sync/kubernetes/kubernetes_sync.go @@ -33,6 +33,15 @@ type Sync struct { Source string } +func (k *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error { + fetch, err := k.fetch(ctx) + if err != nil { + return err + } + dataSync <- sync.DataSync{FlagData: fetch, Source: k.Source, Type: sync.ALL} + return nil +} + func (k *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { // Initial fetch fetch, err := k.fetch(ctx) From d3e67ac17b38f76b8e2ecf219ea35433223bd092 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Tue, 14 Feb 2023 13:55:54 +0000 Subject: [PATCH 05/23] tests passing Signed-off-by: James Milligan --- pkg/sync/file/filepath_sync_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/sync/file/filepath_sync_test.go b/pkg/sync/file/filepath_sync_test.go index 1d1de2f5f..690a6b1fb 100644 --- a/pkg/sync/file/filepath_sync_test.go +++ b/pkg/sync/file/filepath_sync_test.go @@ -87,6 +87,7 @@ func TestSimpleSync(t *testing.T) { handler := Sync{ URI: fmt.Sprintf("%s/%s", fetchDirName, fetchFileName), Logger: logger.NewLogger(nil, false), + Source: fmt.Sprintf("%s/%s", fetchDirName, fetchFileName), } for test, tt := range tests { From f6efa787b5d2fc12d2a4cc53101a9a48ad9cc3c1 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 15 Feb 2023 09:46:24 +0000 Subject: [PATCH 06/23] docs Signed-off-by: James Milligan --- pkg/sync/isync.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/sync/isync.go b/pkg/sync/isync.go index 06c3b1e1c..732107584 100644 --- a/pkg/sync/isync.go +++ b/pkg/sync/isync.go @@ -16,6 +16,7 @@ const ( UPDATE // DELETE - Delete for flag(s) previously provided DELETE + // DELETE - All flags of sync provider. Prevents the triggering of a second resync event RESYNC ) @@ -45,6 +46,7 @@ type ISync interface { // Note that, it is expected to return the first data sync as soon as possible to fill the store. Sync(ctx context.Context, dataSync chan<- DataSync) error + // ReSync is used to validate the flag store following a delete event ReSync(ctx context.Context, dataSync chan<- DataSync) error } From 1d756ffbe9c1366992d5aea6650e5ace2c62455f Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 15 Feb 2023 13:20:31 +0000 Subject: [PATCH 07/23] add test Signed-off-by: James Milligan --- pkg/store/flags_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/store/flags_test.go b/pkg/store/flags_test.go index 8a0ee8467..6e52d0d04 100644 --- a/pkg/store/flags_test.go +++ b/pkg/store/flags_test.go @@ -18,6 +18,7 @@ func TestMergeFlags(t *testing.T) { newSource string want *Flags wantNotifs map[string]interface{} + wantResync bool }{ { name: "both nil", @@ -116,15 +117,29 @@ func TestMergeFlags(t *testing.T) { }}, wantNotifs: map[string]interface{}{}, }, + { + name: "deleted flag", + current: &Flags{ + Flags: map[string]model.Flag{"hello": {DefaultVariant: "off", Source: "A"}}, + }, + new: map[string]model.Flag{}, + newSource: "A", + want: &Flags{Flags: map[string]model.Flag{}}, + wantNotifs: map[string]interface{}{ + "hello": map[string]interface{}{"type": "delete", "source": "A"}, + }, + wantResync: true, + }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - gotNotifs, _ := tt.current.Merge(logger.NewLogger(nil, false), tt.newSource, tt.new) + gotNotifs, resyncRequired := tt.current.Merge(logger.NewLogger(nil, false), tt.newSource, tt.new) require.Equal(t, tt.want, tt.want) require.Equal(t, tt.wantNotifs, gotNotifs) + require.Equal(t, tt.wantResync, resyncRequired) }) } } From 675dd437882c1343cec6efbf3bb155c154c8df8f Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 15 Feb 2023 13:24:50 +0000 Subject: [PATCH 08/23] remove resync operation type Signed-off-by: James Milligan --- pkg/eval/json_evaluator.go | 3 --- pkg/store/flags.go | 7 +++++-- pkg/sync/file/filepath_sync.go | 2 +- pkg/sync/http/http_sync.go | 2 +- pkg/sync/isync.go | 4 +--- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pkg/eval/json_evaluator.go b/pkg/eval/json_evaluator.go index 3ace41152..f06777375 100644 --- a/pkg/eval/json_evaluator.go +++ b/pkg/eval/json_evaluator.go @@ -73,9 +73,6 @@ func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{} return je.store.Update(je.Logger, payload.Source, newFlags.Flags), false, nil case sync.DELETE: return je.store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags), true, nil - case sync.RESYNC: - n, _ := je.store.Merge(je.Logger, payload.Source, newFlags.Flags) - return n, false, nil default: return nil, false, fmt.Errorf("unsupported sync type: %d", payload.Type) } diff --git a/pkg/store/flags.go b/pkg/store/flags.go index afefd24dd..1623b867f 100644 --- a/pkg/store/flags.go +++ b/pkg/store/flags.go @@ -25,7 +25,6 @@ func (f *Flags) hasPriority(stored string, new string) bool { if f.FlagSources[i] == stored { return false } else if f.FlagSources[i] == new { - return true } } @@ -175,7 +174,11 @@ func (f *Flags) DeleteFlags(logger *logger.Logger, source string, flags map[stri } // Merge provided flags from source with currently stored flags. -func (f *Flags) Merge(logger *logger.Logger, source string, flags map[string]model.Flag) (map[string]interface{}, bool) { +func (f *Flags) Merge( + logger *logger.Logger, + source string, + flags map[string]model.Flag, +) (map[string]interface{}, bool) { notifications := map[string]interface{}{} resyncRequired := false diff --git a/pkg/sync/file/filepath_sync.go b/pkg/sync/file/filepath_sync.go index 4d6f574eb..ff6bd706b 100644 --- a/pkg/sync/file/filepath_sync.go +++ b/pkg/sync/file/filepath_sync.go @@ -29,7 +29,7 @@ type Sync struct { const defaultState = "{}" func (fs *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error { - fs.sendDataSync(ctx, sync.RESYNC, dataSync) + fs.sendDataSync(ctx, sync.ALL, dataSync) return nil } diff --git a/pkg/sync/http/http_sync.go b/pkg/sync/http/http_sync.go index 603d09d6c..34bcd1e9c 100644 --- a/pkg/sync/http/http_sync.go +++ b/pkg/sync/http/http_sync.go @@ -42,7 +42,7 @@ func (hs *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error if err != nil { return err } - dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI, Type: sync.RESYNC} + dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI, Type: sync.ALL} return nil } diff --git a/pkg/sync/isync.go b/pkg/sync/isync.go index 732107584..56a442290 100644 --- a/pkg/sync/isync.go +++ b/pkg/sync/isync.go @@ -17,7 +17,6 @@ const ( // DELETE - Delete for flag(s) previously provided DELETE // DELETE - All flags of sync provider. Prevents the triggering of a second resync event - RESYNC ) func (t Type) String() string { @@ -30,8 +29,6 @@ func (t Type) String() string { return "UPDATE" case DELETE: return "DELETE" - case RESYNC: - return "RESYNC" default: return "UNKNOWN" } @@ -47,6 +44,7 @@ type ISync interface { Sync(ctx context.Context, dataSync chan<- DataSync) error // ReSync is used to validate the flag store following a delete event + // This method should trigger an ALL sync operation then exit ReSync(ctx context.Context, dataSync chan<- DataSync) error } From 73835d3d65e0116220ff99e15f9d4dbc445682e1 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 15 Feb 2023 13:32:53 +0000 Subject: [PATCH 09/23] doc Signed-off-by: James Milligan --- cmd/start.go | 5 +++-- docs/configuration/flagd_start.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index 94b830196..0c90a3670 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -46,8 +46,9 @@ func init() { "a", nil, "Sync provider arguments as key values separated by =") flags.StringSliceP( uriFlagName, "f", []string{}, "Set a sync provider uri to read data from, this can be a filepath,"+ - "url or FeatureFlagConfiguration. Using multiple providers is supported however if"+ - " flag keys are duplicated across multiple sources it may lead to unexpected behavior. "+ + "url or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the merge priority "+ + "follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, "+ + "with duplicated keys being overwritten by those from the uri at index 1. "+ "Please note that if you are using filepath, flagd only supports files with `.yaml/.yml/.json` extension.", ) flags.StringP( diff --git a/docs/configuration/flagd_start.md b/docs/configuration/flagd_start.md index 1c14f76d2..bd9a97433 100644 --- a/docs/configuration/flagd_start.md +++ b/docs/configuration/flagd_start.md @@ -21,7 +21,7 @@ flagd start [flags] -d, --socket-path string Flagd socket path. With grpc the service will become available on this address. With http(s) the grpc-gateway proxy will use this address internally. -y, --sync-provider string DEPRECATED: Set a sync provider e.g. filepath or remote -a, --sync-provider-args stringToString Sync provider arguments as key values separated by = (default []) - -f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath,url or FeatureFlagConfiguration. Using multiple providers is supported however if flag keys are duplicated across multiple sources it may lead to unexpected behavior. Please note that if you are using filepath, flagd only supports files with .yaml/.yml/.json extension. + -f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath,url or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. Please note that if you are using filepath, flagd only supports files with `.yaml/.yml/.json` extension. ``` ### Options inherited from parent commands From 3451cc108efd502831d317aebb610c4fa46ad4d9 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Tue, 21 Feb 2023 11:20:58 +0000 Subject: [PATCH 10/23] increased test coverage Signed-off-by: James Milligan --- docs/configuration/flagd_start.md | 2 +- pkg/store/flags_test.go | 32 +++++++++++ pkg/sync/http/http_sync_test.go | 94 +++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/docs/configuration/flagd_start.md b/docs/configuration/flagd_start.md index bd9a97433..0a2809014 100644 --- a/docs/configuration/flagd_start.md +++ b/docs/configuration/flagd_start.md @@ -21,7 +21,7 @@ flagd start [flags] -d, --socket-path string Flagd socket path. With grpc the service will become available on this address. With http(s) the grpc-gateway proxy will use this address internally. -y, --sync-provider string DEPRECATED: Set a sync provider e.g. filepath or remote -a, --sync-provider-args stringToString Sync provider arguments as key values separated by = (default []) - -f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath,url or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. Please note that if you are using filepath, flagd only supports files with `.yaml/.yml/.json` extension. + -f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath,url or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. Please note that if you are using filepath, flagd only supports files with .yaml/.yml/.json extension. ``` ### Options inherited from parent commands diff --git a/pkg/store/flags_test.go b/pkg/store/flags_test.go index 6e52d0d04..9687bb36f 100644 --- a/pkg/store/flags_test.go +++ b/pkg/store/flags_test.go @@ -130,6 +130,38 @@ func TestMergeFlags(t *testing.T) { }, wantResync: true, }, + { + name: "no merge priority", + current: &Flags{ + FlagSources: []string{ + "B", + "A", + }, + Flags: map[string]model.Flag{ + "hello": { + DefaultVariant: "off", + Source: "A", + }, + }, + }, + new: map[string]model.Flag{ + "hello": {DefaultVariant: "off"}, + }, + newSource: "B", + want: &Flags{ + FlagSources: []string{ + "B", + "A", + }, + Flags: map[string]model.Flag{ + "hello": { + DefaultVariant: "off", + Source: "A", + }, + }, + }, + wantNotifs: map[string]interface{}{}, + }, } for _, tt := range tests { diff --git a/pkg/sync/http/http_sync_test.go b/pkg/sync/http/http_sync_test.go index 9689d74c1..0877a2596 100644 --- a/pkg/sync/http/http_sync_test.go +++ b/pkg/sync/http/http_sync_test.go @@ -5,8 +5,10 @@ import ( "io" "log" "net/http" + "reflect" "strings" "testing" + "time" "github.com/open-feature/flagd/pkg/sync" @@ -153,3 +155,95 @@ func TestHTTPSync_Fetch(t *testing.T) { }) } } + +func TestHTTPSync_Resync(t *testing.T) { + ctrl := gomock.NewController(t) + + tests := map[string]struct { + setup func(t *testing.T, client *syncmock.MockClient) + uri string + bearerToken string + lastBodySHA string + handleResponse func(*testing.T, Sync, string, error) + wantErr bool + wantNotifications []sync.DataSync + }{ + "success": { + setup: func(t *testing.T, client *syncmock.MockClient) { + client.EXPECT().Do(gomock.Any()).Return(&http.Response{ + Body: io.NopCloser(strings.NewReader("test response")), + }, nil) + }, + uri: "http://localhost", + handleResponse: func(t *testing.T, _ Sync, fetched string, err error) { + if err != nil { + t.Fatalf("fetch: %v", err) + } + expected := "test response" + if fetched != expected { + t.Errorf("expected fetched to be: '%s', got: '%s'", expected, fetched) + } + }, + wantErr: false, + wantNotifications: []sync.DataSync{ + { + Type: sync.ALL, + FlagData: "", + Source: "", + }, + }, + }, + "error response": { + setup: func(t *testing.T, client *syncmock.MockClient) {}, + handleResponse: func(t *testing.T, _ Sync, fetched string, err error) { + if err == nil { + t.Error("expected err, got nil") + } + }, + wantErr: true, + wantNotifications: []sync.DataSync{}, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + mockClient := syncmock.NewMockClient(ctrl) + + d := make(chan sync.DataSync, len(tt.wantNotifications)) + + tt.setup(t, mockClient) + + httpSync := Sync{ + URI: tt.uri, + Client: mockClient, + BearerToken: tt.bearerToken, + LastBodySHA: tt.lastBodySHA, + Logger: logger.NewLogger(nil, false), + } + + err := httpSync.ReSync(context.Background(), d) + if tt.wantErr && err == nil { + t.Errorf("got no error for %s", name) + } + if !tt.wantErr && err != nil { + t.Errorf("got error for %s %s", name, err.Error()) + } + for _, dataSync := range tt.wantNotifications { + select { + case x := <-d: + if !reflect.DeepEqual(x.String(), dataSync.String()) { + t.Error("unexpected datasync received", x, dataSync) + } + case <-time.After(2 * time.Second): + t.Error("expected datasync not received", dataSync) + } + } + select { + case x := <-d: + t.Error("unexpected datasync received", x) + case <-time.After(2 * time.Second): + + } + }) + } +} From d39e954bd66d7b263edfad6e269672cc642e6523 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 22 Feb 2023 11:31:01 +0000 Subject: [PATCH 11/23] grpc resync Signed-off-by: James Milligan --- go.mod | 56 ++++++++++++------------- go.sum | 73 +++++++++++++++++++++++++++++++++ pkg/sync/grpc/grpc_sync.go | 56 +++++++++++++++++-------- pkg/sync/grpc/grpc_sync_test.go | 4 ++ 4 files changed, 144 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index b8f092191..100c2b63d 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/open-feature/flagd go 1.19 require ( - buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.4.1-20221226184428-0dc62ff103b8.1 - buf.build/gen/go/open-feature/flagd/grpc/go v1.2.0-20230207182158-c211472558c3.4 - buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20230207182158-c211472558c3.4 + buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.5.2-20230222100723-491ee098dd92.1 + buf.build/gen/go/open-feature/flagd/grpc/go v1.2.0-20230222100723-491ee098dd92.4 + buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20230222100723-491ee098dd92.4 github.com/bufbuild/connect-go v1.5.2 github.com/diegoholiveira/jsonlogic/v3 v3.2.7 github.com/dimiro1/banner v1.1.0 @@ -38,35 +38,35 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2 // indirect + github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/go-logr/logr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/imdario/mergo v0.3.12 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.6 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect - github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -74,35 +74,35 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/common v0.40.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect k8s.io/component-base v0.26.1 // indirect - k8s.io/klog/v2 v2.80.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + k8s.io/klog/v2 v2.90.0 // indirect + k8s.io/kube-openapi v0.0.0-20230217203603-ff9a8e8fa21d // indirect + k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 0808f1b3c..39f3953b4 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,16 @@ buf.build/gen/go/grpc-ecosystem/grpc-gateway/grpc/go v1.2.0-20220906183531-bc28b buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20220906183531-bc28b723cd77.4/go.mod h1:92ejKVTiuvnKoAtRlpJpIxKfloI935DDqhs0NCRx+KM= buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.4.1-20221226184428-0dc62ff103b8.1 h1:KoSPqmHyi3x27tPFLQ994CJjG4qc59v+0gbxY9+VXso= buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.4.1-20221226184428-0dc62ff103b8.1/go.mod h1:68WGv4z/jXuTS3G7FEFQTEw4wiMmulBSX6BlSFX2Xc8= +buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.5.2-20230222100723-491ee098dd92.1 h1:OGqJyjvn5VQsctIQEwB7JP8dR1mtN/G6LOTntrKzd+Y= +buf.build/gen/go/open-feature/flagd/bufbuild/connect-go v1.5.2-20230222100723-491ee098dd92.1/go.mod h1:wgQMLfjCfOMyzfdWA8HNnD1SA5HWyIJWPpJTc7dsZgw= buf.build/gen/go/open-feature/flagd/grpc/go v1.2.0-20230207182158-c211472558c3.4 h1:11ayeHd1H1LhRuJlHzIfbcUk64gAtnm5zrBZXxoOyN0= buf.build/gen/go/open-feature/flagd/grpc/go v1.2.0-20230207182158-c211472558c3.4/go.mod h1:8ce/bdmiPVo2i5s+bYLENyIvi24dmBN5zy+nPyCUAHg= +buf.build/gen/go/open-feature/flagd/grpc/go v1.2.0-20230222100723-491ee098dd92.4 h1:uXtx0Mi97Q3Phrhjk7VzHBBMHLaKlC7DeoqtfTBKgGU= +buf.build/gen/go/open-feature/flagd/grpc/go v1.2.0-20230222100723-491ee098dd92.4/go.mod h1:Spunk7gggxKwU2TWOmOQB0M3ZsvxctOqbhc0k9+64GM= buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20230207182158-c211472558c3.4 h1:6Ht0iYYWoG7qDuxGs/aG11uj9ulFfTL2zeWCryj9aWg= buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20230207182158-c211472558c3.4/go.mod h1:+Bnrjo56uVn/aBcLWchTveR8UeCj+KSJN4fE0xSmBNc= +buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20230222100723-491ee098dd92.4 h1:4ktM86Sav99JsrBM2U1UcPCgOUz0zMeWcq/I5ZyIig0= +buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.28.1-20230222100723-491ee098dd92.4/go.mod h1:+Bnrjo56uVn/aBcLWchTveR8UeCj+KSJN4fE0xSmBNc= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -46,6 +52,7 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -61,7 +68,10 @@ github.com/bufbuild/connect-go v1.5.1 h1:ORhrSiu63hWxtuMmC/V1mKySSRhEySsW5RkHJcy github.com/bufbuild/connect-go v1.5.1/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= github.com/bufbuild/connect-go v1.5.2 h1:G4EZd5gF1U1ZhhbVJXplbuUnfKpBZ5j5izqIwu2g2W8= github.com/bufbuild/connect-go v1.5.2/go.mod h1:GmMJYR6orFqD0Y6ZgX8pwQ8j9baizDrIQMm1/a6LnHk= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -74,11 +84,14 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2 h1:tjT4Jp4gxECvsJcYpAMtW2I3YqzBTPuB67OejxXs86s= github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -92,17 +105,21 @@ github.com/dimiro1/banner v1.1.0/go.mod h1:tbL318TJiUaHxOUNN+jnlvFSgsh/RX7iJaQrG github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -125,11 +142,17 @@ github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -170,6 +193,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -185,6 +210,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -214,8 +241,12 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -233,12 +264,15 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -250,6 +284,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -257,15 +293,23 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -314,6 +358,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= +github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -321,6 +367,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -335,6 +383,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -366,6 +415,8 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8 github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= @@ -391,6 +442,8 @@ go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -470,6 +523,7 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= @@ -489,6 +543,8 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -551,6 +607,7 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= @@ -565,6 +622,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= @@ -695,8 +753,11 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 h1:muK+gVBJBfFb4SejshDBlN2/UgxCCOKH9Y34ljqEGOc= +google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -715,6 +776,7 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= @@ -730,6 +792,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -738,6 +801,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -755,6 +819,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -776,10 +841,16 @@ k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= +k8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kube-openapi v0.0.0-20230217203603-ff9a8e8fa21d h1:oFDpQ7FfzinCtrFOl4izwOWsdTprlS2A9IXBENMW0UA= +k8s.io/kube-openapi v0.0.0-20230217203603-ff9a8e8fa21d/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= @@ -787,6 +858,8 @@ sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengb sigs.k8s.io/controller-runtime v0.14.4/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= diff --git a/pkg/sync/grpc/grpc_sync.go b/pkg/sync/grpc/grpc_sync.go index 20d96f485..dc67ce86e 100644 --- a/pkg/sync/grpc/grpc_sync.go +++ b/pkg/sync/grpc/grpc_sync.go @@ -7,8 +7,6 @@ import ( "strings" "time" - "google.golang.org/grpc/credentials/insecure" - "buf.build/gen/go/open-feature/flagd/grpc/go/sync/v1/syncv1grpc" v1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/sync/v1" @@ -31,25 +29,51 @@ const ( ) type Sync struct { - Target string - ProviderID string - Logger *logger.Logger + Target string + ProviderID string + Logger *logger.Logger + client syncv1grpc.FlagSyncServiceClient + DialOptions []grpc.DialOption } -func (g *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { - options := []grpc.DialOption{ - grpc.WithTransportCredentials(insecure.NewCredentials()), +func (g *Sync) connectClient(ctx context.Context) error { + // initial dial and connection. Failure here must result in a startup failure + dial, err := grpc.DialContext(ctx, g.Target, g.DialOptions...) + if err != nil { + return err } - // initial dial and connection. Failure here must result in a startup failure - dial, err := grpc.DialContext(ctx, g.Target, options...) + g.client = syncv1grpc.NewFlagSyncServiceClient(dial) + return nil +} + +func (g *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error { + if g.client == nil { + if err := g.connectClient(ctx); err != nil { + g.Logger.Error(fmt.Sprintf("error establishing grpc connection: %s", err.Error())) + return err + } + } + res, err := g.client.FetchAllFlags(ctx, &v1.FetchAllFlagsRequest{}) if err != nil { + g.Logger.Error(fmt.Sprintf("error fetching all flags: %s", err.Error())) + return err + } + dataSync <- sync.DataSync{ + FlagData: res.GetFlagConfiguration(), + Source: g.Target, + Type: sync.ALL, + } + return nil +} + +func (g *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { + if err := g.connectClient(ctx); err != nil { g.Logger.Error(fmt.Sprintf("error establishing grpc connection: %s", err.Error())) return err } - serviceClient := syncv1grpc.NewFlagSyncServiceClient(dial) - syncClient, err := serviceClient.SyncFlags(ctx, &v1.SyncFlagsRequest{ProviderId: g.ProviderID}) + syncClient, err := g.client.SyncFlags(ctx, &v1.SyncFlagsRequest{ProviderId: g.ProviderID}) if err != nil { g.Logger.Error(fmt.Sprintf("error calling streaming operation: %s", err.Error())) return err @@ -61,7 +85,7 @@ func (g *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { // retry connection establishment for { - syncClient, ok := g.connectWithRetry(ctx, options...) + syncClient, ok := g.connectWithRetry(ctx) if !ok { // We shall exit return nil @@ -104,14 +128,12 @@ func (g *Sync) connectWithRetry( g.Logger.Warn(fmt.Sprintf("connection re-establishment attempt in-progress for grpc target: %s", g.Target)) - dial, err := grpc.DialContext(ctx, g.Target, options...) - if err != nil { + if err := g.connectClient(ctx); err != nil { g.Logger.Debug(fmt.Sprintf("error dialing target: %s", err.Error())) continue } - serviceClient := syncv1grpc.NewFlagSyncServiceClient(dial) - syncClient, err := serviceClient.SyncFlags(ctx, &v1.SyncFlagsRequest{ProviderId: g.ProviderID}) + syncClient, err := g.client.SyncFlags(ctx, &v1.SyncFlagsRequest{ProviderId: g.ProviderID}) if err != nil { g.Logger.Debug(fmt.Sprintf("error opening service client: %s", err.Error())) continue diff --git a/pkg/sync/grpc/grpc_sync_test.go b/pkg/sync/grpc/grpc_sync_test.go index d521372c8..5cf0acb96 100644 --- a/pkg/sync/grpc/grpc_sync_test.go +++ b/pkg/sync/grpc/grpc_sync_test.go @@ -328,3 +328,7 @@ func (b *bufferedServer) SyncFlags(req *v1.SyncFlagsRequest, stream syncv1grpc.F return nil } + +func (b *bufferedServer) FetchAllFlags(ctx context.Context, req *v1.FetchAllFlagsRequest) (*v1.FetchAllFlagsResponse, error) { + return nil, nil +} From a16b10c69c4aa86e56fad2dc4c74f0aa3c1da5e8 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 22 Feb 2023 11:47:34 +0000 Subject: [PATCH 12/23] cleanup Signed-off-by: James Milligan --- cmd/start.go | 6 +++--- docs/configuration/flagd_start.md | 2 +- pkg/sync/grpc/grpc_sync.go | 2 +- pkg/sync/http/http_sync_test.go | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index 46f8d6d58..e15fcd30c 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -46,9 +46,9 @@ func init() { "a", nil, "Sync provider arguments as key values separated by =") flags.StringSliceP( uriFlagName, "f", []string{}, "Set a sync provider uri to read data from, this can be a filepath,"+ - "url (http and grpc) or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the merge priority "+ - "follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, "+ - "with duplicated keys being overwritten by those from the uri at index 1. "+ + "url (http and grpc) or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the "+ + "merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the "+ + "lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. "+ "Please note that if you are using filepath, flagd only supports files with `.yaml/.yml/.json` extension.", ) flags.StringP( diff --git a/docs/configuration/flagd_start.md b/docs/configuration/flagd_start.md index 0cc8945f3..4bf22b745 100644 --- a/docs/configuration/flagd_start.md +++ b/docs/configuration/flagd_start.md @@ -21,7 +21,7 @@ flagd start [flags] -d, --socket-path string Flagd socket path. With grpc the service will become available on this address. With http(s) the grpc-gateway proxy will use this address internally. -y, --sync-provider string DEPRECATED: Set a sync provider e.g. filepath or remote -a, --sync-provider-args stringToString Sync provider arguments as key values separated by = (default []) - -f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath,url (http and grpc) or FeatureFlagConfiguration. Using multiple providers is supported however if flag keys are duplicated across multiple sources it may lead to unexpected behavior. Please note that if you are using filepath, flagd only supports files with .yaml/.yml/.json extension. + -f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath,url (http and grpc) or FeatureFlagConfiguration. When flag keys are duplicated across multiple providers the merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. Please note that if you are using filepath, flagd only supports files with .yaml/.yml/.json extension. ``` ### Options inherited from parent commands diff --git a/pkg/sync/grpc/grpc_sync.go b/pkg/sync/grpc/grpc_sync.go index dc67ce86e..04ccde279 100644 --- a/pkg/sync/grpc/grpc_sync.go +++ b/pkg/sync/grpc/grpc_sync.go @@ -104,7 +104,7 @@ func (g *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { // internally. However, if the provided context is done, method exit with a non-ok state which must be verified by the // caller func (g *Sync) connectWithRetry( - ctx context.Context, options ...grpc.DialOption, + ctx context.Context, ) (syncv1grpc.FlagSyncService_SyncFlagsClient, bool) { var iteration int diff --git a/pkg/sync/http/http_sync_test.go b/pkg/sync/http/http_sync_test.go index 0877a2596..c110c7d47 100644 --- a/pkg/sync/http/http_sync_test.go +++ b/pkg/sync/http/http_sync_test.go @@ -242,7 +242,6 @@ func TestHTTPSync_Resync(t *testing.T) { case x := <-d: t.Error("unexpected datasync received", x) case <-time.After(2 * time.Second): - } }) } From 533435673b18e463108e398ff0f081d240bdb8ba Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 22 Feb 2023 13:43:55 +0000 Subject: [PATCH 13/23] introduce hasPriority unit tests Signed-off-by: James Milligan --- pkg/store/flags_test.go | 62 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pkg/store/flags_test.go b/pkg/store/flags_test.go index 9687bb36f..35237756b 100644 --- a/pkg/store/flags_test.go +++ b/pkg/store/flags_test.go @@ -9,6 +9,68 @@ import ( "github.com/stretchr/testify/require" ) +func TestHasPriority(t *testing.T) { + tests := []struct { + name string + currentState *Flags + storedSource string + newSource string + hasPriority bool + }{ + { + name: "same source", + currentState: &Flags{}, + storedSource: "A", + newSource: "A", + hasPriority: true, + }, + { + name: "no priority", + currentState: &Flags{ + FlagSources: []string{ + "B", + "A", + }, + }, + storedSource: "A", + newSource: "B", + hasPriority: false, + }, + { + name: "priority", + currentState: &Flags{ + FlagSources: []string{ + "A", + "B", + }, + }, + storedSource: "B", + newSource: "A", + hasPriority: false, + }, + { + name: "not in sources", + currentState: &Flags{ + FlagSources: []string{ + "A", + "B", + }, + }, + storedSource: "C", + newSource: "D", + hasPriority: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + p := tt.currentState.hasPriority(tt.storedSource, tt.newSource) + require.Equal(t, p, tt.hasPriority) + }) + } +} + func TestMergeFlags(t *testing.T) { t.Parallel() tests := []struct { From ed74aa757edbcf11f145496fe9e7de9bc8d78c07 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 22 Feb 2023 15:20:24 +0000 Subject: [PATCH 14/23] extended tests Signed-off-by: James Milligan --- pkg/sync/grpc/grpc_sync_test.go | 112 +++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/pkg/sync/grpc/grpc_sync_test.go b/pkg/sync/grpc/grpc_sync_test.go index 5cf0acb96..396c8a3b9 100644 --- a/pkg/sync/grpc/grpc_sync_test.go +++ b/pkg/sync/grpc/grpc_sync_test.go @@ -2,6 +2,7 @@ package grpc import ( "context" + "errors" "fmt" "io" "log" @@ -18,6 +19,109 @@ import ( "google.golang.org/grpc/test/bufconn" ) +var reSyncTestResponses = map[string]struct { + res string + err error +}{ + "happy-path": { + res: "success", + err: nil, + }, +} + +func Test_ReSyncTests(t *testing.T) { + const target = "localBufCon" + + tests := []struct { + name string + res *v1.FetchAllFlagsResponse + err error + shouldError bool + notifications []sync.DataSync + }{ + { + name: "happy-path", + res: &v1.FetchAllFlagsResponse{ + FlagConfiguration: "success", + }, + notifications: []sync.DataSync{ + { + FlagData: "success", + Type: sync.ALL, + }, + }, + shouldError: false, + }, + { + name: "happy-path", + res: &v1.FetchAllFlagsResponse{}, + err: errors.New("internal disaster"), + notifications: []sync.DataSync{}, + shouldError: true, + }, + } + + for _, test := range tests { + bufCon := bufconn.Listen(5) + + bufServer := bufferedServer{ + listener: bufCon, + fetchAllFlagsResponse: test.res, + fetchAllFlagsError: test.err, + } + + // start server + go serve(&bufServer) + + // initialize client + dial, err := grpc.Dial(target, + grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { + return bufCon.Dial() + }), + grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + t.Errorf("Error setting up client connection: %s", err.Error()) + } + + c := syncv1grpc.NewFlagSyncServiceClient(dial) + + grpcSync := Sync{ + Target: target, + ProviderID: "", + Logger: logger.NewLogger(nil, false), + client: c, + } + + syncChan := make(chan sync.DataSync, 1) + + err = grpcSync.ReSync(context.Background(), syncChan) + if test.shouldError && err == nil { + t.Errorf("test %s should have returned error but did not", test.name) + } + if !test.shouldError && err != nil { + t.Errorf("test %s should not have returned error, but did: %s", test.name, err.Error()) + } + + for _, expected := range test.notifications { + out := <-syncChan + fmt.Println(out.Type) + if expected.Type != out.Type { + t.Errorf("Returned sync type = %v, wanted %v", out.Type, expected.Type) + } + + if expected.FlagData != out.FlagData { + t.Errorf("Returned sync data = %v, wanted %v", out.FlagData, expected.FlagData) + } + } + + // channel must be empty + if len(syncChan) != 0 { + t.Errorf("Data sync channel must be empty after all test syncs. But received non empty: %d", len(syncChan)) + } + } + +} + func TestUrlToGRPCTarget(t *testing.T) { tests := []struct { name string @@ -310,8 +414,10 @@ type serverPayload struct { // bufferedServer - a mock grpc service backed by buffered connection type bufferedServer struct { - listener *bufconn.Listener - mockResponses []serverPayload + listener *bufconn.Listener + mockResponses []serverPayload + fetchAllFlagsResponse *v1.FetchAllFlagsResponse + fetchAllFlagsError error } func (b *bufferedServer) SyncFlags(req *v1.SyncFlagsRequest, stream syncv1grpc.FlagSyncService_SyncFlagsServer) error { @@ -330,5 +436,5 @@ func (b *bufferedServer) SyncFlags(req *v1.SyncFlagsRequest, stream syncv1grpc.F } func (b *bufferedServer) FetchAllFlags(ctx context.Context, req *v1.FetchAllFlagsRequest) (*v1.FetchAllFlagsResponse, error) { - return nil, nil + return b.fetchAllFlagsResponse, b.fetchAllFlagsError } From 53e1d766444bdb7fe51820f9097a926044c415a4 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Mon, 27 Feb 2023 11:00:05 +0000 Subject: [PATCH 15/23] test coverage Signed-off-by: James Milligan --- pkg/eval/json_evaluator_test.go | 46 ++++++++++++++++++++++++- pkg/sync/file/filepath_sync_test.go | 52 +++++++++++++++++++++++++++++ pkg/sync/grpc/grpc_sync_test.go | 11 ------ 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/pkg/eval/json_evaluator_test.go b/pkg/eval/json_evaluator_test.go index 2f7545ebb..06cca9421 100644 --- a/pkg/eval/json_evaluator_test.go +++ b/pkg/eval/json_evaluator_test.go @@ -852,8 +852,10 @@ func TestSetState_DefaultVariantValidation(t *testing.T) { func TestState_Evaluator(t *testing.T) { tests := map[string]struct { inputState string + inputSyncType sync.Type expectedOutputState string expectedError bool + expectedResync bool }{ "success": { inputState: ` @@ -886,6 +888,7 @@ func TestState_Evaluator(t *testing.T) { } } `, + inputSyncType: sync.ALL, expectedOutputState: ` { "flags": { @@ -945,6 +948,7 @@ func TestState_Evaluator(t *testing.T) { } } `, + inputSyncType: sync.ALL, expectedOutputState: ` { "flags": { @@ -1000,6 +1004,7 @@ func TestState_Evaluator(t *testing.T) { } } `, + inputSyncType: sync.ALL, expectedError: true, }, "empty evaluator": { @@ -1029,25 +1034,64 @@ func TestState_Evaluator(t *testing.T) { } } `, + inputSyncType: sync.ALL, expectedError: true, }, + "unexpected sync type": { + inputState: ` + { + "flags": { + "fibAlgo": { + "variants": { + "recursive": "recursive", + "memo": "memo", + "loop": "loop", + "binet": "binet" + }, + "defaultVariant": "recursive", + "state": "ENABLED", + "targeting": { + "if": [ + { + "$ref": "emailWithFaas" + }, "binet", null + ] + } + } + }, + "$evaluators": { + "emailWithFaas": "" + } + } + `, + inputSyncType: 999, + expectedError: true, + expectedResync: false, + }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { jsonEvaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) - _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState}) + _, resync, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState}) if err != nil { if !tt.expectedError { t.Error(err) } + if resync != tt.expectedResync { + t.Errorf("expected resync %t got %t", tt.expectedResync, resync) + } return } else if tt.expectedError { t.Error("expected error, got nil") return } + if resync != tt.expectedResync { + t.Errorf("expected resync %t got %t", tt.expectedResync, resync) + } + got, err := jsonEvaluator.GetState() if err != nil { t.Error(err) diff --git a/pkg/sync/file/filepath_sync_test.go b/pkg/sync/file/filepath_sync_test.go index 690a6b1fb..a31b1f3a7 100644 --- a/pkg/sync/file/filepath_sync_test.go +++ b/pkg/sync/file/filepath_sync_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "reflect" "testing" "time" @@ -19,6 +20,57 @@ const ( fetchFileContents = "fetch me" ) +func TestSimpleReSync(t *testing.T) { + tests := map[string]struct { + fileContents string + expectedDataSync sync.DataSync + }{ + "simple-read": { + fileContents: "hello", + expectedDataSync: sync.DataSync{ + FlagData: "hello", + Source: fmt.Sprintf("%s/%s", fetchDirName, fetchFileName), + Type: sync.ALL, + }, + }, + } + + handler := Sync{ + URI: fmt.Sprintf("%s/%s", fetchDirName, fetchFileName), + Logger: logger.NewLogger(nil, false), + Source: fmt.Sprintf("%s/%s", fetchDirName, fetchFileName), + } + + for test, tt := range tests { + t.Run(test, func(t *testing.T) { + defer t.Cleanup(cleanupFilePath) + setupDir(t, fetchDirName) + createFile(t, fetchDirName, fetchFileName) + writeToFile(t, tt.fileContents) + + ctx := context.Background() + dataSyncChan := make(chan sync.DataSync, 1) + + go func() { + err := handler.ReSync(ctx, dataSyncChan) + if err != nil { + log.Fatalf("Error start sync: %s", err.Error()) + return + } + }() + + select { + case s := <-dataSyncChan: + if !reflect.DeepEqual(tt.expectedDataSync, s) { + t.Errorf("resync failed, incorrect datasync value, got %v want %v", s, tt.expectedDataSync) + } + case <-time.After(5 * time.Second): + t.Error("timed out waiting for datasync") + } + }) + } +} + func TestSimpleSync(t *testing.T) { tests := map[string]struct { manipulationFuncs []func(t *testing.T) diff --git a/pkg/sync/grpc/grpc_sync_test.go b/pkg/sync/grpc/grpc_sync_test.go index 396c8a3b9..ac8364be9 100644 --- a/pkg/sync/grpc/grpc_sync_test.go +++ b/pkg/sync/grpc/grpc_sync_test.go @@ -19,16 +19,6 @@ import ( "google.golang.org/grpc/test/bufconn" ) -var reSyncTestResponses = map[string]struct { - res string - err error -}{ - "happy-path": { - res: "success", - err: nil, - }, -} - func Test_ReSyncTests(t *testing.T) { const target = "localBufCon" @@ -119,7 +109,6 @@ func Test_ReSyncTests(t *testing.T) { t.Errorf("Data sync channel must be empty after all test syncs. But received non empty: %d", len(syncChan)) } } - } func TestUrlToGRPCTarget(t *testing.T) { From d2085a3d5fb378a9935c8e9dd52bdc74a5c66979 Mon Sep 17 00:00:00 2001 From: James Milligan <75740990+james-milligan@users.noreply.github.com> Date: Mon, 27 Feb 2023 16:54:55 +0000 Subject: [PATCH 16/23] Update pkg/store/flags_test.go Co-authored-by: Todd Baert Signed-off-by: James Milligan <75740990+james-milligan@users.noreply.github.com> --- pkg/store/flags_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/store/flags_test.go b/pkg/store/flags_test.go index 35237756b..9b2e26e5b 100644 --- a/pkg/store/flags_test.go +++ b/pkg/store/flags_test.go @@ -44,9 +44,9 @@ func TestHasPriority(t *testing.T) { "B", }, }, - storedSource: "B", - newSource: "A", - hasPriority: false, + storedSource: "A", + newSource: "B", + hasPriority: true, }, { name: "not in sources", From f64d08c7b472755fca04b7726a82dba5d4d4389e Mon Sep 17 00:00:00 2001 From: James Milligan Date: Mon, 27 Feb 2023 16:55:50 +0000 Subject: [PATCH 17/23] remove old comment Signed-off-by: James Milligan --- pkg/sync/isync.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/sync/isync.go b/pkg/sync/isync.go index 56a442290..04ca538aa 100644 --- a/pkg/sync/isync.go +++ b/pkg/sync/isync.go @@ -16,7 +16,6 @@ const ( UPDATE // DELETE - Delete for flag(s) previously provided DELETE - // DELETE - All flags of sync provider. Prevents the triggering of a second resync event ) func (t Type) String() string { From c9533080219471d9c0bd2cbca179492634488c14 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Mon, 27 Feb 2023 16:56:43 +0000 Subject: [PATCH 18/23] generalised comment Signed-off-by: James Milligan --- pkg/sync/isync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sync/isync.go b/pkg/sync/isync.go index 04ca538aa..cab5ee51a 100644 --- a/pkg/sync/isync.go +++ b/pkg/sync/isync.go @@ -42,7 +42,7 @@ type ISync interface { // Note that, it is expected to return the first data sync as soon as possible to fill the store. Sync(ctx context.Context, dataSync chan<- DataSync) error - // ReSync is used to validate the flag store following a delete event + // ReSync is used to fetch the full flag configuration from the sync // This method should trigger an ALL sync operation then exit ReSync(ctx context.Context, dataSync chan<- DataSync) error } From f2ac8397412491fee41183cf6ac982808a8e33b5 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Tue, 28 Feb 2023 10:03:21 +0000 Subject: [PATCH 19/23] conflicts fix Signed-off-by: James Milligan --- go.mod | 6 ++-- pkg/sync/file/filepath_sync_test.go | 1 + pkg/sync/grpc/grpc_sync.go | 40 +++++++++++--------------- pkg/sync/grpc/grpc_sync_test.go | 4 +-- pkg/sync/kubernetes/kubernetes_sync.go | 2 +- 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 7f1cd6b12..d7dddb198 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( go.opentelemetry.io/otel v1.13.0 go.opentelemetry.io/otel/exporters/prometheus v0.36.0 go.opentelemetry.io/otel/metric v0.36.0 + go.opentelemetry.io/otel/sdk v1.13.0 go.opentelemetry.io/otel/sdk/metric v0.36.0 go.uber.org/zap v1.24.0 golang.org/x/net v0.7.0 @@ -53,11 +54,9 @@ require ( github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -102,7 +101,6 @@ require ( github.com/subosito/gotenv v1.4.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - go.opentelemetry.io/otel/sdk v1.13.0 // indirect go.opentelemetry.io/otel/trace v1.13.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect diff --git a/pkg/sync/file/filepath_sync_test.go b/pkg/sync/file/filepath_sync_test.go index 9c459ea57..edc84b9f9 100644 --- a/pkg/sync/file/filepath_sync_test.go +++ b/pkg/sync/file/filepath_sync_test.go @@ -152,6 +152,7 @@ func TestSimpleSync(t *testing.T) { URI: fmt.Sprintf("%s/%s", fetchDirName, fetchFileName), Logger: logger.NewLogger(nil, false), Mux: &msync.RWMutex{}, + Source: fmt.Sprintf("%s/%s", fetchDirName, fetchFileName), } err := handler.Init(ctx) if err != nil { diff --git a/pkg/sync/grpc/grpc_sync.go b/pkg/sync/grpc/grpc_sync.go index 170b9176a..02b7071f2 100644 --- a/pkg/sync/grpc/grpc_sync.go +++ b/pkg/sync/grpc/grpc_sync.go @@ -14,6 +14,7 @@ import ( "github.com/open-feature/flagd/pkg/logger" "github.com/open-feature/flagd/pkg/sync" "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) const ( @@ -33,7 +34,8 @@ type Sync struct { Target string ProviderID string Logger *logger.Logger - client syncv1grpc.FlagSyncService_SyncFlagsClient + syncClient syncv1grpc.FlagSyncService_SyncFlagsClient + client syncv1grpc.FlagSyncServiceClient options []grpc.DialOption ready bool Mux *msync.RWMutex @@ -41,22 +43,23 @@ type Sync struct { func (g *Sync) connectClient(ctx context.Context) error { // initial dial and connection. Failure here must result in a startup failure - dial, err := grpc.DialContext(ctx, g.Target, g.DialOptions...) + dial, err := grpc.DialContext(ctx, g.Target, g.options...) if err != nil { return err } g.client = syncv1grpc.NewFlagSyncServiceClient(dial) + + syncClient, err := g.client.SyncFlags(ctx, &v1.SyncFlagsRequest{ProviderId: g.ProviderID}) + if err != nil { + g.Logger.Error(fmt.Sprintf("error calling streaming operation: %s", err.Error())) + return err + } + g.syncClient = syncClient return nil } func (g *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error { - if g.client == nil { - if err := g.connectClient(ctx); err != nil { - g.Logger.Error(fmt.Sprintf("error establishing grpc connection: %s", err.Error())) - return err - } - } res, err := g.client.FetchAllFlags(ctx, &v1.FetchAllFlagsRequest{}) if err != nil { g.Logger.Error(fmt.Sprintf("error fetching all flags: %s", err.Error())) @@ -76,19 +79,7 @@ func (g *Sync) Init(ctx context.Context) error { } // initial dial and connection. Failure here must result in a startup failure - dial, err := grpc.DialContext(ctx, g.Target, g.options...) - if err != nil { - g.Logger.Error(fmt.Sprintf("error establishing grpc connection: %s", err.Error())) - return err - } - - syncClient, err := g.client.SyncFlags(ctx, &v1.SyncFlagsRequest{ProviderId: g.ProviderID}) - if err != nil { - g.Logger.Error(fmt.Sprintf("error calling streaming operation: %s", err.Error())) - return err - } - g.client = syncClient - return nil + return g.connectClient(ctx) } func (g *Sync) IsReady() bool { @@ -106,12 +97,15 @@ func (g *Sync) setReady(val bool) { func (g *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error { // initial stream listening g.setReady(true) - err := g.handleFlagSync(g.client, dataSync) + err := g.handleFlagSync(g.syncClient, dataSync) + if err == nil { + return nil + } g.Logger.Warn(fmt.Sprintf("error with stream listener: %s", err.Error())) // retry connection establishment for { g.setReady(false) - syncClient, ok := g.connectWithRetry(ctx, g.options...) + syncClient, ok := g.connectWithRetry(ctx) if !ok { // We shall exit return nil diff --git a/pkg/sync/grpc/grpc_sync_test.go b/pkg/sync/grpc/grpc_sync_test.go index 34dc8da14..811731647 100644 --- a/pkg/sync/grpc/grpc_sync_test.go +++ b/pkg/sync/grpc/grpc_sync_test.go @@ -208,7 +208,7 @@ func TestSync_BasicFlagSyncStates(t *testing.T) { syncChan := make(chan sync.DataSync) go func() { - grpcSyncImpl.client = test.stream + grpcSyncImpl.syncClient = test.stream err := grpcSyncImpl.Sync(context.TODO(), syncChan) if err != nil { t.Errorf("Error handling flag sync: %s", err.Error()) @@ -359,7 +359,7 @@ func Test_StreamListener(t *testing.T) { // listen to stream go func() { - grpcSync.client = syncClient + grpcSync.syncClient = syncClient err := grpcSync.Sync(context.TODO(), syncChan) if err != nil { // must ignore EOF as this is returned for stream end diff --git a/pkg/sync/kubernetes/kubernetes_sync.go b/pkg/sync/kubernetes/kubernetes_sync.go index 023c9ab0b..c06195bd2 100644 --- a/pkg/sync/kubernetes/kubernetes_sync.go +++ b/pkg/sync/kubernetes/kubernetes_sync.go @@ -31,6 +31,7 @@ type Sync struct { client client.Client URI string Source string + ready bool } func (k *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error { @@ -40,7 +41,6 @@ func (k *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error } dataSync <- sync.DataSync{FlagData: fetch, Source: k.Source, Type: sync.ALL} return nil - ready bool } func (k *Sync) Init(ctx context.Context) error { From 53eb938e05502078d595ebb59c59b4adc8aa2d9f Mon Sep 17 00:00:00 2001 From: James Milligan Date: Tue, 28 Feb 2023 10:06:12 +0000 Subject: [PATCH 20/23] linting Signed-off-by: James Milligan --- pkg/runtime/runtime.go | 7 ------- pkg/sync/file/filepath_sync_test.go | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index e57b77131..be04c88f4 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -51,13 +51,10 @@ func (r *Runtime) Start() error { if r.Evaluator == nil { return errors.New("no evaluator set") } - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() - g, gCtx := errgroup.WithContext(ctx) dataSync := make(chan sync.DataSync, len(r.SyncImpl)) - // Initialize DataSync channel watcher g.Go(func() error { for { @@ -78,14 +75,12 @@ func (r *Runtime) Start() error { } } }) - // Init sync providers for _, s := range r.SyncImpl { if err := s.Init(gCtx); err != nil { return err } } - // Start sync provider for _, s := range r.SyncImpl { p := s @@ -93,13 +88,11 @@ func (r *Runtime) Start() error { return p.Sync(gCtx, dataSync) }) } - g.Go(func() error { return r.Service.Serve(gCtx, r.Evaluator, service.Configuration{ ReadinessProbe: r.isReady, }) }) - <-gCtx.Done() if err := g.Wait(); err != nil { return err diff --git a/pkg/sync/file/filepath_sync_test.go b/pkg/sync/file/filepath_sync_test.go index edc84b9f9..097f7aa5d 100644 --- a/pkg/sync/file/filepath_sync_test.go +++ b/pkg/sync/file/filepath_sync_test.go @@ -45,8 +45,8 @@ func TestSimpleReSync(t *testing.T) { for test, tt := range tests { t.Run(test, func(t *testing.T) { defer t.Cleanup(cleanupFilePath) - setupDir(t, fetchDirName) - createFile(t, fetchDirName, fetchFileName) + setupDir(t) + createFile(t) writeToFile(t, tt.fileContents) ctx := context.Background() @@ -140,8 +140,8 @@ func TestSimpleSync(t *testing.T) { for test, tt := range tests { t.Run(test, func(t *testing.T) { defer t.Cleanup(cleanupFilePath) - setupDir(t, fetchDirName) - createFile(t, fetchDirName, fetchFileName) + setupDir(t) + createFile(t) ctx := context.Background() @@ -229,8 +229,8 @@ func TestFilePathSync_Fetch(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - setupDir(t, fetchDirName) - createFile(t, fetchDirName, fetchFileName) + setupDir(t) + createFile(t) writeToFile(t, fetchFileContents) defer t.Cleanup(cleanupFilePath) @@ -248,8 +248,8 @@ func TestIsReadySyncFlag(t *testing.T) { Mux: &msync.RWMutex{}, } - setupDir(t, fetchDirName) - createFile(t, fetchDirName, fetchFileName) + setupDir(t) + createFile(t) writeToFile(t, fetchFileContents) defer t.Cleanup(cleanupFilePath) if fpSync.IsReady() != false { @@ -291,14 +291,14 @@ func deleteFile(t *testing.T, dirName string, fileName string) { } } -func setupDir(t *testing.T, dirName string) { - if err := os.Mkdir(dirName, os.ModePerm); err != nil { +func setupDir(t *testing.T) { + if err := os.Mkdir(fetchDirName, os.ModePerm); err != nil { t.Fatal(err) } } -func createFile(t *testing.T, dirName string, fileName string) { - if _, err := os.Create(fmt.Sprintf("%s/%s", dirName, fileName)); err != nil { +func createFile(t *testing.T) { + if _, err := os.Create(fmt.Sprintf("%s/%s", fetchDirName, fetchFileName)); err != nil { t.Fatal(err) } } From 8c2b391e9d632ad41ca70125aafa06678af49ed0 Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 1 Mar 2023 13:09:38 +0000 Subject: [PATCH 21/23] cleanup logs Signed-off-by: James Milligan --- pkg/store/flags.go | 1 - pkg/sync/grpc/grpc_sync_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/pkg/store/flags.go b/pkg/store/flags.go index 1623b867f..93e305293 100644 --- a/pkg/store/flags.go +++ b/pkg/store/flags.go @@ -153,7 +153,6 @@ func (f *Flags) DeleteFlags(logger *logger.Logger, source string, flags map[stri flag, ok := f.Get(k) if ok { if !f.hasPriority(flag.Source, source) { - fmt.Println(flag.Source, source, "priority failing") continue } notifications[k] = map[string]interface{}{ diff --git a/pkg/sync/grpc/grpc_sync_test.go b/pkg/sync/grpc/grpc_sync_test.go index 811731647..0c7e84ba8 100644 --- a/pkg/sync/grpc/grpc_sync_test.go +++ b/pkg/sync/grpc/grpc_sync_test.go @@ -95,7 +95,6 @@ func Test_ReSyncTests(t *testing.T) { for _, expected := range test.notifications { out := <-syncChan - fmt.Println(out.Type) if expected.Type != out.Type { t.Errorf("Returned sync type = %v, wanted %v", out.Type, expected.Type) } From c60db46c0c4442219c8abdfe9615a9b7c2b5286d Mon Sep 17 00:00:00 2001 From: James Milligan <75740990+james-milligan@users.noreply.github.com> Date: Wed, 1 Mar 2023 13:19:07 +0000 Subject: [PATCH 22/23] Apply suggestions from code review Co-authored-by: Skye Gill Signed-off-by: James Milligan <75740990+james-milligan@users.noreply.github.com> --- pkg/store/flags.go | 5 +++-- pkg/sync/grpc/grpc_sync.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/store/flags.go b/pkg/store/flags.go index 93e305293..91b5b0898 100644 --- a/pkg/store/flags.go +++ b/pkg/store/flags.go @@ -22,9 +22,10 @@ func (f *Flags) hasPriority(stored string, new string) bool { return true } for i := len(f.FlagSources) - 1; i >= 0; i-- { - if f.FlagSources[i] == stored { + switch f.FlagSources[i] { + case stored: return false - } else if f.FlagSources[i] == new { + case new: return true } } diff --git a/pkg/sync/grpc/grpc_sync.go b/pkg/sync/grpc/grpc_sync.go index 02b7071f2..d1fb80641 100644 --- a/pkg/sync/grpc/grpc_sync.go +++ b/pkg/sync/grpc/grpc_sync.go @@ -62,7 +62,7 @@ func (g *Sync) connectClient(ctx context.Context) error { func (g *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error { res, err := g.client.FetchAllFlags(ctx, &v1.FetchAllFlagsRequest{}) if err != nil { - g.Logger.Error(fmt.Sprintf("error fetching all flags: %s", err.Error())) + g.Logger.Error(fmt.Sprintf("fetching all flags: %s", err.Error())) return err } dataSync <- sync.DataSync{ From a50d6bd7bcbfde5c4d2e5dc8d1695398c2c5f0aa Mon Sep 17 00:00:00 2001 From: James Milligan Date: Wed, 1 Mar 2023 14:45:34 +0000 Subject: [PATCH 23/23] comments Signed-off-by: James Milligan --- pkg/runtime/runtime.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index be04c88f4..7ed480158 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -60,6 +60,9 @@ func (r *Runtime) Start() error { for { select { case data := <-dataSync: + // resync events are triggered when a delete occurs during flag mergesĀ in the store + // resync events may trigger further resync events, however for a flag to be deleted from the store + // its source must match, preventing the opportunity for resync events to snowball if resyncRequired := r.updateWithNotify(data); resyncRequired { for _, s := range r.SyncImpl { p := s