diff --git a/build/README.md b/build/README.md index 7e0ea6cb96..c0e8495dc8 100644 --- a/build/README.md +++ b/build/README.md @@ -10,24 +10,6 @@ This directory contains a Go module dedicated to building and testing Flipt usin All the commands in this directory can be invoked from the root of the Flipt repo using `dagger call`. -```console -USAGE - dagger call [options] [arguments] - -EXAMPLES - dagger call test - dagger call build -o ./bin/myapp - dagger call lint stdout - -FUNCTIONS - base Returns a container with all the assets compiled and ready for testing and distribution - base-container - build Return container with Flipt binaries in a thinner alpine distribution - generate Execute generate function with subcommand - source - test Execute test specific by subcommand -``` - This version of the command runs using the `dagger` cli (`brew install dagger/tap/dagger`). It comes with a nice TUI. @@ -53,15 +35,12 @@ USAGE dagger call test [arguments] FUNCTIONS - base-container - cli Run all cli tests - flipt-container + base-container - + flipt-container - integration Run all integration tests - load Run all load tests - migration Run all migration tests - source + source - ui Run all ui tests - uicontainer + uicontainer - unit Run all unit tests ARGUMENTS @@ -70,11 +49,8 @@ ARGUMENTS Use "dagger call test [command] --help" for more information about a command. ``` -- `dagger call test --source=. cli`: Runs a suite of tests against `flipt` CLI commands - `dagger call test --source=. unit`: Runs the entire suite of unit style tests - `dagger call test --source=. ui`: Runs the entire suite of UI integration style tests (playwright against running Flipt) -- `dagger call test --source=. migration`: Ensures backwards compatibility after running database migrations -- `dagger call test --source=. load`: Run Flipt and measure performance running evaluations under load ### Integration Tests @@ -91,7 +67,11 @@ ARGUMENTS --cases string (default "*") ``` -By default, it runs all cases concurrently against the target dagger runtime. +- `dagger call test --source=. integration`: Runs all cases concurrently against the target dagger runtime. + However, cases can be constrained to the individual top-level suites via the `--cases` flag. + +- `dagger call test --source=. integration --cases="authn"`: Runs only the authn suite. + This flag expects a space delimited list of the case names. The case names are maintained near the top of [integration.go](./testing/integration.go). diff --git a/build/testing/integration.go b/build/testing/integration.go index f4b331bcad..3f15f9402c 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -49,12 +49,12 @@ var ( AllCases = map[string]testCaseFn{ // "fs/git": git, // "fs/local": local, - // "authz": authz, + //"authz": authz, "authn": authn(), "envs": envsAPI(""), "envs_with_dir": envsAPI("root"), - // "ofrep": withAuthz(ofrepAPI), - // "snapshot": withAuthz(snapshotAPI), + "ofrep": withAuthz(ofrepAPI()), + "snapshot": withAuthz(snapshotAPI()), } ) @@ -288,6 +288,34 @@ func authn() testCaseFn { }, testdataDir) } +func snapshotAPI() testCaseFn { + return func(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error { + flipt = flipt. + WithEnvVariable("FLIPT_LOG_LEVEL", "DEBUG"). + WithEnvVariable("FLIPT_ENVIRONMENTS_DEFAULT_STORAGE", "default"). + WithEnvVariable("FLIPT_STORAGE_TYPE", "local"). + WithEnvVariable("FLIPT_STORAGE_LOCAL_PATH", "/tmp/testdata"). + WithDirectory("/tmp/testdata", base.Directory(testdataDir)). + WithEnvVariable("UNIQUE", uuid.New().String()) + + return suite(ctx, "snapshot", base, flipt.WithExec(nil), conf) + } +} + +func ofrepAPI() testCaseFn { + return func(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error { + flipt = flipt. + WithEnvVariable("FLIPT_LOG_LEVEL", "DEBUG"). + WithEnvVariable("FLIPT_ENVIRONMENTS_DEFAULT_STORAGE", "default"). + WithEnvVariable("FLIPT_STORAGE_TYPE", "local"). + WithEnvVariable("FLIPT_STORAGE_LOCAL_PATH", "/tmp/testdata"). + WithDirectory("/tmp/testdata", base.Directory(testdataDir)). + WithEnvVariable("UNIQUE", uuid.New().String()) + + return suite(ctx, "ofrep", base, flipt.WithExec(nil), conf) + } +} + func withGitea(fn testCaseFn, dataDir string) testCaseFn { return func(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error { gitea := client.Container(). diff --git a/internal/cmd/authn.go b/internal/cmd/authn.go index 00f6219d01..cc0d33ff5a 100644 --- a/internal/cmd/authn.go +++ b/internal/cmd/authn.go @@ -21,7 +21,6 @@ import ( authgithub "go.flipt.io/flipt/internal/server/authn/method/github" authkubernetes "go.flipt.io/flipt/internal/server/authn/method/kubernetes" authoidc "go.flipt.io/flipt/internal/server/authn/method/oidc" - authtoken "go.flipt.io/flipt/internal/server/authn/method/token" authmiddlewaregrpc "go.flipt.io/flipt/internal/server/authn/middleware/grpc" authmiddlewarehttp "go.flipt.io/flipt/internal/server/authn/middleware/http" "go.flipt.io/flipt/internal/server/authn/public" @@ -99,13 +98,6 @@ func authenticationGRPC( var interceptors []grpc.UnaryServerInterceptor - // register auth method token service - if authCfg.Methods.Token.Enabled { - rpcauth.RegisterAuthenticationMethodTokenServiceServer(handlers, authtoken.NewServer(logger, store)) - - logger.Debug("authentication method \"token\" server registered") - } - // register auth method oidc service if authCfg.Methods.OIDC.Enabled { rpcauth.RegisterAuthenticationMethodOIDCServiceServer(handlers, authoidc.NewServer(logger, store, authCfg)) @@ -207,10 +199,6 @@ func authenticationGRPC( interceptors = append(interceptors, selector.UnaryServerInterceptor(authmiddlewaregrpc.EmailMatchingInterceptor(logger, rgxs, authOpts...), authmiddlewaregrpc.ClientTokenInterceptorSelector())) } - if authCfg.Methods.Token.Enabled { - interceptors = append(interceptors, selector.UnaryServerInterceptor(authmiddlewaregrpc.NamespaceMatchingInterceptor(logger, authOpts...), authmiddlewaregrpc.ClientTokenInterceptorSelector())) - } - // at this point, we have already registered all authentication methods that are enabled // so atleast one authentication method should pass if authentication is required interceptors = append(interceptors, authmiddlewaregrpc.AuthenticationRequiredInterceptor(logger, authOpts...)) diff --git a/internal/cmd/grpc.go b/internal/cmd/grpc.go index 273d0e3f28..8d7c8279bf 100644 --- a/internal/cmd/grpc.go +++ b/internal/cmd/grpc.go @@ -29,9 +29,9 @@ import ( serverenvironments "go.flipt.io/flipt/internal/server/environments" "go.flipt.io/flipt/internal/server/evaluation" evaluationdata "go.flipt.io/flipt/internal/server/evaluation/data" + "go.flipt.io/flipt/internal/server/evaluation/ofrep" "go.flipt.io/flipt/internal/server/metadata" middlewaregrpc "go.flipt.io/flipt/internal/server/middleware/grpc" - "go.flipt.io/flipt/internal/server/ofrep" "go.flipt.io/flipt/internal/storage/environments" "go.flipt.io/flipt/internal/tracing" rpcflipt "go.flipt.io/flipt/rpc/flipt" @@ -194,7 +194,7 @@ func NewGRPCServer( evalsrv = evaluation.New(logger, environmentStore) evaldatasrv = evaluationdata.New(logger, environmentStore) fliptv1srv = serverfliptv1.New(logger, environmentStore) - ofrepsrv = ofrep.New(logger, evalsrv, nil) + ofrepsrv = ofrep.New(logger, evalsrv, environmentStore) // health service healthsrv = health.NewServer() diff --git a/internal/cmd/http.go b/internal/cmd/http.go index d174c433a2..8fc97e463e 100644 --- a/internal/cmd/http.go +++ b/internal/cmd/http.go @@ -24,9 +24,9 @@ import ( "go.flipt.io/flipt/internal/gateway" "go.flipt.io/flipt/internal/info" "go.flipt.io/flipt/internal/server/authn/method" + ofrep_middleware "go.flipt.io/flipt/internal/server/evaluation/ofrep" grpc_middleware "go.flipt.io/flipt/internal/server/middleware/grpc" http_middleware "go.flipt.io/flipt/internal/server/middleware/http" - ofrep_middleware "go.flipt.io/flipt/internal/server/ofrep" "go.flipt.io/flipt/rpc/flipt" "go.flipt.io/flipt/rpc/flipt/analytics" "go.flipt.io/flipt/rpc/flipt/evaluation" diff --git a/internal/server/authn/method/token/server.go b/internal/server/authn/method/token/server.go deleted file mode 100644 index f15e6d8b9e..0000000000 --- a/internal/server/authn/method/token/server.go +++ /dev/null @@ -1,79 +0,0 @@ -package token - -import ( - "context" - "fmt" - - storageauth "go.flipt.io/flipt/internal/storage/authn" - "go.flipt.io/flipt/rpc/flipt/auth" - "go.uber.org/zap" - "google.golang.org/grpc" -) - -const ( - storageMetadataNameKey = "io.flipt.auth.token.name" - storageMetadataDescriptionKey = "io.flipt.auth.token.description" - storageMetadataNamespaceKey = "io.flipt.auth.token.namespace" -) - -// Server is an implementation of auth.AuthenticationMethodTokenServiceServer -// -// It is used to create static tokens within the backing AuthenticationStore. -type Server struct { - logger *zap.Logger - store storageauth.Store - auth.UnimplementedAuthenticationMethodTokenServiceServer -} - -// NewServer constructs and configures a new *Server. -func NewServer(logger *zap.Logger, store storageauth.Store) *Server { - return &Server{ - logger: logger, - store: store, - } -} - -// RegisterGRPC registers the server as an Server on the provided grpc server. -func (s *Server) RegisterGRPC(server *grpc.Server) { - auth.RegisterAuthenticationMethodTokenServiceServer(server, s) -} - -func (s *Server) AllowsNamespaceScopedAuthentication(ctx context.Context) bool { - return false -} - -// CreateToken adapts and delegates the token request to the backing AuthenticationStore. -// -// Implicitly, the Authentication created will be of type auth.Method_TOKEN. -// Name, Description, and NamespaceKey are both stored in Authentication.Metadata. -// Given the token is created successfully, the generate clientToken string is returned. -// Along with the created Authentication, which includes it's identifier and associated timestamps. -func (s *Server) CreateToken(ctx context.Context, req *auth.CreateTokenRequest) (*auth.CreateTokenResponse, error) { - metadata := req.Metadata - if metadata == nil { - metadata = map[string]string{} - } - - metadata[storageMetadataNameKey] = req.GetName() - if req.GetDescription() != "" { - metadata[storageMetadataDescriptionKey] = req.GetDescription() - } - - if req.GetNamespaceKey() != "" { - metadata[storageMetadataNamespaceKey] = req.GetNamespaceKey() - } - - clientToken, authentication, err := s.store.CreateAuthentication(ctx, &storageauth.CreateAuthenticationRequest{ - Method: auth.Method_METHOD_TOKEN, - ExpiresAt: req.ExpiresAt, - Metadata: metadata, - }) - if err != nil { - return nil, fmt.Errorf("attempting to create token: %w", err) - } - - return &auth.CreateTokenResponse{ - ClientToken: clientToken, - Authentication: authentication, - }, nil -} diff --git a/internal/server/authn/method/token/server_test.go b/internal/server/authn/method/token/server_test.go deleted file mode 100644 index c86fc6d641..0000000000 --- a/internal/server/authn/method/token/server_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package token - -import ( - "context" - "net" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - middleware "go.flipt.io/flipt/internal/server/middleware/grpc" - "go.flipt.io/flipt/internal/storage/authn/memory" - "go.flipt.io/flipt/rpc/flipt/auth" - "go.uber.org/zap/zaptest" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/status" - "google.golang.org/grpc/test/bufconn" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func TestServer(t *testing.T) { - var ( - logger = zaptest.NewLogger(t) - store = memory.NewStore(logger) - listener = bufconn.Listen(1024 * 1024) - server = grpc.NewServer( - grpc.ChainUnaryInterceptor( - middleware.ErrorUnaryInterceptor, - ), - ) - errC = make(chan error) - shutdown = func(t *testing.T) { - t.Helper() - - server.Stop() - if err := <-errC; err != nil { - t.Fatal(err) - } - } - ) - - defer shutdown(t) - - auth.RegisterAuthenticationMethodTokenServiceServer(server, NewServer(logger, store)) - - go func() { - errC <- server.Serve(listener) - }() - - var ( - ctx = context.Background() - dialer = func(context.Context, string) (net.Conn, error) { - return listener.Dial() - } - ) - - conn, err := grpc.NewClient("passthrough://local", grpc.WithContextDialer(dialer), grpc.WithTransportCredentials(insecure.NewCredentials())) - require.NoError(t, err) - defer conn.Close() - - client := auth.NewAuthenticationMethodTokenServiceClient(conn) - - // attempt to create token - resp, err := client.CreateToken(ctx, &auth.CreateTokenRequest{ - Name: "access_all_areas", - Description: "Super secret skeleton key", - NamespaceKey: "my-namespace", - Metadata: map[string]string{ - "foo": "bar", - }, - }) - require.NoError(t, err) - - // assert auth is as expected - metadata := resp.Authentication.Metadata - assert.Equal(t, "access_all_areas", metadata["io.flipt.auth.token.name"]) - assert.Equal(t, "Super secret skeleton key", metadata["io.flipt.auth.token.description"]) - assert.Equal(t, "my-namespace", metadata["io.flipt.auth.token.namespace"]) - assert.Equal(t, "bar", metadata["foo"]) - - // ensure client token can be used on store to fetch authentication - // and that the authentication returned matches the one received - // by the client - retrieved, err := store.GetAuthenticationByClientToken(ctx, resp.ClientToken) - require.NoError(t, err) - - // switch to go-cmp here to do the comparisons since assert trips up - // on the unexported sizeCache values. - if diff := cmp.Diff(retrieved, resp.Authentication, protocmp.Transform()); err != nil { - t.Errorf("-exp/+got:\n%s", diff) - } - - // attempt to create token with invalid expires at - _, err = client.CreateToken(ctx, &auth.CreateTokenRequest{ - Name: "access_all_areas", - Description: "Super secret skeleton key", - // invalid expires at, nanos must be positive - ExpiresAt: ×tamppb.Timestamp{Nanos: -1}, - }) - - require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "attempting to create token: invalid expiry time: nanos:-1")) -} - -func Test_Server_DisallowsNamespaceScopedAuthentication(t *testing.T) { - server := &Server{} - assert.False(t, server.AllowsNamespaceScopedAuthentication(context.Background())) -} diff --git a/internal/server/authn/middleware/grpc/middleware.go b/internal/server/authn/middleware/grpc/middleware.go index a9e748113e..4740f9da5e 100644 --- a/internal/server/authn/middleware/grpc/middleware.go +++ b/internal/server/authn/middleware/grpc/middleware.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/cap/jwt" "go.flipt.io/flipt/internal/containers" middlewarecommon "go.flipt.io/flipt/internal/server/authn/middleware/common" - "go.flipt.io/flipt/rpc/flipt" authrpc "go.flipt.io/flipt/rpc/flipt/auth" "go.uber.org/zap" "google.golang.org/grpc" @@ -107,11 +106,6 @@ func WithServerSkipsAuthentication(server any) containers.Option[InterceptorOpti } } -// ScopedAuthenticationServer is a grpc.Server which allows for specific scoped authentication. -type ScopedAuthenticationServer interface { - AllowsNamespaceScopedAuthentication(ctx context.Context) bool -} - // SkipsAuthenticationServer is a grpc.Server which should always skip authentication. type SkipsAuthenticationServer interface { SkipsAuthentication(ctx context.Context) bool @@ -357,90 +351,6 @@ func EmailMatchingInterceptor(logger *zap.Logger, rgxs []*regexp.Regexp, o ...co } } -func NamespaceMatchingInterceptor(logger *zap.Logger, o ...containers.Option[InterceptorOptions]) grpc.UnaryServerInterceptor { - var opts InterceptorOptions - containers.ApplyAll(&opts, o...) - - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - // skip auth for any preconfigured servers - if skipped(ctx, info, opts) { - logger.Debug("skipping authentication for server", zap.String("method", info.FullMethod)) - return handler(ctx, req) - } - - auth := GetAuthenticationFrom(ctx) - if auth == nil { - panic("authentication not found in context, middleware installed incorrectly") - } - - // this mechanism only applies to static toke authentications - if auth.Method != authrpc.Method_METHOD_TOKEN { - return handler(ctx, req) - } - - namespace, ok := auth.Metadata["io.flipt.auth.token.namespace"] - if !ok { - // if no namespace is provided then we should allow the request - return handler(ctx, req) - } - - nsServer, ok := info.Server.(ScopedAuthenticationServer) - if !ok || !nsServer.AllowsNamespaceScopedAuthentication(ctx) { - logger.Error("unauthenticated", - zap.String("reason", "namespace is not allowed")) - return ctx, errUnauthenticated - } - - namespace = strings.TrimSpace(namespace) - if namespace == "" { - return handler(ctx, req) - } - - logger := logger.With(zap.String("expected_namespace", namespace)) - - var reqNamespace string - switch nsReq := req.(type) { - case flipt.Namespaced: - reqNamespace = nsReq.GetNamespaceKey() - if reqNamespace == "" { - reqNamespace = "default" - } - case flipt.BatchNamespaced: - // ensure that all namespaces referenced in - // the batch are the same - for _, ns := range nsReq.GetNamespaceKeys() { - if ns == "" { - ns = "default" - } - - if reqNamespace == "" { - reqNamespace = ns - continue - } - - if reqNamespace != ns { - logger.Error("unauthenticated", - zap.String("reason", "same namespace is not set for all requests")) - return ctx, errUnauthenticated - } - } - default: - // if the the token has a namespace but the request does not then we should reject the request - logger.Error("unauthenticated", - zap.String("reason", "namespace is required when using namespace scoped token")) - return ctx, errUnauthenticated - } - - if reqNamespace != namespace { - logger.Error("unauthenticated", - zap.String("reason", "namespace is not allowed")) - return ctx, errUnauthenticated - } - - return handler(ctx, req) - } -} - func clientTokenFromMetadata(md metadata.MD) (string, error) { if authenticationHeader := md.Get(authenticationHeaderKey); len(authenticationHeader) > 0 { return fromAuthorization(authenticationHeader[0], authenticationSchemeBearer) diff --git a/internal/server/authn/middleware/grpc/middleware_test.go b/internal/server/authn/middleware/grpc/middleware_test.go index 607b6a4e5e..f38d29f63b 100644 --- a/internal/server/authn/middleware/grpc/middleware_test.go +++ b/internal/server/authn/middleware/grpc/middleware_test.go @@ -18,7 +18,6 @@ import ( "go.flipt.io/flipt/internal/storage/authn" "go.flipt.io/flipt/internal/storage/authn/memory" authrpc "go.flipt.io/flipt/rpc/flipt/auth" - "go.flipt.io/flipt/rpc/flipt/evaluation" "go.uber.org/zap/zaptest" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -27,18 +26,13 @@ import ( // mockServer is used to test skipping authn type mockServer struct { - skipsAuthn bool - allowNamespacedAuthn bool + skipsAuthn bool } func (s *mockServer) SkipsAuthentication(ctx context.Context) bool { return s.skipsAuthn } -func (s *mockServer) AllowsNamespaceScopedAuthentication(ctx context.Context) bool { - return s.allowNamespacedAuthn -} - var priv *rsa.PrivateKey func init() { @@ -624,254 +618,3 @@ func TestEmailMatchingInterceptor(t *testing.T) { }) } } - -func TestNamespaceMatchingInterceptorWithNoAuth(t *testing.T) { - var ( - logger = zaptest.NewLogger(t) - - ctx = context.Background() - handler = func(ctx context.Context, req interface{}) (interface{}, error) { - return nil, nil - } - ) - - require.Panics(t, func() { - _, _ = NamespaceMatchingInterceptor(logger)( - ctx, - nil, - &grpc.UnaryServerInfo{Server: &mockServer{}}, - handler, - ) - }) -} - -func TestNamespaceMatchingInterceptor(t *testing.T) { - for _, tt := range []struct { - name string - authReq *authn.CreateAuthenticationRequest - req any - srv any - wantCalled bool - expectedErr error - }{ - { - name: "successful namespace match", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "foo", - }, - }, - req: &evaluation.EvaluationRequest{ - NamespaceKey: "foo", - }, - wantCalled: true, - }, - { - name: "successful namespace match on batch", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "foo", - }, - }, - req: &evaluation.BatchEvaluationRequest{ - Requests: []*evaluation.EvaluationRequest{ - { - NamespaceKey: "foo", - }, - { - NamespaceKey: "foo", - }, - }, - }, - wantCalled: true, - }, - { - name: "successful namespace (default) match on batch", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "default", - }, - }, - req: &evaluation.BatchEvaluationRequest{ - Requests: []*evaluation.EvaluationRequest{ - {}, - {}, - }, - }, - wantCalled: true, - }, - { - name: "successful skips auth", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "foo", - }, - }, - req: &struct{}{}, - srv: &mockServer{ - skipsAuthn: true, - }, - wantCalled: true, - }, - { - name: "not a token authentication", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_OIDC, - Metadata: map[string]string{ - "io.flipt.auth.github.sub": "foo", - }, - }, - req: &evaluation.EvaluationRequest{ - NamespaceKey: "foo", - }, - wantCalled: true, - }, - { - name: "namespace does not match", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "bar", - }, - }, - req: &evaluation.EvaluationRequest{ - NamespaceKey: "foo", - }, - expectedErr: errUnauthenticated, - }, - { - name: "namespace not provided by token authentication", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - }, - req: &evaluation.EvaluationRequest{ - NamespaceKey: "foo", - }, - wantCalled: true, - }, - { - name: "empty namespace provided by token authentication", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "", - }, - }, - req: &evaluation.EvaluationRequest{ - NamespaceKey: "foo", - }, - wantCalled: true, - }, - { - name: "namespace not provided by request", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "foo", - }, - }, - req: &evaluation.EvaluationRequest{}, - expectedErr: errUnauthenticated, - }, - { - name: "namespace not available", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "foo", - }, - }, - req: &evaluation.BatchEvaluationRequest{}, - expectedErr: errUnauthenticated, - }, - { - name: "namespace not consistent for batch", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "foo", - }, - }, - req: &evaluation.BatchEvaluationRequest{ - Requests: []*evaluation.EvaluationRequest{ - { - NamespaceKey: "foo", - }, - { - NamespaceKey: "bar", - }, - }, - }, - expectedErr: errUnauthenticated, - }, - { - name: "non-namespaced request", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "foo", - }, - }, - req: &struct{}{}, - expectedErr: errUnauthenticated, - }, - { - name: "non-namespaced scoped server", - authReq: &authn.CreateAuthenticationRequest{ - Method: authrpc.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.namespace": "foo", - }, - }, - req: &evaluation.EvaluationRequest{ - NamespaceKey: "foo", - }, - srv: &mockServer{ - allowNamespacedAuthn: false, - }, - expectedErr: errUnauthenticated, - }, - } { - tt := tt - t.Run(tt.name, func(t *testing.T) { - var ( - logger = zaptest.NewLogger(t) - authenticator = memory.NewStore(logger) - ) - - clientToken, storedAuth, err := authenticator.CreateAuthentication( - context.TODO(), - tt.authReq, - ) - - require.NoError(t, err) - - var ( - ctx = ContextWithAuthentication(context.Background(), storedAuth) - handler = func(ctx context.Context, req interface{}) (interface{}, error) { - assert.True(t, tt.wantCalled) - return nil, nil - } - - srv = &grpc.UnaryServerInfo{Server: &mockServer{ - allowNamespacedAuthn: true, - }} - ) - - if tt.srv != nil { - srv.Server = tt.srv - } - - ctx = metadata.NewIncomingContext(ctx, metadata.MD{ - "Authorization": []string{"Bearer " + clientToken}, - }) - - _, err = NamespaceMatchingInterceptor(logger)(ctx, tt.req, srv, handler) - assert.Equal(t, tt.expectedErr, err) - }) - } -} diff --git a/internal/server/authn/server.go b/internal/server/authn/server.go index 50b5b1eb4c..598e35b502 100644 --- a/internal/server/authn/server.go +++ b/internal/server/authn/server.go @@ -107,10 +107,6 @@ func (s *Server) RegisterGRPC(server *grpc.Server) { auth.RegisterAuthenticationServiceServer(server, s) } -func (s *Server) AllowsNamespaceScopedAuthentication(ctx context.Context) bool { - return false -} - // GetAuthenticationSelf returns the Authentication which was derived from the request context. func (s *Server) GetAuthenticationSelf(ctx context.Context, _ *emptypb.Empty) (*auth.Authentication, error) { if auth := authmiddlewaregrpc.GetAuthenticationFrom(ctx); auth != nil { diff --git a/internal/server/authn/server_test.go b/internal/server/authn/server_test.go index 7ae1aebad5..9110f2c6e8 100644 --- a/internal/server/authn/server_test.go +++ b/internal/server/authn/server_test.go @@ -197,13 +197,3 @@ func TestServer(t *testing.T) { require.ErrorContains(t, err, "request was not authenticated") }) } - -func Test_Server_DisallowsNamespaceScopedAuthentication(t *testing.T) { - var ( - logger = zaptest.NewLogger(t) - store = memory.NewStore(logger) - server = authn.NewServer(logger, store) - ) - - assert.False(t, server.AllowsNamespaceScopedAuthentication(context.Background())) -} diff --git a/internal/server/evaluation/data/server.go b/internal/server/evaluation/data/server.go index 8c90a0f88a..402a83a167 100644 --- a/internal/server/evaluation/data/server.go +++ b/internal/server/evaluation/data/server.go @@ -30,10 +30,6 @@ func (s *Server) RegisterGRPC(server *grpc.Server) { evaluation.RegisterDataServiceServer(server, s) } -func (s *Server) AllowsNamespaceScopedAuthentication(ctx context.Context) bool { - return true -} - func (s *Server) EvaluationSnapshotNamespace(ctx context.Context, r *evaluation.EvaluationNamespaceSnapshotRequest) (*evaluation.EvaluationNamespaceSnapshot, error) { // TODO(georgemac): support overriding via configuration and or metadata header environment := "default" diff --git a/internal/server/ofrep/middleware.go b/internal/server/evaluation/ofrep/middleware.go similarity index 97% rename from internal/server/ofrep/middleware.go rename to internal/server/evaluation/ofrep/middleware.go index 3e010d3368..d0a5d5baa7 100644 --- a/internal/server/ofrep/middleware.go +++ b/internal/server/evaluation/ofrep/middleware.go @@ -19,6 +19,8 @@ const ( errorCodeTargetingKeyMissing errorCode = "TARGETING_KEY_MISSING" errorCodeInvalidContext errorCode = "INVALID_CONTEXT" errorCodeParseGeneral errorCode = "GENERAL" + + statusFlagKeyPointer = "ofrep-flag-key" ) type errorSchema struct { diff --git a/internal/server/ofrep/middleware_test.go b/internal/server/evaluation/ofrep/middleware_test.go similarity index 68% rename from internal/server/ofrep/middleware_test.go rename to internal/server/evaluation/ofrep/middleware_test.go index 3df836d506..8d1373ac21 100644 --- a/internal/server/ofrep/middleware_test.go +++ b/internal/server/evaluation/ofrep/middleware_test.go @@ -3,6 +3,7 @@ package ofrep import ( "context" "errors" + "fmt" "net/http" "net/http/httptest" "testing" @@ -12,6 +13,7 @@ import ( "go.uber.org/zap/zaptest" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/structpb" ) func TestErrorHandler(t *testing.T) { @@ -62,3 +64,33 @@ func TestErrorHandler(t *testing.T) { }) } } + +func statusWithKey(st *status.Status, key string) (*status.Status, error) { + return st.WithDetails(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + statusFlagKeyPointer: structpb.NewStringValue(key), + }, + }) +} + +func newBadRequestError(key string, err error) error { + v := status.New(codes.InvalidArgument, err.Error()) + v, derr := statusWithKey(v, key) + if derr != nil { + return status.Errorf(codes.Internal, "failed to encode not bad request error") + } + return v.Err() +} + +func newFlagNotFoundError(key string) error { + v := status.New(codes.NotFound, fmt.Sprintf("flag was not found %s", key)) + v, derr := statusWithKey(v, key) + if derr != nil { + return status.Errorf(codes.Internal, "failed to encode not found error") + } + return v.Err() +} + +func newFlagMissingError() error { + return status.Error(codes.InvalidArgument, "flag key was not provided") +} diff --git a/internal/server/evaluation/ofrep/server.go b/internal/server/evaluation/ofrep/server.go new file mode 100644 index 0000000000..fe8e8fc2f2 --- /dev/null +++ b/internal/server/evaluation/ofrep/server.go @@ -0,0 +1,66 @@ +package ofrep + +import ( + "context" + + "go.flipt.io/flipt/internal/server/environments" + "go.uber.org/zap" + + "go.flipt.io/flipt/rpc/flipt/ofrep" + "google.golang.org/grpc" +) + +// Bridge is the interface between the OFREP specification to Flipt internals. +type Bridge interface { + // OFREPFlagEvaluation evaluates a single flag. + OFREPFlagEvaluation(ctx context.Context, input *ofrep.EvaluateFlagRequest) (*ofrep.EvaluationResponse, error) + // OFREPFlagEvaluationBulk evaluates a list of flags. + OFREPFlagEvaluationBulk(ctx context.Context, input *ofrep.EvaluateBulkRequest) (*ofrep.BulkEvaluationResponse, error) +} + +// Server servers the methods used by the OpenFeature Remote Evaluation Protocol. +// It will be used only with gRPC Gateway as there's no specification for gRPC itself. +type Server struct { + logger *zap.Logger + bridge Bridge + envs *environments.EnvironmentStore + ofrep.UnimplementedOFREPServiceServer +} + +// New constructs a new Server. +func New(logger *zap.Logger, bridge Bridge, envs *environments.EnvironmentStore) *Server { + return &Server{ + logger: logger, + bridge: bridge, + envs: envs, + } +} + +// RegisterGRPC registers the EvaluateServer onto the provided gRPC Server. +func (s *Server) RegisterGRPC(server *grpc.Server) { + ofrep.RegisterOFREPServiceServer(server, s) +} + +func (s *Server) SkipsAuthorization(ctx context.Context) bool { + return true +} + +func (s *Server) EvaluateFlag(ctx context.Context, r *ofrep.EvaluateFlagRequest) (*ofrep.EvaluationResponse, error) { + return s.bridge.OFREPFlagEvaluation(ctx, r) +} + +func (s *Server) EvaluateBulk(ctx context.Context, r *ofrep.EvaluateBulkRequest) (*ofrep.BulkEvaluationResponse, error) { + return s.bridge.OFREPFlagEvaluationBulk(ctx, r) +} + +// GetProviderConfiguration returns the configuration set by the running flipt instance. +func (s *Server) GetProviderConfiguration(_ context.Context, _ *ofrep.GetProviderConfigurationRequest) (*ofrep.GetProviderConfigurationResponse, error) { + return &ofrep.GetProviderConfigurationResponse{ + Name: "flipt", + Capabilities: &ofrep.Capabilities{ + FlagEvaluation: &ofrep.FlagEvaluation{ + SupportedTypes: []string{"string", "boolean"}, + }, + }, + }, nil +} diff --git a/internal/server/ofrep/extensions_test.go b/internal/server/evaluation/ofrep/server_test.go similarity index 79% rename from internal/server/ofrep/extensions_test.go rename to internal/server/evaluation/ofrep/server_test.go index 24ed15c30a..6d18faa9ac 100644 --- a/internal/server/ofrep/extensions_test.go +++ b/internal/server/evaluation/ofrep/server_test.go @@ -4,13 +4,17 @@ import ( "context" "testing" - "go.uber.org/zap/zaptest" - + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.flipt.io/flipt/internal/common" "go.flipt.io/flipt/rpc/flipt/ofrep" + "go.uber.org/zap/zaptest" ) +func Test_Server_SkipsAuthorization(t *testing.T) { + server := &Server{} + assert.True(t, server.SkipsAuthorization(context.Background())) +} + func TestGetProviderConfiguration(t *testing.T) { testCases := []struct { name string @@ -31,9 +35,7 @@ func TestGetProviderConfiguration(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - b := NewMockBridge(t) - store := common.NewMockStore(t) - s := New(zaptest.NewLogger(t), b, store) + s := New(zaptest.NewLogger(t), nil, nil) resp, err := s.GetProviderConfiguration(context.TODO(), &ofrep.GetProviderConfigurationRequest{}) diff --git a/internal/server/evaluation/ofrep_bridge.go b/internal/server/evaluation/ofrep_bridge.go index aec7a3c1e8..0d98cd41ec 100644 --- a/internal/server/evaluation/ofrep_bridge.go +++ b/internal/server/evaluation/ofrep_bridge.go @@ -3,17 +3,22 @@ package evaluation import ( "context" "errors" + "fmt" "strconv" "strings" - "go.flipt.io/flipt/internal/server/ofrep" - - "go.flipt.io/flipt/rpc/flipt/core" - rpcevaluation "go.flipt.io/flipt/rpc/flipt/evaluation" - + "github.com/google/uuid" + flipterrors "go.flipt.io/flipt/errors" fliptotel "go.flipt.io/flipt/internal/server/otel" "go.flipt.io/flipt/internal/storage" + "go.flipt.io/flipt/rpc/flipt/core" + rpcevaluation "go.flipt.io/flipt/rpc/flipt/evaluation" + "go.flipt.io/flipt/rpc/flipt/ofrep" "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/structpb" ) const ( @@ -21,35 +26,44 @@ const ( metaKeySegments = "segments" ) -func (s *Server) OFREPFlagEvaluation(ctx context.Context, input ofrep.EvaluationBridgeInput) (ofrep.EvaluationBridgeOutput, error) { +func (s *Server) OFREPFlagEvaluation(ctx context.Context, r *ofrep.EvaluateFlagRequest) (*ofrep.EvaluationResponse, error) { store, err := s.getEvalStore(ctx) if err != nil { - return ofrep.EvaluationBridgeOutput{}, err + return nil, err + } + + if r.Key == "" { + return nil, newFlagMissingError() } - flag, err := store.GetFlag(ctx, storage.NewResource(input.NamespaceKey, input.FlagKey)) + var ( + namespaceKey = getNamespace(ctx) + entityId = getTargetingKey(r.Context) + ) + + flag, err := store.GetFlag(ctx, storage.NewResource(namespaceKey, r.Key)) if err != nil { - return ofrep.EvaluationBridgeOutput{}, err + return nil, transformError(r.Key, err) } span := trace.SpanFromContext(ctx) span.SetAttributes( - fliptotel.AttributeNamespace.String(input.NamespaceKey), - fliptotel.AttributeFlag.String(input.FlagKey), + fliptotel.AttributeNamespace.String(namespaceKey), + fliptotel.AttributeFlag.String(r.Key), fliptotel.AttributeProviderName, ) req := &rpcevaluation.EvaluationRequest{ - NamespaceKey: input.NamespaceKey, - FlagKey: input.FlagKey, - EntityId: input.EntityId, - Context: input.Context, + NamespaceKey: namespaceKey, + FlagKey: r.Key, + EntityId: entityId, + Context: r.Context, } switch flag.Type { case core.FlagType_VARIANT_FLAG_TYPE: resp, err := s.variant(ctx, store, flag, req) if err != nil { - return ofrep.EvaluationBridgeOutput{}, err + return nil, transformError(r.Key, err) } span.SetAttributes( @@ -61,25 +75,34 @@ func (s *Server) OFREPFlagEvaluation(ctx context.Context, input ofrep.Evaluation fliptotel.AttributeFlagVariant(resp.VariantKey), ) - metadata := map[string]any{} + mm := map[string]any{} if len(resp.SegmentKeys) > 0 { - metadata[metaKeySegments] = strings.Join(resp.SegmentKeys, ",") + mm[metaKeySegments] = strings.Join(resp.SegmentKeys, ",") } if resp.VariantAttachment != "" { - metadata[metaKeyAttachment] = resp.VariantAttachment + mm[metaKeyAttachment] = resp.VariantAttachment } - return ofrep.EvaluationBridgeOutput{ - FlagKey: resp.FlagKey, - Reason: resp.Reason, + value, err := structpb.NewValue(resp.VariantKey) + if err != nil { + return nil, err + } + metadata, err := structpb.NewStruct(mm) + if err != nil { + return nil, err + } + return &ofrep.EvaluationResponse{ + Key: resp.FlagKey, + Reason: transformReason(resp.Reason), Variant: resp.VariantKey, - Value: resp.VariantKey, + Value: value, Metadata: metadata, }, nil + case core.FlagType_BOOLEAN_FLAG_TYPE: resp, err := s.boolean(ctx, store, flag, req) if err != nil { - return ofrep.EvaluationBridgeOutput{}, err + return nil, transformError(r.Key, err) } span.SetAttributes( @@ -88,14 +111,147 @@ func (s *Server) OFREPFlagEvaluation(ctx context.Context, input ofrep.Evaluation fliptotel.AttributeFlagVariant(strconv.FormatBool(resp.Enabled)), ) - return ofrep.EvaluationBridgeOutput{ - FlagKey: resp.FlagKey, - Variant: strconv.FormatBool(resp.Enabled), - Reason: resp.Reason, - Value: resp.Enabled, - Metadata: map[string]any{}, + value, err := structpb.NewValue(resp.Enabled) + if err != nil { + return nil, err + } + + return &ofrep.EvaluationResponse{ + Key: resp.FlagKey, + Reason: transformReason(resp.Reason), + Variant: strconv.FormatBool(resp.Enabled), + Value: value, }, nil default: - return ofrep.EvaluationBridgeOutput{}, errors.New("unsupported flag type for ofrep bridge") + return nil, errors.New("unsupported flag type for ofrep bridge") + } +} + +func (s *Server) OFREPFlagEvaluationBulk(ctx context.Context, r *ofrep.EvaluateBulkRequest) (*ofrep.BulkEvaluationResponse, error) { + store, err := s.getEvalStore(ctx) + if err != nil { + return nil, err + } + + var ( + namespaceKey = getNamespace(ctx) + flagKeys, ok = r.Context["flags"] + keys = strings.Split(flagKeys, ",") + ) + + if !ok { + flags, err := store.ListFlags(ctx, storage.ListWithOptions(storage.NewNamespace(namespaceKey))) + if err != nil { + return nil, err + } + + keys = make([]string, 0, len(flags.Results)) + for _, flag := range flags.Results { + switch flag.Type { + case core.FlagType_BOOLEAN_FLAG_TYPE: + keys = append(keys, flag.Key) + case core.FlagType_VARIANT_FLAG_TYPE: + if flag.Enabled { + keys = append(keys, flag.Key) + } + } + } + } + + responses := make([]*ofrep.EvaluationResponse, 0, len(keys)) + for _, flagKey := range keys { + resp, err := s.OFREPFlagEvaluation(ctx, &ofrep.EvaluateFlagRequest{ + Key: flagKey, + Context: r.Context, + }) + if err != nil { + return nil, err + } + responses = append(responses, resp) + } + + return &ofrep.BulkEvaluationResponse{ + Flags: responses, + }, nil +} + +const ofrepCtxTargetingKey = "targetingKey" + +func getTargetingKey(context map[string]string) string { + // https://openfeature.dev/docs/reference/concepts/evaluation-context/#targeting-key + if targetingKey, ok := context[ofrepCtxTargetingKey]; ok { + return targetingKey + } + return uuid.NewString() +} + +func getNamespace(ctx context.Context) string { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return "default" + } + + namespace := md.Get("x-flipt-namespace") + if len(namespace) == 0 { + return "default" + } + + return namespace[0] +} + +func transformReason(reason rpcevaluation.EvaluationReason) ofrep.EvaluateReason { + switch reason { + case rpcevaluation.EvaluationReason_FLAG_DISABLED_EVALUATION_REASON: + return ofrep.EvaluateReason_DISABLED + case rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON: + return ofrep.EvaluateReason_TARGETING_MATCH + case rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON: + return ofrep.EvaluateReason_DEFAULT + default: + return ofrep.EvaluateReason_UNKNOWN + } +} + +func transformError(key string, err error) error { + switch { + case flipterrors.AsMatch[flipterrors.ErrInvalid](err): + return newBadRequestError(key, err) + case flipterrors.AsMatch[flipterrors.ErrValidation](err): + return newBadRequestError(key, err) + case flipterrors.AsMatch[flipterrors.ErrNotFound](err): + return newFlagNotFoundError(key) + } + return err +} + +const statusFlagKeyPointer = "ofrep-flag-key" + +func statusWithKey(st *status.Status, key string) (*status.Status, error) { + return st.WithDetails(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + statusFlagKeyPointer: structpb.NewStringValue(key), + }, + }) +} + +func newBadRequestError(key string, err error) error { + v := status.New(codes.InvalidArgument, err.Error()) + v, derr := statusWithKey(v, key) + if derr != nil { + return status.Errorf(codes.Internal, "failed to encode not bad request error") + } + return v.Err() +} + +func newFlagNotFoundError(key string) error { + v := status.New(codes.NotFound, fmt.Sprintf("flag was not found %s", key)) + v, derr := statusWithKey(v, key) + if derr != nil { + return status.Errorf(codes.Internal, "failed to encode not found error") } + return v.Err() +} + +func newFlagMissingError() error { + return status.Error(codes.InvalidArgument, "flag key was not provided") } diff --git a/internal/server/evaluation/ofrep_bridge_test.go b/internal/server/evaluation/ofrep_bridge_test.go index ed1d4691c5..104bef0a5f 100644 --- a/internal/server/evaluation/ofrep_bridge_test.go +++ b/internal/server/evaluation/ofrep_bridge_test.go @@ -8,11 +8,11 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.flipt.io/flipt/internal/server/environments" - "go.flipt.io/flipt/internal/server/ofrep" "go.flipt.io/flipt/internal/storage" "go.flipt.io/flipt/rpc/flipt/core" - rpcevaluation "go.flipt.io/flipt/rpc/flipt/evaluation" + "go.flipt.io/flipt/rpc/flipt/ofrep" "go.uber.org/zap/zaptest" + "google.golang.org/grpc/metadata" ) func TestOFREPFlagEvaluation_Variant(t *testing.T) { @@ -65,9 +65,12 @@ func TestOFREPFlagEvaluation_Variant(t *testing.T) { }, }, nil) - output, err := s.OFREPFlagEvaluation(context.TODO(), ofrep.EvaluationBridgeInput{ - FlagKey: flagKey, - NamespaceKey: namespaceKey, + ctx := metadata.NewIncomingContext(context.TODO(), metadata.New(map[string]string{ + "x-flipt-namespace": namespaceKey, + })) + + output, err := s.OFREPFlagEvaluation(ctx, &ofrep.EvaluateFlagRequest{ + Key: flagKey, Context: map[string]string{ "hello": "world", "targetingKey": "12345", @@ -75,10 +78,10 @@ func TestOFREPFlagEvaluation_Variant(t *testing.T) { }) require.NoError(t, err) - assert.Equal(t, flagKey, output.FlagKey) - assert.Equal(t, rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON, output.Reason) + assert.Equal(t, flagKey, output.Key) + assert.Equal(t, ofrep.EvaluateReason_TARGETING_MATCH, output.Reason) assert.Equal(t, "boz", output.Variant) - assert.Equal(t, "boz", output.Value) + assert.Equal(t, "boz", output.Value.GetStringValue()) } func TestOFREPFlagEvaluation_Boolean(t *testing.T) { @@ -104,17 +107,20 @@ func TestOFREPFlagEvaluation_Boolean(t *testing.T) { store.On("GetEvaluationRollouts", mock.Anything, storage.NewResource(namespaceKey, flagKey)).Return([]*storage.EvaluationRollout{}, nil) - output, err := s.OFREPFlagEvaluation(context.TODO(), ofrep.EvaluationBridgeInput{ - FlagKey: flagKey, - NamespaceKey: namespaceKey, + ctx := metadata.NewIncomingContext(context.TODO(), metadata.New(map[string]string{ + "x-flipt-namespace": namespaceKey, + })) + + output, err := s.OFREPFlagEvaluation(ctx, &ofrep.EvaluateFlagRequest{ + Key: flagKey, Context: map[string]string{ "targetingKey": "12345", }, }) require.NoError(t, err) - assert.Equal(t, flagKey, output.FlagKey) - assert.Equal(t, rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON, output.Reason) + assert.Equal(t, flagKey, output.Key) + assert.Equal(t, ofrep.EvaluateReason_DEFAULT, output.Reason) assert.Equal(t, "true", output.Variant) - assert.Equal(t, true, output.Value) + assert.Equal(t, true, output.Value.GetBoolValue()) } diff --git a/internal/server/evaluation/server.go b/internal/server/evaluation/server.go index c08a7692da..32e785cb9f 100644 --- a/internal/server/evaluation/server.go +++ b/internal/server/evaluation/server.go @@ -35,10 +35,6 @@ func (s *Server) RegisterGRPC(server *grpc.Server) { evaluation.RegisterEvaluationServiceServer(server, s) } -func (s *Server) AllowsNamespaceScopedAuthentication(ctx context.Context) bool { - return true -} - func (s *Server) SkipsAuthorization(ctx context.Context) bool { return true } diff --git a/internal/server/evaluation/server_test.go b/internal/server/evaluation/server_test.go index b576462f1a..e9719869a5 100644 --- a/internal/server/evaluation/server_test.go +++ b/internal/server/evaluation/server_test.go @@ -7,11 +7,6 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_Server_AllowsNamespaceScopedAuthentication(t *testing.T) { - server := &Server{} - assert.True(t, server.AllowsNamespaceScopedAuthentication(context.Background())) -} - func Test_Server_SkipsAuthorization(t *testing.T) { server := &Server{} assert.True(t, server.SkipsAuthorization(context.Background())) diff --git a/internal/server/ofrep/errors.go b/internal/server/ofrep/errors.go deleted file mode 100644 index ab45c020b0..0000000000 --- a/internal/server/ofrep/errors.go +++ /dev/null @@ -1,41 +0,0 @@ -package ofrep - -import ( - "fmt" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/structpb" -) - -const statusFlagKeyPointer = "ofrep-flag-key" - -func statusWithKey(st *status.Status, key string) (*status.Status, error) { - return st.WithDetails(&structpb.Struct{ - Fields: map[string]*structpb.Value{ - statusFlagKeyPointer: structpb.NewStringValue(key), - }, - }) -} - -func newBadRequestError(key string, err error) error { - v := status.New(codes.InvalidArgument, err.Error()) - v, derr := statusWithKey(v, key) - if derr != nil { - return status.Errorf(codes.Internal, "failed to encode not bad request error") - } - return v.Err() -} - -func newFlagNotFoundError(key string) error { - v := status.New(codes.NotFound, fmt.Sprintf("flag was not found %s", key)) - v, derr := statusWithKey(v, key) - if derr != nil { - return status.Errorf(codes.Internal, "failed to encode not found error") - } - return v.Err() -} - -func newFlagMissingError() error { - return status.Error(codes.InvalidArgument, "flag key was not provided") -} diff --git a/internal/server/ofrep/evaluation.go b/internal/server/ofrep/evaluation.go deleted file mode 100644 index 9a6f886711..0000000000 --- a/internal/server/ofrep/evaluation.go +++ /dev/null @@ -1,165 +0,0 @@ -package ofrep - -import ( - "context" - "strings" - - "github.com/google/uuid" - flipterrors "go.flipt.io/flipt/errors" - "go.flipt.io/flipt/internal/storage" - - "go.flipt.io/flipt/rpc/flipt/core" - rpcevaluation "go.flipt.io/flipt/rpc/flipt/evaluation" - "go.flipt.io/flipt/rpc/flipt/ofrep" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/structpb" -) - -const ofrepCtxTargetingKey = "targetingKey" - -func (s *Server) EvaluateFlag(ctx context.Context, r *ofrep.EvaluateFlagRequest) (*ofrep.EvaluatedFlag, error) { - if r.Key == "" { - return nil, newFlagMissingError() - } - - var ( - flagKey = r.Key - namespaceKey = getNamespace(ctx) - entityId = getTargetingKey(r.Context) - ) - - output, err := s.bridge.OFREPFlagEvaluation(ctx, EvaluationBridgeInput{ - FlagKey: flagKey, - NamespaceKey: namespaceKey, - EntityId: entityId, - Context: r.Context, - }) - if err != nil { - return nil, transformError(flagKey, err) - } - - return transformOutput(output) -} - -func (s *Server) EvaluateBulk(ctx context.Context, r *ofrep.EvaluateBulkRequest) (*ofrep.BulkEvaluationResponse, error) { - var ( - entityId = getTargetingKey(r.Context) - namespaceKey = getNamespace(ctx) - ) - - var ( - flagKeys, ok = r.Context["flags"] - keys = strings.Split(flagKeys, ",") - ) - - if !ok { - flags, err := s.store.ListFlags(ctx, storage.ListWithOptions(storage.NewNamespace(namespaceKey))) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to fetch list of flags") - } - keys = make([]string, 0, len(flags.Results)) - for _, flag := range flags.Results { - switch flag.Type { - case core.FlagType_BOOLEAN_FLAG_TYPE: - keys = append(keys, flag.Key) - case core.FlagType_VARIANT_FLAG_TYPE: - if flag.Enabled { - keys = append(keys, flag.Key) - } - } - } - } - - flags := make([]*ofrep.EvaluatedFlag, 0, len(keys)) - - for _, key := range keys { - key = strings.TrimSpace(key) - o, err := s.bridge.OFREPFlagEvaluation(ctx, EvaluationBridgeInput{ - FlagKey: key, - NamespaceKey: namespaceKey, - EntityId: entityId, - Context: r.Context, - }) - if err != nil { - return nil, transformError(key, err) - } - - evaluation, err := transformOutput(o) - if err != nil { - return nil, transformError(key, err) - } - flags = append(flags, evaluation) - } - - return &ofrep.BulkEvaluationResponse{ - Flags: flags, - }, nil -} - -func transformOutput(output EvaluationBridgeOutput) (*ofrep.EvaluatedFlag, error) { - value, err := structpb.NewValue(output.Value) - if err != nil { - return nil, err - } - metadata, err := structpb.NewStruct(output.Metadata) - if err != nil { - return nil, err - } - - return &ofrep.EvaluatedFlag{ - Key: output.FlagKey, - Reason: transformReason(output.Reason), - Variant: output.Variant, - Value: value, - Metadata: metadata, - }, nil -} - -func getTargetingKey(context map[string]string) string { - // https://openfeature.dev/docs/reference/concepts/evaluation-context/#targeting-key - if targetingKey, ok := context[ofrepCtxTargetingKey]; ok { - return targetingKey - } - return uuid.NewString() -} - -func getNamespace(ctx context.Context) string { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return "default" - } - - namespace := md.Get("x-flipt-namespace") - if len(namespace) == 0 { - return "default" - } - - return namespace[0] -} - -func transformReason(reason rpcevaluation.EvaluationReason) ofrep.EvaluateReason { - switch reason { - case rpcevaluation.EvaluationReason_FLAG_DISABLED_EVALUATION_REASON: - return ofrep.EvaluateReason_DISABLED - case rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON: - return ofrep.EvaluateReason_TARGETING_MATCH - case rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON: - return ofrep.EvaluateReason_DEFAULT - default: - return ofrep.EvaluateReason_UNKNOWN - } -} - -func transformError(key string, err error) error { - switch { - case flipterrors.AsMatch[flipterrors.ErrInvalid](err): - return newBadRequestError(key, err) - case flipterrors.AsMatch[flipterrors.ErrValidation](err): - return newBadRequestError(key, err) - case flipterrors.AsMatch[flipterrors.ErrNotFound](err): - return newFlagNotFoundError(key) - } - return err -} diff --git a/internal/server/ofrep/evaluation_test.go b/internal/server/ofrep/evaluation_test.go deleted file mode 100644 index 4f21964d0f..0000000000 --- a/internal/server/ofrep/evaluation_test.go +++ /dev/null @@ -1,262 +0,0 @@ -package ofrep - -import ( - "context" - "errors" - "io" - "testing" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - flipterrors "go.flipt.io/flipt/errors" - "google.golang.org/grpc/codes" - - "google.golang.org/grpc/metadata" - - "google.golang.org/protobuf/proto" - - "github.com/stretchr/testify/assert" - "go.flipt.io/flipt/internal/common" - "go.flipt.io/flipt/internal/storage" - "go.flipt.io/flipt/rpc/flipt/core" - rpcevaluation "go.flipt.io/flipt/rpc/flipt/evaluation" - "go.flipt.io/flipt/rpc/flipt/ofrep" - "go.uber.org/zap/zaptest" - "google.golang.org/protobuf/types/known/structpb" -) - -func TestEvaluateFlag_Success(t *testing.T) { - t.Run("should use the default namespace when no one was provided", func(t *testing.T) { - ctx := context.TODO() - flagKey := "flag-key" - expectedResponse := &ofrep.EvaluatedFlag{ - Key: flagKey, - Reason: ofrep.EvaluateReason_DEFAULT, - Variant: "false", - Value: structpb.NewBoolValue(false), - Metadata: &structpb.Struct{Fields: make(map[string]*structpb.Value)}, - } - bridge := NewMockBridge(t) - store := common.NewMockStore(t) - s := New(zaptest.NewLogger(t), bridge, store) - - bridge.On("OFREPFlagEvaluation", ctx, EvaluationBridgeInput{ - FlagKey: flagKey, - NamespaceKey: "default", - EntityId: "testing-key", - Context: map[string]string{ - ofrepCtxTargetingKey: "testing-key", - "hello": "world", - }, - }).Return(EvaluationBridgeOutput{ - FlagKey: flagKey, - Reason: rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON, - Variant: "false", - Value: false, - }, nil) - - actualResponse, err := s.EvaluateFlag(ctx, &ofrep.EvaluateFlagRequest{ - Key: flagKey, - Context: map[string]string{ - ofrepCtxTargetingKey: "testing-key", - "hello": "world", - }, - }) - require.NoError(t, err) - assert.True(t, proto.Equal(expectedResponse, actualResponse)) - }) - - t.Run("should use the given namespace when one was provided", func(t *testing.T) { - namespace := "test-namespace" - ctx := metadata.NewIncomingContext(context.TODO(), metadata.New(map[string]string{ - "x-flipt-namespace": namespace, - })) - flagKey := "flag-key" - expectedResponse := &ofrep.EvaluatedFlag{ - Key: flagKey, - Reason: ofrep.EvaluateReason_DISABLED, - Variant: "true", - Value: structpb.NewBoolValue(true), - Metadata: &structpb.Struct{Fields: make(map[string]*structpb.Value)}, - } - bridge := NewMockBridge(t) - store := common.NewMockStore(t) - s := New(zaptest.NewLogger(t), bridge, store) - - bridge.On("OFREPFlagEvaluation", ctx, EvaluationBridgeInput{ - FlagKey: flagKey, - EntityId: "string", - NamespaceKey: namespace, - Context: map[string]string{ - ofrepCtxTargetingKey: "string", - }, - }).Return(EvaluationBridgeOutput{ - FlagKey: flagKey, - Reason: rpcevaluation.EvaluationReason_FLAG_DISABLED_EVALUATION_REASON, - Variant: "true", - Value: true, - }, nil) - - actualResponse, err := s.EvaluateFlag(ctx, &ofrep.EvaluateFlagRequest{ - Key: flagKey, - Context: map[string]string{ofrepCtxTargetingKey: "string"}, - }) - require.NoError(t, err) - assert.True(t, proto.Equal(expectedResponse, actualResponse)) - }) -} - -func TestEvaluateFlag_Failure(t *testing.T) { - testCases := []struct { - name string - req *ofrep.EvaluateFlagRequest - err error - expectedCode codes.Code - expectedErr error - }{ - { - name: "should return a targeting key missing error when a key is not provided", - req: &ofrep.EvaluateFlagRequest{}, - expectedErr: newFlagMissingError(), - }, - { - name: "should return a bad request error when an invalid is returned by the bridge", - req: &ofrep.EvaluateFlagRequest{Key: "test-flag"}, - err: flipterrors.ErrInvalid("invalid"), - expectedErr: newBadRequestError("test-flag", flipterrors.ErrInvalid("invalid")), - }, - { - name: "should return a bad request error when a validation error is returned by the bridge", - req: &ofrep.EvaluateFlagRequest{Key: "test-flag"}, - err: flipterrors.InvalidFieldError("field", "reason"), - expectedErr: newBadRequestError("test-flag", flipterrors.InvalidFieldError("field", "reason")), - }, - { - name: "should return a not found error when a flag not found error is returned by the bridge", - req: &ofrep.EvaluateFlagRequest{Key: "test-flag"}, - err: flipterrors.ErrNotFound("test-flag"), - expectedErr: newFlagNotFoundError("test-flag"), - }, - { - name: "should return a general error", - req: &ofrep.EvaluateFlagRequest{Key: "test-flag"}, - err: io.ErrNoProgress, - expectedErr: io.ErrNoProgress, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - ctx := context.TODO() - bridge := NewMockBridge(t) - store := &common.StoreMock{} - s := New(zaptest.NewLogger(t), bridge, store) - if tc.req.Key != "" { - bridge.On("OFREPFlagEvaluation", ctx, mock.Anything).Return(EvaluationBridgeOutput{}, tc.err) - } - - _, err := s.EvaluateFlag(ctx, tc.req) - - assert.Equal(t, tc.expectedErr, err) - }) - } -} - -func TestEvaluateBulkSuccess(t *testing.T) { - ctx := context.TODO() - flagKey := "flag-key" - expectedResponse := []*ofrep.EvaluatedFlag{{ - Key: flagKey, - Reason: ofrep.EvaluateReason_DEFAULT, - Variant: "false", - Value: structpb.NewBoolValue(false), - Metadata: &structpb.Struct{ - Fields: map[string]*structpb.Value{"attachment": structpb.NewStringValue("my value")}, - }, - }} - bridge := NewMockBridge(t) - store := &common.StoreMock{} - s := New(zaptest.NewLogger(t), bridge, store) - - t.Run("with flags in the evaluation request", func(t *testing.T) { - bridge.On("OFREPFlagEvaluation", ctx, EvaluationBridgeInput{ - FlagKey: flagKey, - NamespaceKey: "default", - EntityId: "targeting", - Context: map[string]string{ - ofrepCtxTargetingKey: "targeting", - "flags": flagKey, - }, - }).Return(EvaluationBridgeOutput{ - FlagKey: flagKey, - Reason: rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON, - Variant: "false", - Value: false, - Metadata: map[string]any{"attachment": "my value"}, - }, nil) - actualResponse, err := s.EvaluateBulk(ctx, &ofrep.EvaluateBulkRequest{ - Context: map[string]string{ - ofrepCtxTargetingKey: "targeting", - "flags": flagKey, - }, - }) - require.NoError(t, err) - require.Len(t, actualResponse.Flags, len(expectedResponse)) - for i, expected := range expectedResponse { - assert.True(t, proto.Equal(expected, actualResponse.Flags[i])) - } - }) - - t.Run("without flags in the evaluation request", func(t *testing.T) { - bridge.On("OFREPFlagEvaluation", ctx, EvaluationBridgeInput{ - FlagKey: flagKey, - NamespaceKey: "default", - EntityId: "targeting", - Context: map[string]string{ - ofrepCtxTargetingKey: "targeting", - }, - }).Return(EvaluationBridgeOutput{ - FlagKey: flagKey, - Reason: rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON, - Variant: "false", - Value: false, - Metadata: map[string]any{"attachment": "my value"}, - }, nil) - - store.On("ListFlags", mock.Anything, storage.ListWithOptions(storage.NewNamespace("default"))).Return( - storage.ResultSet[*core.Flag]{ - Results: []*core.Flag{ - {Key: flagKey, Type: core.FlagType_VARIANT_FLAG_TYPE, Enabled: true}, - {Key: "disabled", Type: core.FlagType_VARIANT_FLAG_TYPE}, - }, - NextPageToken: "YmFy", - }, nil).Once() - - actualResponse, err := s.EvaluateBulk(ctx, &ofrep.EvaluateBulkRequest{ - Context: map[string]string{ - ofrepCtxTargetingKey: "targeting", - }, - }) - require.NoError(t, err) - require.Len(t, actualResponse.Flags, len(expectedResponse)) - for i, expected := range expectedResponse { - assert.True(t, proto.Equal(expected, actualResponse.Flags[i])) - } - }) - - t.Run("without flags in the evaluation request failed fetch the flags", func(t *testing.T) { - store.On("ListFlags", mock.Anything, storage.ListWithOptions(storage.NewNamespace("default"))).Return( - storage.ResultSet[*core.Flag]{ - Results: nil, - NextPageToken: "", - }, errors.New("failed to fetch flags")).Once() - - _, err := s.EvaluateBulk(ctx, &ofrep.EvaluateBulkRequest{ - Context: map[string]string{ - ofrepCtxTargetingKey: "targeting", - }, - }) - require.Error(t, err) - require.ErrorContains(t, err, "code = Internal desc = failed to fetch list of flags") - }) -} diff --git a/internal/server/ofrep/extensions.go b/internal/server/ofrep/extensions.go deleted file mode 100644 index 3119fc3514..0000000000 --- a/internal/server/ofrep/extensions.go +++ /dev/null @@ -1,19 +0,0 @@ -package ofrep - -import ( - "context" - - "go.flipt.io/flipt/rpc/flipt/ofrep" -) - -// GetProviderConfiguration returns the configuration set by the running flipt instance. -func (s *Server) GetProviderConfiguration(_ context.Context, _ *ofrep.GetProviderConfigurationRequest) (*ofrep.GetProviderConfigurationResponse, error) { - return &ofrep.GetProviderConfigurationResponse{ - Name: "flipt", - Capabilities: &ofrep.Capabilities{ - FlagEvaluation: &ofrep.FlagEvaluation{ - SupportedTypes: []string{"string", "boolean"}, - }, - }, - }, nil -} diff --git a/internal/server/ofrep/mock_bridge.go b/internal/server/ofrep/mock_bridge.go deleted file mode 100644 index 01405cd66c..0000000000 --- a/internal/server/ofrep/mock_bridge.go +++ /dev/null @@ -1,56 +0,0 @@ -// Code generated by mockery v2.43.0. DO NOT EDIT. - -package ofrep - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" -) - -// MockBridge is an autogenerated mock type for the Bridge type -type MockBridge struct { - mock.Mock -} - -// OFREPFlagEvaluation provides a mock function with given fields: ctx, input -func (_m *MockBridge) OFREPFlagEvaluation(ctx context.Context, input EvaluationBridgeInput) (EvaluationBridgeOutput, error) { - ret := _m.Called(ctx, input) - - if len(ret) == 0 { - panic("no return value specified for OFREPFlagEvaluation") - } - - var r0 EvaluationBridgeOutput - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, EvaluationBridgeInput) (EvaluationBridgeOutput, error)); ok { - return rf(ctx, input) - } - if rf, ok := ret.Get(0).(func(context.Context, EvaluationBridgeInput) EvaluationBridgeOutput); ok { - r0 = rf(ctx, input) - } else { - r0 = ret.Get(0).(EvaluationBridgeOutput) - } - - if rf, ok := ret.Get(1).(func(context.Context, EvaluationBridgeInput) error); ok { - r1 = rf(ctx, input) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewMockBridge creates a new instance of MockBridge. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockBridge(t interface { - mock.TestingT - Cleanup(func()) -}) *MockBridge { - mock := &MockBridge{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/server/ofrep/server.go b/internal/server/ofrep/server.go deleted file mode 100644 index b34e22b50e..0000000000 --- a/internal/server/ofrep/server.go +++ /dev/null @@ -1,67 +0,0 @@ -package ofrep - -import ( - "context" - - "go.flipt.io/flipt/internal/storage" - "go.flipt.io/flipt/rpc/flipt/core" - rpcevaluation "go.flipt.io/flipt/rpc/flipt/evaluation" - "go.uber.org/zap" - - "go.flipt.io/flipt/rpc/flipt/ofrep" - "google.golang.org/grpc" -) - -// EvaluationBridgeInput is the input for the bridge between OFREP specficiation to Flipt internals. -type EvaluationBridgeInput struct { - FlagKey string - NamespaceKey string - EntityId string - Context map[string]string -} - -// EvaluationBridgeOutput is the input for the bridge between Flipt internals and OFREP specficiation. -type EvaluationBridgeOutput struct { - FlagKey string - Reason rpcevaluation.EvaluationReason - Variant string - Value any - Metadata map[string]any -} - -// Bridge is the interface between the OFREP specification to Flipt internals. -type Bridge interface { - // OFREPFlagEvaluation evaluates a single flag. - OFREPFlagEvaluation(ctx context.Context, input EvaluationBridgeInput) (EvaluationBridgeOutput, error) -} - -type Storer interface { - ListFlags(ctx context.Context, req *storage.ListRequest[storage.NamespaceRequest]) (storage.ResultSet[*core.Flag], error) -} - -// Server servers the methods used by the OpenFeature Remote Evaluation Protocol. -// It will be used only with gRPC Gateway as there's no specification for gRPC itself. -type Server struct { - logger *zap.Logger - bridge Bridge - store Storer - ofrep.UnimplementedOFREPServiceServer -} - -// New constructs a new Server. -func New(logger *zap.Logger, bridge Bridge, store Storer) *Server { - return &Server{ - logger: logger, - bridge: bridge, - store: store, - } -} - -// RegisterGRPC registers the EvaluateServer onto the provided gRPC Server. -func (s *Server) RegisterGRPC(server *grpc.Server) { - ofrep.RegisterOFREPServiceServer(server, s) -} - -func (s *Server) SkipsAuthorization(ctx context.Context) bool { - return true -} diff --git a/internal/server/ofrep/server_test.go b/internal/server/ofrep/server_test.go deleted file mode 100644 index 217c8ad5fe..0000000000 --- a/internal/server/ofrep/server_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package ofrep - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_Server_SkipsAuthorization(t *testing.T) { - server := &Server{} - assert.True(t, server.SkipsAuthorization(context.Background())) -} \ No newline at end of file diff --git a/internal/server/server.go b/internal/server/server.go index 68908ea26c..7464ab3fea 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -37,10 +37,6 @@ func (s *Server) RegisterGRPC(server *grpc.Server) { flipt.RegisterFliptServer(server, s) } -func (s *Server) AllowsNamespaceScopedAuthentication(ctx context.Context) bool { - return true -} - func (s *Server) getStore(ctx context.Context) (storage.ReadOnlyStore, error) { return s.store.GetDefault(ctx).EvaluationStore() } diff --git a/rpc/flipt/ofrep/ofrep.pb.go b/rpc/flipt/ofrep/ofrep.pb.go index 1c33c008eb..3933eaceb5 100644 --- a/rpc/flipt/ofrep/ofrep.pb.go +++ b/rpc/flipt/ofrep/ofrep.pb.go @@ -409,7 +409,7 @@ func (x *EvaluateFlagRequest) GetContext() map[string]string { return nil } -type EvaluatedFlag struct { +type EvaluationResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Reason EvaluateReason `protobuf:"varint,2,opt,name=reason,proto3,enum=flipt.ofrep.EvaluateReason" json:"reason,omitempty"` @@ -420,20 +420,20 @@ type EvaluatedFlag struct { sizeCache protoimpl.SizeCache } -func (x *EvaluatedFlag) Reset() { - *x = EvaluatedFlag{} +func (x *EvaluationResponse) Reset() { + *x = EvaluationResponse{} mi := &file_ofrep_ofrep_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *EvaluatedFlag) String() string { +func (x *EvaluationResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*EvaluatedFlag) ProtoMessage() {} +func (*EvaluationResponse) ProtoMessage() {} -func (x *EvaluatedFlag) ProtoReflect() protoreflect.Message { +func (x *EvaluationResponse) ProtoReflect() protoreflect.Message { mi := &file_ofrep_ofrep_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -445,40 +445,40 @@ func (x *EvaluatedFlag) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use EvaluatedFlag.ProtoReflect.Descriptor instead. -func (*EvaluatedFlag) Descriptor() ([]byte, []int) { +// Deprecated: Use EvaluationResponse.ProtoReflect.Descriptor instead. +func (*EvaluationResponse) Descriptor() ([]byte, []int) { return file_ofrep_ofrep_proto_rawDescGZIP(), []int{7} } -func (x *EvaluatedFlag) GetKey() string { +func (x *EvaluationResponse) GetKey() string { if x != nil { return x.Key } return "" } -func (x *EvaluatedFlag) GetReason() EvaluateReason { +func (x *EvaluationResponse) GetReason() EvaluateReason { if x != nil { return x.Reason } return EvaluateReason_UNKNOWN } -func (x *EvaluatedFlag) GetVariant() string { +func (x *EvaluationResponse) GetVariant() string { if x != nil { return x.Variant } return "" } -func (x *EvaluatedFlag) GetMetadata() *structpb.Struct { +func (x *EvaluationResponse) GetMetadata() *structpb.Struct { if x != nil { return x.Metadata } return nil } -func (x *EvaluatedFlag) GetValue() *structpb.Value { +func (x *EvaluationResponse) GetValue() *structpb.Value { if x != nil { return x.Value } @@ -531,7 +531,7 @@ func (x *EvaluateBulkRequest) GetContext() map[string]string { type BulkEvaluationResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Flags []*EvaluatedFlag `protobuf:"bytes,1,rep,name=flags,proto3" json:"flags,omitempty"` + Flags []*EvaluationResponse `protobuf:"bytes,1,rep,name=flags,proto3" json:"flags,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -566,7 +566,7 @@ func (*BulkEvaluationResponse) Descriptor() ([]byte, []int) { return file_ofrep_ofrep_proto_rawDescGZIP(), []int{9} } -func (x *BulkEvaluationResponse) GetFlags() []*EvaluatedFlag { +func (x *BulkEvaluationResponse) GetFlags() []*EvaluationResponse { if x != nil { return x.Flags } @@ -632,75 +632,76 @@ var file_ofrep_ofrep_proto_rawDesc = string([]byte{ 0x3a, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd3, 0x01, 0x0a, 0x0d, - 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x64, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x33, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x45, 0x76, - 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x33, - 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x13, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x42, 0x75, - 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x07, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, - 0x65, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, - 0x0a, 0x16, 0x42, 0x75, 0x6c, 0x6b, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x64, 0x46, - 0x6c, 0x61, 0x67, 0x42, 0x03, 0xe0, 0x41, 0x02, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x2a, - 0x4d, 0x0a, 0x0e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, - 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, - 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, - 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x03, 0x32, 0x80, - 0x04, 0x0a, 0x0c, 0x4f, 0x46, 0x52, 0x45, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0xb0, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0xba, 0x47, 0x15, 0x2a, 0x13, - 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x6f, 0x66, 0x72, 0x65, - 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x8e, 0x01, 0x0a, 0x0c, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x46, - 0x6c, 0x61, 0x67, 0x12, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, - 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, - 0x72, 0x65, 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x64, 0x46, 0x6c, 0x61, - 0x67, 0x22, 0x40, 0xba, 0x47, 0x14, 0x2a, 0x12, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x65, 0x76, - 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, - 0x3a, 0x01, 0x2a, 0x22, 0x1e, 0x2f, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x65, - 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x2f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x2f, 0x7b, 0x6b, - 0x65, 0x79, 0x7d, 0x12, 0x91, 0x01, 0x0a, 0x0c, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, - 0x42, 0x75, 0x6c, 0x6b, 0x12, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, - 0x65, 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x42, 0x75, 0x6c, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, - 0x66, 0x72, 0x65, 0x70, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0xba, 0x47, 0x14, - 0x2a, 0x12, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, - 0x42, 0x75, 0x6c, 0x6b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x22, 0x18, 0x2f, - 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, - 0x65, 0x2f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x1a, 0x18, 0xfa, 0xd2, 0xe4, 0x93, 0x02, 0x12, 0x12, - 0x10, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x3a, 0x73, 0x64, 0x6b, 0x3a, 0x69, 0x67, 0x6e, 0x6f, 0x72, - 0x65, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x6f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, - 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2f, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd8, 0x01, 0x0a, 0x12, + 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, + 0x65, 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x61, 0x72, + 0x69, 0x61, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x61, 0x72, 0x69, + 0x61, 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x13, 0x45, 0x76, 0x61, 0x6c, 0x75, + 0x61, 0x74, 0x65, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, + 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2d, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x45, 0x76, + 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x54, 0x0a, 0x16, 0x42, 0x75, 0x6c, 0x6b, 0x45, 0x76, 0x61, 0x6c, 0x75, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, + 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x03, 0xe0, + 0x41, 0x02, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x2a, 0x4d, 0x0a, 0x0e, 0x45, 0x76, 0x61, + 0x6c, 0x75, 0x61, 0x74, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, + 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, + 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, + 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x03, 0x32, 0x85, 0x04, 0x0a, 0x0c, 0x4f, 0x46, 0x52, + 0x45, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xb0, 0x01, 0x0a, 0x18, 0x47, 0x65, + 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, + 0x66, 0x72, 0x65, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, + 0x65, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x37, 0xba, 0x47, 0x15, 0x2a, 0x13, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x93, 0x01, 0x0a, + 0x0c, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x20, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, + 0x75, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x45, 0x76, + 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x40, 0xba, 0x47, 0x14, 0x2a, 0x12, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x65, 0x76, 0x61, + 0x6c, 0x75, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, + 0x01, 0x2a, 0x22, 0x1e, 0x2f, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, + 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x2f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x2f, 0x7b, 0x6b, 0x65, + 0x79, 0x7d, 0x12, 0x91, 0x01, 0x0a, 0x0c, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x42, + 0x75, 0x6c, 0x6b, 0x12, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, 0x72, 0x65, + 0x70, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x6f, 0x66, + 0x72, 0x65, 0x70, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0xba, 0x47, 0x14, 0x2a, + 0x12, 0x6f, 0x66, 0x72, 0x65, 0x70, 0x2e, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x42, + 0x75, 0x6c, 0x6b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x22, 0x18, 0x2f, 0x6f, + 0x66, 0x72, 0x65, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, + 0x2f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x1a, 0x18, 0xfa, 0xd2, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x3a, 0x73, 0x64, 0x6b, 0x3a, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, + 0x42, 0x23, 0x5a, 0x21, 0x67, 0x6f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2f, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, + 0x6f, 0x66, 0x72, 0x65, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -726,7 +727,7 @@ var file_ofrep_ofrep_proto_goTypes = []any{ (*Polling)(nil), // 5: flipt.ofrep.Polling (*FlagEvaluation)(nil), // 6: flipt.ofrep.FlagEvaluation (*EvaluateFlagRequest)(nil), // 7: flipt.ofrep.EvaluateFlagRequest - (*EvaluatedFlag)(nil), // 8: flipt.ofrep.EvaluatedFlag + (*EvaluationResponse)(nil), // 8: flipt.ofrep.EvaluationResponse (*EvaluateBulkRequest)(nil), // 9: flipt.ofrep.EvaluateBulkRequest (*BulkEvaluationResponse)(nil), // 10: flipt.ofrep.BulkEvaluationResponse nil, // 11: flipt.ofrep.EvaluateFlagRequest.ContextEntry @@ -740,16 +741,16 @@ var file_ofrep_ofrep_proto_depIdxs = []int32{ 6, // 2: flipt.ofrep.Capabilities.flag_evaluation:type_name -> flipt.ofrep.FlagEvaluation 5, // 3: flipt.ofrep.CacheInvalidation.polling:type_name -> flipt.ofrep.Polling 11, // 4: flipt.ofrep.EvaluateFlagRequest.context:type_name -> flipt.ofrep.EvaluateFlagRequest.ContextEntry - 0, // 5: flipt.ofrep.EvaluatedFlag.reason:type_name -> flipt.ofrep.EvaluateReason - 13, // 6: flipt.ofrep.EvaluatedFlag.metadata:type_name -> google.protobuf.Struct - 14, // 7: flipt.ofrep.EvaluatedFlag.value:type_name -> google.protobuf.Value + 0, // 5: flipt.ofrep.EvaluationResponse.reason:type_name -> flipt.ofrep.EvaluateReason + 13, // 6: flipt.ofrep.EvaluationResponse.metadata:type_name -> google.protobuf.Struct + 14, // 7: flipt.ofrep.EvaluationResponse.value:type_name -> google.protobuf.Value 12, // 8: flipt.ofrep.EvaluateBulkRequest.context:type_name -> flipt.ofrep.EvaluateBulkRequest.ContextEntry - 8, // 9: flipt.ofrep.BulkEvaluationResponse.flags:type_name -> flipt.ofrep.EvaluatedFlag + 8, // 9: flipt.ofrep.BulkEvaluationResponse.flags:type_name -> flipt.ofrep.EvaluationResponse 1, // 10: flipt.ofrep.OFREPService.GetProviderConfiguration:input_type -> flipt.ofrep.GetProviderConfigurationRequest 7, // 11: flipt.ofrep.OFREPService.EvaluateFlag:input_type -> flipt.ofrep.EvaluateFlagRequest 9, // 12: flipt.ofrep.OFREPService.EvaluateBulk:input_type -> flipt.ofrep.EvaluateBulkRequest 2, // 13: flipt.ofrep.OFREPService.GetProviderConfiguration:output_type -> flipt.ofrep.GetProviderConfigurationResponse - 8, // 14: flipt.ofrep.OFREPService.EvaluateFlag:output_type -> flipt.ofrep.EvaluatedFlag + 8, // 14: flipt.ofrep.OFREPService.EvaluateFlag:output_type -> flipt.ofrep.EvaluationResponse 10, // 15: flipt.ofrep.OFREPService.EvaluateBulk:output_type -> flipt.ofrep.BulkEvaluationResponse 13, // [13:16] is the sub-list for method output_type 10, // [10:13] is the sub-list for method input_type diff --git a/rpc/flipt/ofrep/ofrep.proto b/rpc/flipt/ofrep/ofrep.proto index 3be6e6a65c..7d89265c8a 100644 --- a/rpc/flipt/ofrep/ofrep.proto +++ b/rpc/flipt/ofrep/ofrep.proto @@ -40,7 +40,7 @@ message EvaluateFlagRequest { map context = 2; } -message EvaluatedFlag { +message EvaluationResponse { string key = 1; EvaluateReason reason = 2; string variant = 3; @@ -60,7 +60,7 @@ message EvaluateBulkRequest { } message BulkEvaluationResponse { - repeated EvaluatedFlag flags = 1 [(google.api.field_behavior) = REQUIRED]; + repeated EvaluationResponse flags = 1 [(google.api.field_behavior) = REQUIRED]; } service OFREPService { @@ -71,7 +71,7 @@ service OFREPService { option (gnostic.openapi.v3.operation) = {operation_id: "ofrep.configuration"}; } // OFREP single flag evaluation - rpc EvaluateFlag(EvaluateFlagRequest) returns (EvaluatedFlag) { + rpc EvaluateFlag(EvaluateFlagRequest) returns (EvaluationResponse) { option (google.api.http) = { post: "/ofrep/v1/evaluate/flags/{key}" body: "*" diff --git a/rpc/flipt/ofrep/ofrep_grpc.pb.go b/rpc/flipt/ofrep/ofrep_grpc.pb.go index cb623ab0ff..384fc6e17b 100644 --- a/rpc/flipt/ofrep/ofrep_grpc.pb.go +++ b/rpc/flipt/ofrep/ofrep_grpc.pb.go @@ -31,7 +31,7 @@ type OFREPServiceClient interface { // OFREP provider configuration GetProviderConfiguration(ctx context.Context, in *GetProviderConfigurationRequest, opts ...grpc.CallOption) (*GetProviderConfigurationResponse, error) // OFREP single flag evaluation - EvaluateFlag(ctx context.Context, in *EvaluateFlagRequest, opts ...grpc.CallOption) (*EvaluatedFlag, error) + EvaluateFlag(ctx context.Context, in *EvaluateFlagRequest, opts ...grpc.CallOption) (*EvaluationResponse, error) // OFREP bulk flag evaluation EvaluateBulk(ctx context.Context, in *EvaluateBulkRequest, opts ...grpc.CallOption) (*BulkEvaluationResponse, error) } @@ -54,9 +54,9 @@ func (c *oFREPServiceClient) GetProviderConfiguration(ctx context.Context, in *G return out, nil } -func (c *oFREPServiceClient) EvaluateFlag(ctx context.Context, in *EvaluateFlagRequest, opts ...grpc.CallOption) (*EvaluatedFlag, error) { +func (c *oFREPServiceClient) EvaluateFlag(ctx context.Context, in *EvaluateFlagRequest, opts ...grpc.CallOption) (*EvaluationResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(EvaluatedFlag) + out := new(EvaluationResponse) err := c.cc.Invoke(ctx, OFREPService_EvaluateFlag_FullMethodName, in, out, cOpts...) if err != nil { return nil, err @@ -81,7 +81,7 @@ type OFREPServiceServer interface { // OFREP provider configuration GetProviderConfiguration(context.Context, *GetProviderConfigurationRequest) (*GetProviderConfigurationResponse, error) // OFREP single flag evaluation - EvaluateFlag(context.Context, *EvaluateFlagRequest) (*EvaluatedFlag, error) + EvaluateFlag(context.Context, *EvaluateFlagRequest) (*EvaluationResponse, error) // OFREP bulk flag evaluation EvaluateBulk(context.Context, *EvaluateBulkRequest) (*BulkEvaluationResponse, error) mustEmbedUnimplementedOFREPServiceServer() @@ -97,7 +97,7 @@ type UnimplementedOFREPServiceServer struct{} func (UnimplementedOFREPServiceServer) GetProviderConfiguration(context.Context, *GetProviderConfigurationRequest) (*GetProviderConfigurationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProviderConfiguration not implemented") } -func (UnimplementedOFREPServiceServer) EvaluateFlag(context.Context, *EvaluateFlagRequest) (*EvaluatedFlag, error) { +func (UnimplementedOFREPServiceServer) EvaluateFlag(context.Context, *EvaluateFlagRequest) (*EvaluationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EvaluateFlag not implemented") } func (UnimplementedOFREPServiceServer) EvaluateBulk(context.Context, *EvaluateBulkRequest) (*BulkEvaluationResponse, error) {