Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: flagSetMetadata in OFREP/ResolveAll, core refactors #1540

Merged
merged 4 commits into from
Jan 31, 2025

Conversation

toddbaert
Copy link
Member

@toddbaert toddbaert commented Jan 29, 2025

⚠️ This PR brings a breaking change to the flagd-core library: the IStore interface now returns an additional value representing the flag set metadata. There are no breaking changes in flagd's behavior.

Changes in flagd:

  • returns flag set metadata as metadata for error flags (best effort)
  • returns flag set metadata in OFREP and RPC calls
  • moves metadata merging logic to evaluator (all flags inherent flag set metadata, but can override, as as before but now reusable in flagd core)
  • removes duplicated flag set metadata keys when the same flag set metadata key exists in multiple sources

To Test

  • requires curl, grpcurl, and jq

RPC

grpcurl -import-path  /...../schemas/protobuf/flagd/evaluation/v1  -proto evaluation.proto -plaintext  localhost:8013 flagd.evaluation.v1.Service/ResolveAll | jq

OFREP

curl --location 'http://localhost:8016/ofrep/v1/evaluate/flags'  --header 'Content-Type: application/json' --data '{"context": {"color": "yellow"}}'  | jq

Sync

grpcurl -import-path /...../schemas/protobuf/flagd/sync/v1/ -proto sync.proto -plaintext   localhost:8015 flagd.sync.v1.FlagSyncService/FetchAllFlags    | jq -r .flagConfiguration  | jq

@toddbaert toddbaert requested a review from a team as a code owner January 29, 2025 13:21
@toddbaert toddbaert marked this pull request as draft January 29, 2025 13:21
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jan 29, 2025
Copy link

netlify bot commented Jan 29, 2025

Deploy Preview for polite-licorice-3db33c canceled.

Name Link
🔨 Latest commit 3a44dfc
🔍 Latest deploy log https://app.netlify.com/sites/polite-licorice-3db33c/deploys/679b85a54677cc00087ad67c

@@ -1184,222 +1264,6 @@ func TestState_Evaluator(t *testing.T) {
expectedError: true,
expectedResync: false,
},
"flag metadata": {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of these tests is now moved above (metadata merging).

@toddbaert toddbaert force-pushed the metadata-errors branch 2 times, most recently from 738c41d to 575526c Compare January 29, 2025 14:03
@toddbaert toddbaert marked this pull request as ready for review January 29, 2025 14:05
Signed-off-by: Todd Baert <[email protected]>
Copy link
Member

@beeme1mr beeme1mr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of TODO that should either be removed or actioned. The change looks great and the functionality aligns with that JS implementation. Thanks!

Comment on lines +122 to +131
// TODO: We do not handle metadata in ADD/UPDATE operations. These are only relevant for grpc sync implementations.
switch payload.Type {
case sync.ALL:
events, reSync = je.store.Merge(je.Logger, payload.Source, payload.Selector, newFlags.Flags)
events, reSync = je.store.Merge(je.Logger, payload.Source, payload.Selector, definition.Flags, definition.Metadata)
case sync.ADD:
events = je.store.Add(je.Logger, payload.Source, payload.Selector, newFlags.Flags)
events = je.store.Add(je.Logger, payload.Source, payload.Selector, definition.Flags)
case sync.UPDATE:
events = je.store.Update(je.Logger, payload.Source, payload.Selector, newFlags.Flags)
events = je.store.Update(je.Logger, payload.Source, payload.Selector, definition.Flags)
case sync.DELETE:
events = je.store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags)
events = je.store.DeleteFlags(je.Logger, payload.Source, definition.Flags)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ADD/UPDATE are only relevant to hypothetical gRPC server implementations, and were a premature optimization to allow gRPC servers to tell flagd "just add these flags, or just change this flag" instead of sending a whole payload. I have never seen this implemented, and it's not relevant to any of the other sync types.

I think we should cease to enhance them and deprecate them in the protobuf.

@toddbaert toddbaert merged commit b49abf9 into main Jan 31, 2025
16 checks passed
@github-actions github-actions bot mentioned this pull request Jan 31, 2025
beeme1mr pushed a commit that referenced this pull request Jan 31, 2025
🤖 I have created a release *beep* *boop*
---


<details><summary>flagd: 0.12.0</summary>

##
[0.12.0](flagd/v0.11.8...flagd/v0.12.0)
(2025-01-31)


### ⚠ BREAKING CHANGES

* flagSetMetadata in OFREP/ResolveAll, core refactors
([#1540](#1540))

### 🐛 Bug Fixes

* **deps:** update module
buf.build/gen/go/open-feature/flagd/connectrpc/go to
v1.18.1-20250127221518-be6d1143b690.1
([#1535](#1535))
([d5ec921](d5ec921))
* **deps:** update module buf.build/gen/go/open-feature/flagd/grpc/go to
v1.5.1-20250127221518-be6d1143b690.2
([#1536](#1536))
([e23060f](e23060f))
* **deps:** update module
buf.build/gen/go/open-feature/flagd/protocolbuffers/go to
v1.36.4-20241220192239-696330adaff0.1
([#1529](#1529))
([8881a80](8881a80))
* **deps:** update module
buf.build/gen/go/open-feature/flagd/protocolbuffers/go to
v1.36.4-20250127221518-be6d1143b690.1
([#1537](#1537))
([f74207b](f74207b))
* **deps:** update module github.com/open-feature/flagd/core to v0.10.8
([#1526](#1526))
([fbf2ed5](fbf2ed5))
* **deps:** update module google.golang.org/grpc to v1.70.0
([#1528](#1528))
([79b2b0a](79b2b0a))


### ✨ New Features

* flagSetMetadata in OFREP/ResolveAll, core refactors
([#1540](#1540))
([b49abf9](b49abf9))
</details>

<details><summary>flagd-proxy: 0.7.0</summary>

##
[0.7.0](flagd-proxy/v0.6.11...flagd-proxy/v0.7.0)
(2025-01-31)


### ⚠ BREAKING CHANGES

* flagSetMetadata in OFREP/ResolveAll, core refactors
([#1540](#1540))

### 🐛 Bug Fixes

* **deps:** update module buf.build/gen/go/open-feature/flagd/grpc/go to
v1.5.1-20250127221518-be6d1143b690.2
([#1536](#1536))
([e23060f](e23060f))
* **deps:** update module
buf.build/gen/go/open-feature/flagd/protocolbuffers/go to
v1.36.4-20241220192239-696330adaff0.1
([#1529](#1529))
([8881a80](8881a80))
* **deps:** update module
buf.build/gen/go/open-feature/flagd/protocolbuffers/go to
v1.36.4-20250127221518-be6d1143b690.1
([#1537](#1537))
([f74207b](f74207b))
* **deps:** update module github.com/open-feature/flagd/core to v0.10.8
([#1526](#1526))
([fbf2ed5](fbf2ed5))
* **deps:** update module google.golang.org/grpc to v1.70.0
([#1528](#1528))
([79b2b0a](79b2b0a))


### ✨ New Features

* flagSetMetadata in OFREP/ResolveAll, core refactors
([#1540](#1540))
([b49abf9](b49abf9))
</details>

<details><summary>core: 0.11.0</summary>

##
[0.11.0](core/v0.10.8...core/v0.11.0)
(2025-01-31)


### ⚠ BREAKING CHANGES

* flagSetMetadata in OFREP/ResolveAll, core refactors
([#1540](#1540))

### 🐛 Bug Fixes

* **deps:** update github.com/open-feature/flagd-schemas digest to
bb76343 ([#1534](#1534))
([8303353](8303353))
* **deps:** update golang.org/x/exp digest to 3edf0e9
([#1538](#1538))
([7a06567](7a06567))
* **deps:** update golang.org/x/exp digest to e0ece0d
([#1539](#1539))
([4281c6e](4281c6e))
* **deps:** update module buf.build/gen/go/open-feature/flagd/grpc/go to
v1.5.1-20250127221518-be6d1143b690.2
([#1536](#1536))
([e23060f](e23060f))
* **deps:** update module
buf.build/gen/go/open-feature/flagd/protocolbuffers/go to
v1.36.4-20241220192239-696330adaff0.1
([#1529](#1529))
([8881a80](8881a80))
* **deps:** update module
buf.build/gen/go/open-feature/flagd/protocolbuffers/go to
v1.36.4-20250127221518-be6d1143b690.1
([#1537](#1537))
([f74207b](f74207b))
* **deps:** update module google.golang.org/grpc to v1.70.0
([#1528](#1528))
([79b2b0a](79b2b0a))


### ✨ New Features

* flagSetMetadata in OFREP/ResolveAll, core refactors
([#1540](#1540))
([b49abf9](b49abf9))
* support yaml in blob, file, and http syncs
([#1522](#1522))
([76d673a](76d673a))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: OpenFeature Bot <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Copy link
Contributor

@alexandraoberaigner alexandraoberaigner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better late then never...I just finished reviewing & added a few questions :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q] When running the evaluation proto ResolveAll rpc command the result does not include the added metadata to the Response message. Is this how it should be or would it make sense to include the metadata as well?
The response looks like this:

{
  "flags": {
    "fibAlgo": {
      "reason": "DEFAULT",
      "variant": "recursive",
      "stringValue": "recursive"
    },
    "headerColor": {
      "reason": "DEFAULT",
      "variant": "red",
      "stringValue": "#FF0000"
    },
    "isColorYellow": {
      "reason": "TARGETING_MATCH",
      "variant": "off",
      "boolValue": false
    },
    "myBoolFlag": {
      "reason": "STATIC",
      "variant": "on",
      "boolValue": true
    },
    "myFloatFlag": {
      "reason": "STATIC",
      "variant": "one",
      "doubleValue": 1.23
    },
    ...

Copy link
Member Author

@toddbaert toddbaert Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it when I try - I suspect that the proto files you are pointing to in the command you are running are out of date, and don't have the latest additions of metadata. cd into the schemas repo and pull it. We only very recently added this field to the proto. Sorry - I should have made sure to mention this in the testing instructions.

The proto files are essentially schemas that tell the unmarshaller how to parse the protobuf binary data; they need to know about every field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see, makes sense! Thanks for explaining :)

@@ -119,15 +119,16 @@ func (je *JSON) SetState(payload sync.DataSync) (map[string]interface{}, bool, e
var events map[string]interface{}
var reSync bool

// TODO: We do not handle metadata in ADD/UPDATE operations. These are only relevant for grpc sync implementations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q] did you miss this TODO or should it rather be a normal comment? reading your comment below the TODO would be to deprecate these cases (ADD/UPDATE/DELETE) for sync impls, or did I misunderstand it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are understanding correctly. I left it as a TODO because I wanted to open an issue to discuss the deprecation of these, which I still intend to do.

@@ -156,14 +157,16 @@ func NewResolver(store store.IStore, logger *logger.Logger, jsonEvalTracer trace
return Resolver{store: store, Logger: logger, tracer: jsonEvalTracer}
}

func (je *Resolver) ResolveAllValues(ctx context.Context, reqID string, context map[string]any) ([]AnyValue, error) {
func (je *Resolver) ResolveAllValues(ctx context.Context, reqID string, context map[string]any) ([]AnyValue,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q] is this the function that relates to this proto command (from evaluation.proto):
rpc ResolveAll(ResolveAllRequest) returns (ResolveAllResponse) {}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is the inner "domain layer" function satisfying that rpc. More directly, this function is the actual rpc endpoint.

}

for _, test := range tests {
_, _, _, metadata, _ := evaluator.ResolveBooleanValue(context.TODO(), reqID, test.flagKey, nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q] What does context.TODO() do?

Copy link
Member Author

@toddbaert toddbaert Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context in Go is sort of like a very explicit (most things in Go are very explicit) ThreadLocal in Java, or a continuation in Javascript; it's passed down the entire call chain (at least in cases of good design) and can be used to propagate signals and store data (for example, request data).

context.TODO() is sort of a "bare" or "root" context often used in testing or at the very start of some call stack.

see: https://pkg.go.dev/context

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should finish my Go course 😁 I thought it was an OpenFeature context attribute

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's understandable since there's a name collision here. It's not ideal. I try to refer to the OpenFeature one as "EvaluationContext" to disambiguate them.

@@ -303,3 +318,40 @@ func (f *Flags) Merge(
}
return notifications, resyncRequired
}

func (f *State) getMetadataForSource(source string) model.Metadata {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q] What does source represent?

Copy link
Member Author

@toddbaert toddbaert Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a "source" of flags. For example, when you start flagd like this:

flagd start -f file:../config/samples/example_flags.flagd.json -f http://some-domain.com/my-flags.json

you have set 2 sources (one file and one http). These provide flag definitions to flagd, and it will watch them for updates. There are many different styles of sources (syncs) in addition to file and http. See: https://flagd.dev/concepts/syncs/ for the full list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size:XL This PR changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants