From 524f65ea7215466bb4ac24a8d0d5953dd1cfe9a0 Mon Sep 17 00:00:00 2001 From: James Milligan <75740990+james-milligan@users.noreply.github.com> Date: Wed, 1 Mar 2023 19:10:29 +0000 Subject: [PATCH] feat: introduce flag merge behaviour (#414) ## This PR - introduces a priority merge order for sync providers - triggers a resync of the state store on delete events, ensuring the store doesn't fall out of sync with flag sources --------- Signed-off-by: James Milligan Co-authored-by: Todd Baert Co-authored-by: Skye Gill --- cmd/start.go | 5 +- docs/configuration/flagd_start.md | 2 +- go.mod | 34 +++--- go.sum | 152 ++++++------------------- pkg/eval/fractional_evaluation_test.go | 2 +- pkg/eval/ievaluator.go | 2 +- pkg/eval/json_evaluator.go | 19 ++-- pkg/eval/json_evaluator_test.go | 121 ++++++++++++++------ pkg/eval/mock/ievaluator.go | 7 +- pkg/runtime/from_config.go | 5 +- pkg/runtime/runtime.go | 29 +++-- pkg/store/flags.go | 65 ++++++----- pkg/store/flags_test.go | 127 ++++++++++++++++++++- pkg/sync/file/filepath_sync.go | 8 +- pkg/sync/file/filepath_sync_test.go | 73 ++++++++++-- pkg/sync/grpc/grpc_sync.go | 58 ++++++---- pkg/sync/grpc/grpc_sync_test.go | 106 ++++++++++++++++- pkg/sync/http/http_sync.go | 9 ++ pkg/sync/http/http_sync_test.go | 93 +++++++++++++++ pkg/sync/isync.go | 4 + pkg/sync/kubernetes/kubernetes_sync.go | 16 ++- 21 files changed, 670 insertions(+), 267 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index ee6f1b597..e12e59f99 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -47,8 +47,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. Using multiple providers is supported however if"+ - " flag keys are duplicated across multiple sources it may lead to unexpected behavior. "+ + "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/go.mod b/go.mod index fe9027d8f..0bfc3a367 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/cucumber/godog v0.12.6 github.com/diegoholiveira/jsonlogic/v3 v3.2.7 @@ -46,7 +46,7 @@ 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/cucumber/gherkin-go/v19 v19.0.3 // indirect github.com/cucumber/messages-go/v16 v16.0.1 // indirect @@ -55,8 +55,8 @@ require ( 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.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // 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/gofrs/uuid v4.2.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -72,13 +72,13 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.13 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // 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.1.1 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.16 // 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 @@ -91,8 +91,8 @@ 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 @@ -103,23 +103,23 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.opentelemetry.io/otel/trace v1.13.0 // 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-20221123214604-86e75ddd809a // indirect - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // 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 68e3f9ded..b6be300c5 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ buf.build/gen/go/grpc-ecosystem/grpc-gateway/grpc/go v1.2.0-20220906183531-bc28b723cd77.4/go.mod h1:hAKk3I2AivrJgMLXjDGrfzRx2NVWQgEPNfr4Co9DLX4= 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/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/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/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-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-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= @@ -47,15 +47,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 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= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bufbuild/connect-go v1.5.2 h1:G4EZd5gF1U1ZhhbVJXplbuUnfKpBZ5j5izqIwu2g2W8= @@ -64,7 +57,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -79,11 +71,13 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH 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.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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= github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= github.com/cucumber/godog v0.12.6 h1:3IToXviU45G7FgijwTk/LdB4iojn8zUFDfQLj4MMiHc= @@ -121,14 +115,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= @@ -136,19 +122,15 @@ github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 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/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +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.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.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/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -246,31 +228,23 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: 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.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -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= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= -github.com/klauspost/cpuid/v2 v2.1.1/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/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 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= @@ -278,10 +252,9 @@ 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= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 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= @@ -289,9 +262,9 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +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.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/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -305,14 +278,10 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx 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= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= @@ -334,39 +303,21 @@ github.com/open-feature/schemas v0.2.8 h1:oA75hJXpOd9SFgmNI2IAxWZkwzQPUDm7Jyyh3q github.com/open-feature/schemas v0.2.8/go.mod h1:vj+rfTsOLlh5PtGGkAbitnJmFPYuTHXTjOy13kzNgKQ= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -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/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= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -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/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= +github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= +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= @@ -378,9 +329,6 @@ github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -401,7 +349,6 @@ github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -452,15 +399,13 @@ go.opentelemetry.io/otel/sdk/metric v0.36.0/go.mod h1:Lv4HQQPSCSkhyBKzLNtE8YhTSd go.opentelemetry.io/otel/trace v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY= go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -504,7 +449,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -512,7 +456,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -536,10 +479,7 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -551,10 +491,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -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= @@ -569,13 +507,10 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -585,7 +520,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -598,8 +532,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -607,23 +539,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -634,7 +561,6 @@ 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= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -764,8 +690,8 @@ google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6D 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= @@ -803,7 +729,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ 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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -814,18 +739,13 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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= @@ -852,12 +772,12 @@ k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= 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/kube-openapi v0.0.0-20221123214604-86e75ddd809a h1:UR2YSPKAb8j3uL2yK8V+t2ElG4RoBxhJTxa5gg0ZtSo= -k8s.io/kube-openapi v0.0.0-20221123214604-86e75ddd809a/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -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/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-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-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= diff --git a/pkg/eval/fractional_evaluation_test.go b/pkg/eval/fractional_evaluation_test.go index 3c23615a5..e1aa5a214 100644 --- a/pkg/eval/fractional_evaluation_test.go +++ b/pkg/eval/fractional_evaluation_test.go @@ -282,7 +282,7 @@ 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 := NewJSONEvaluator(logger.NewLogger(nil, false), store.NewFlags()) je.store.Flags = tt.flags.Flags value, variant, reason, err := resolve[string]( 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 b8668ae54..f06777375 100644 --- a/pkg/eval/json_evaluator.go +++ b/pkg/eval/json_evaluator.go @@ -40,13 +40,13 @@ 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 @@ -56,24 +56,25 @@ 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 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 87c52918f..06cca9421 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,8 +277,8 @@ var Flags = fmt.Sprintf(`{ DisabledFlag) func TestGetState_Valid_ContainsFlag(t *testing.T) { - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags}) + 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,28 +297,28 @@ 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}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags}) if err == nil { t.Fatalf("expected error") } } 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}) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags}) if err != nil { t.Fatalf("expected no error") } } func TestResolveAllValues(t *testing.T) { - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +377,8 @@ func TestResolveBooleanValue(t *testing.T) { {DisabledFlag, nil, StaticBoolValue, model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +416,8 @@ func BenchmarkResolveBooleanValue(b *testing.B) { {DisabledFlag, nil, StaticBoolValue, model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +460,8 @@ func TestResolveStringValue(t *testing.T) { {DisabledFlag, nil, "", model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +500,8 @@ func BenchmarkResolveStringValue(b *testing.B) { {DisabledFlag, nil, "", model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +544,8 @@ func TestResolveFloatValue(t *testing.T) { {DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +584,8 @@ func BenchmarkResolveFloatValue(b *testing.B) { {DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +628,8 @@ func TestResolveIntValue(t *testing.T) { {DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +668,8 @@ func BenchmarkResolveIntValue(b *testing.B) { {DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +712,8 @@ func TestResolveObjectValue(t *testing.T) { {DisabledFlag, nil, "{}", model.ErrorReason, model.FlagDisabledErrorCode}, } const reqID = "default" - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,8 +755,8 @@ func BenchmarkResolveObjectValue(b *testing.B) { {DisabledFlag, nil, "{}", model.ErrorReason, model.FlagDisabledErrorCode}, } - evaluator := eval.NewJSONEvaluator(logger.NewLogger(nil, false)) - _, err := evaluator.SetState(sync.DataSync{FlagData: Flags}) + 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,9 +838,9 @@ 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}) + _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags}) if tt.valid && err != nil { t.Error(err) @@ -851,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: ` @@ -885,6 +888,7 @@ func TestState_Evaluator(t *testing.T) { } } `, + inputSyncType: sync.ALL, expectedOutputState: ` { "flags": { @@ -908,7 +912,8 @@ func TestState_Evaluator(t *testing.T) { ] } } - } + }, + "flagSources":null } `, }, @@ -943,6 +948,7 @@ func TestState_Evaluator(t *testing.T) { } } `, + inputSyncType: sync.ALL, expectedOutputState: ` { "flags": { @@ -966,7 +972,8 @@ func TestState_Evaluator(t *testing.T) { ] } } - } + }, + "flagSources":null } `, }, @@ -997,6 +1004,7 @@ func TestState_Evaluator(t *testing.T) { } } `, + inputSyncType: sync.ALL, expectedError: true, }, "empty evaluator": { @@ -1026,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)) + 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) @@ -1132,9 +1179,9 @@ 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}) + _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Type: sync.ADD}) if err != nil { t.Fatal(err) } @@ -1157,7 +1204,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/from_config.go b/pkg/runtime/from_config.go index 7e313de9c..6d320f543 100644 --- a/pkg/runtime/from_config.go +++ b/pkg/runtime/from_config.go @@ -10,6 +10,7 @@ import ( "github.com/open-feature/flagd/pkg/eval" "github.com/open-feature/flagd/pkg/logger" "github.com/open-feature/flagd/pkg/service" + "github.com/open-feature/flagd/pkg/store" "github.com/open-feature/flagd/pkg/sync" "github.com/open-feature/flagd/pkg/sync/file" "github.com/open-feature/flagd/pkg/sync/grpc" @@ -34,10 +35,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 diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 4772eaef3..7ed480158 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -51,32 +51,39 @@ 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 { select { case data := <-dataSync: - r.updateWithNotify(data) + // 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 + go func() { + g.Go(func() error { + return p.ReSync(gCtx, dataSync) + }) + }() + } + } case <-gCtx.Done(): return nil } } }) - // 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 @@ -84,13 +91,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 @@ -109,14 +114,14 @@ func (r *Runtime) isReady() bool { } // 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{ @@ -125,4 +130,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 8d2eea8be..91b5b0898 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-- { + switch f.FlagSources[i] { + case stored: + return false + case 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,7 @@ 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) { continue } notifications[k] = map[string]interface{}{ @@ -168,8 +174,13 @@ 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 { @@ -181,6 +192,7 @@ func (f *Flags) Merge(logger *logger.Logger, source string, flags map[string]mod "type": string(model.NotificationDelete), "source": source, } + resyncRequired = true continue } } @@ -191,22 +203,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, @@ -217,5 +222,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 e3ba77828..9b2e26e5b 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: "A", + newSource: "B", + hasPriority: true, + }, + { + 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 { @@ -18,6 +80,7 @@ func TestMergeFlags(t *testing.T) { newSource string want *Flags wantNotifs map[string]interface{} + wantResync bool }{ { name: "both nil", @@ -116,15 +179,61 @@ 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, + }, + { + 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 { 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) }) } } @@ -361,6 +470,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 +483,10 @@ func TestFlags_Delete(t *testing.T) { "B": {Source: mockSource}, "C": {Source: mockSource2}, }, + FlagSources: []string{ + mockSource, + mockSource2, + }, }, expectedNotificationKeys: []string{"A"}, }, @@ -381,6 +498,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 +512,10 @@ func TestFlags_Delete(t *testing.T) { "B": {Source: mockSource}, "C": {Source: mockSource2}, }, + FlagSources: []string{ + mockSource, + mockSource2, + }, }, expectedNotificationKeys: []string{}, }, diff --git a/pkg/sync/file/filepath_sync.go b/pkg/sync/file/filepath_sync.go index 5fc31ebfd..c24f98305 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., @@ -30,6 +31,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.ALL, dataSync) + return nil +} + func (fs *Sync) Init(ctx context.Context) error { fs.Logger.Info("Starting filepath sync notifier") w, err := fsnotify.NewWatcher() @@ -128,7 +134,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/file/filepath_sync_test.go b/pkg/sync/file/filepath_sync_test.go index 330973d56..097f7aa5d 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" msync "sync" "testing" "time" @@ -20,6 +21,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) + createFile(t) + 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) @@ -88,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() @@ -100,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 { @@ -176,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) @@ -195,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 { @@ -238,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) } } diff --git a/pkg/sync/grpc/grpc_sync.go b/pkg/sync/grpc/grpc_sync.go index a36c7ac35..d1fb80641 100644 --- a/pkg/sync/grpc/grpc_sync.go +++ b/pkg/sync/grpc/grpc_sync.go @@ -8,14 +8,13 @@ import ( msync "sync" "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" "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 ( @@ -35,34 +34,54 @@ 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 } -func (g *Sync) Init(ctx context.Context) error { - g.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.options...) if 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}) + 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.client = syncClient + g.syncClient = syncClient + return nil +} + +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("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) Init(ctx context.Context) error { + g.options = []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + } + + // initial dial and connection. Failure here must result in a startup failure + return g.connectClient(ctx) +} + func (g *Sync) IsReady() bool { g.Mux.RLock() defer g.Mux.RUnlock() @@ -78,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 @@ -103,7 +125,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 @@ -127,14 +149,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 b326a2340..0c7e84ba8 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" @@ -19,6 +20,97 @@ import ( "google.golang.org/grpc/test/bufconn" ) +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 + 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 @@ -115,7 +207,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()) @@ -266,7 +358,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 @@ -324,8 +416,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 { @@ -342,3 +436,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 b.fetchAllFlagsResponse, b.fetchAllFlagsError +} diff --git a/pkg/sync/http/http_sync.go b/pkg/sync/http/http_sync.go index c2a56460f..0609a7b45 100644 --- a/pkg/sync/http/http_sync.go +++ b/pkg/sync/http/http_sync.go @@ -38,6 +38,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.ALL} + return nil +} + func (hs *Sync) Init(ctx context.Context) error { // noop return nil diff --git a/pkg/sync/http/http_sync_test.go b/pkg/sync/http/http_sync_test.go index e7e1596c4..0d734a982 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" @@ -161,3 +163,94 @@ 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): + } + }) + } +} diff --git a/pkg/sync/isync.go b/pkg/sync/isync.go index 7af60ff9d..7f102550f 100644 --- a/pkg/sync/isync.go +++ b/pkg/sync/isync.go @@ -45,6 +45,10 @@ 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 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 + // IsReady shall return true if the provider is ready to communicate with the Runtime IsReady() bool } diff --git a/pkg/sync/kubernetes/kubernetes_sync.go b/pkg/sync/kubernetes/kubernetes_sync.go index 632599d67..c06195bd2 100644 --- a/pkg/sync/kubernetes/kubernetes_sync.go +++ b/pkg/sync/kubernetes/kubernetes_sync.go @@ -30,9 +30,19 @@ type Sync struct { ProviderArgs sync.ProviderArgs client client.Client URI string + Source string ready bool } +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) Init(ctx context.Context) error { // noop return nil @@ -51,7 +61,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) @@ -71,7 +81,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) @@ -80,7 +90,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: