Skip to content

Commit 07a45d9

Browse files
bbland1toddbaert
andauthored
feat: add sync_context to SyncFlags (#1642)
## This PR - adds comment about `GetMetadata` being deprecated in future release - implements the `sync_context` field to the `SyncFlags` of `flag-sync/handler.go` ### Related Issues Fixes #1635 --------- Signed-off-by: bbland1 <104288486+bbland1@users.noreply.github.com> Signed-off-by: Todd Baert <todd.baert@dynatrace.com> Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
1 parent 1ccc07e commit 07a45d9

File tree

12 files changed

+237
-107
lines changed

12 files changed

+237
-107
lines changed

core/go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ go 1.23.0
55
toolchain go1.24.2
66

77
require (
8-
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2
9-
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1
8+
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2
9+
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1
1010
connectrpc.com/connect v1.18.1
1111
connectrpc.com/otelconnect v0.7.2
1212
github.com/diegoholiveira/jsonlogic/v3 v3.7.4
@@ -37,6 +37,7 @@ require (
3737
golang.org/x/mod v0.23.0
3838
golang.org/x/sync v0.11.0
3939
google.golang.org/grpc v1.71.0
40+
google.golang.org/protobuf v1.36.6
4041
gopkg.in/yaml.v3 v3.0.1
4142
k8s.io/apimachinery v0.31.4
4243
k8s.io/client-go v0.31.4
@@ -142,7 +143,6 @@ require (
142143
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect
143144
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
144145
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
145-
google.golang.org/protobuf v1.36.5 // indirect
146146
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
147147
gopkg.in/inf.v0 v0.9.1 // indirect
148148
gopkg.in/yaml.v2 v2.4.0 // indirect

core/go.sum

Lines changed: 8 additions & 84 deletions
Large diffs are not rendered by default.

core/pkg/sync/grpc/grpc_sync.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ func (g *Sync) handleFlagSync(stream syncv1grpc.FlagSyncService_SyncFlagsClient,
201201

202202
dataSync <- sync.DataSync{
203203
FlagData: data.FlagConfiguration,
204+
SyncContext: data.SyncContext,
204205
Source: g.URI,
205206
Selector: g.Selector,
206207
Type: sync.ALL,

core/pkg/sync/grpc/grpc_sync_test.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"google.golang.org/grpc/credentials"
2727
"google.golang.org/grpc/credentials/insecure"
2828
"google.golang.org/grpc/test/bufconn"
29+
"google.golang.org/protobuf/proto"
30+
"google.golang.org/protobuf/types/known/structpb"
2931
)
3032

3133
func Test_InitWithMockCredentialBuilder(t *testing.T) {
@@ -215,11 +217,16 @@ func TestSync_BasicFlagSyncStates(t *testing.T) {
215217
{
216218
name: "State All maps to Sync All",
217219
setup: func(t *testing.T, client *grpcmock.MockFlagSyncServiceClient, clientResponse *grpcmock.MockFlagSyncServiceClientResponse) {
220+
metadata, err := structpb.NewStruct(map[string]any{"sources": "A,B,C"})
221+
if err != nil {
222+
t.Fatalf("Failed to create sync context: %v", err)
223+
}
218224
client.EXPECT().SyncFlags(gomock.Any(), gomock.Any(), gomock.Any()).Return(clientResponse, nil)
219225
gomock.InOrder(
220226
clientResponse.EXPECT().Recv().Return(
221227
&v1.SyncFlagsResponse{
222228
FlagConfiguration: "{}",
229+
SyncContext: metadata,
223230
},
224231
nil,
225232
),
@@ -287,6 +294,11 @@ func TestSync_BasicFlagSyncStates(t *testing.T) {
287294
func Test_StreamListener(t *testing.T) {
288295
const target = "localBufCon"
289296

297+
metadata, err := structpb.NewStruct(map[string]any{"sources": "A,B,C"})
298+
if err != nil {
299+
t.Fatalf("Failed to create sync context: %v", err)
300+
}
301+
290302
tests := []struct {
291303
name string
292304
input []serverPayload
@@ -301,8 +313,9 @@ func Test_StreamListener(t *testing.T) {
301313
},
302314
output: []sync.DataSync{
303315
{
304-
FlagData: "{\"flags\": {}}",
305-
Type: sync.ALL,
316+
FlagData: "{\"flags\": {}}",
317+
SyncContext: metadata,
318+
Type: sync.ALL,
306319
},
307320
},
308321
},
@@ -319,11 +332,13 @@ func Test_StreamListener(t *testing.T) {
319332
output: []sync.DataSync{
320333
{
321334
FlagData: "{}",
335+
SyncContext: metadata,
322336
Type: sync.ALL,
323337
},
324338
{
325-
FlagData: "{\"flags\": {}}",
326-
Type: sync.ALL,
339+
FlagData: "{\"flags\": {}}",
340+
SyncContext: metadata,
341+
Type: sync.ALL,
327342
},
328343
},
329344
},
@@ -383,6 +398,10 @@ func Test_StreamListener(t *testing.T) {
383398
if expected.FlagData != out.FlagData {
384399
t.Errorf("Returned sync data = %v, wanted %v", out.FlagData, expected.FlagData)
385400
}
401+
402+
if !proto.Equal(expected.SyncContext, out.SyncContext) {
403+
t.Errorf("Returned sync context = %v, wanted = %v", out.SyncContext, expected.SyncContext)
404+
}
386405
}
387406

388407
// channel must be empty
@@ -575,8 +594,10 @@ type bufferedServer struct {
575594

576595
func (b *bufferedServer) SyncFlags(_ *v1.SyncFlagsRequest, stream syncv1grpc.FlagSyncService_SyncFlagsServer) error {
577596
for _, response := range b.mockResponses {
597+
metadata, _ := structpb.NewStruct(map[string]any{"sources": "A,B,C"})
578598
err := stream.Send(&v1.SyncFlagsResponse{
579599
FlagConfiguration: response.flags,
600+
SyncContext: metadata,
580601
})
581602
if err != nil {
582603
fmt.Printf("Error with stream: %s", err.Error())

core/pkg/sync/isync.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package sync
22

33
import (
44
"context"
5+
6+
"google.golang.org/protobuf/types/known/structpb"
57
)
68

79
type Type int
@@ -56,6 +58,7 @@ type ISync interface {
5658
// DataSync is the data contract between Runtime and sync implementations
5759
type DataSync struct {
5860
FlagData string
61+
SyncContext *structpb.Struct
5962
Source string
6063
Selector string
6164
Type

flagd-proxy/pkg/service/subscriptions/manager_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type syncMock struct {
2121

2222
initError error
2323
ctxCloseError error
24+
25+
mu sync.Mutex
2426
}
2527

2628
func newMockSync() *syncMock {
@@ -38,6 +40,8 @@ func (s *syncMock) Sync(ctx context.Context, dataSync chan<- isync.DataSync) err
3840
for {
3941
select {
4042
case <-ctx.Done():
43+
s.mu.Lock()
44+
defer s.mu.Unlock()
4145
return s.ctxCloseError
4246
case d := <-s.dataSyncChanIn:
4347
dataSync <- d
@@ -48,6 +52,8 @@ func (s *syncMock) Sync(ctx context.Context, dataSync chan<- isync.DataSync) err
4852
}
4953

5054
func (s *syncMock) ReSync(_ context.Context, dataSync chan<- isync.DataSync) error {
55+
s.mu.Lock()
56+
defer s.mu.Unlock()
5157
if s.resyncData != nil {
5258
dataSync <- *s.resyncData
5359
}

flagd/go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ toolchain go1.24.2
66

77
require (
88
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250127221518-be6d1143b690.1
9-
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2
10-
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1
9+
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2
10+
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1
1111
connectrpc.com/connect v1.18.1
1212
github.com/dimiro1/banner v1.1.0
1313
github.com/gorilla/mux v1.8.1
@@ -20,6 +20,7 @@ require (
2020
github.com/spf13/pflag v1.0.6
2121
github.com/spf13/viper v1.19.0
2222
github.com/stretchr/testify v1.10.0
23+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
2324
go.opentelemetry.io/otel v1.35.0
2425
go.opentelemetry.io/otel/sdk v1.35.0
2526
go.opentelemetry.io/otel/sdk/metric v1.35.0
@@ -29,7 +30,7 @@ require (
2930
golang.org/x/net v0.35.0
3031
golang.org/x/sync v0.11.0
3132
google.golang.org/grpc v1.71.0
32-
google.golang.org/protobuf v1.36.5
33+
google.golang.org/protobuf v1.36.6
3334
)
3435

3536
require (
@@ -140,7 +141,6 @@ require (
140141
go.opencensus.io v0.24.0 // indirect
141142
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
142143
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
143-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
144144
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect
145145
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
146146
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect

flagd/go.sum

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250127221518-be6d1143b690.1 h1:BIA4nkcwwyl0zPZ4vutd5Mn+VeLOV756RmvbQbfnb9g=
22
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250127221518-be6d1143b690.1/go.mod h1:BSerK2QrH0wQdgiFP6fKevMB4QXotvyXUfmE6mExwHY=
3-
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2 h1:D3HI5RQbqgffyf+Z77+hReDx5kigFVAKGvttULD9/ms=
4-
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2/go.mod h1:b9rfG6rbGXZAlLwQwedvZ0kI0nUcR+aLaYF70pj920E=
5-
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1 h1:eZKupK8gUTuc6zifAFQon8Gnt44fR4cd0GnTWjELvEw=
6-
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1/go.mod h1:wJvVIADHM0IaBc5sYf8wgMMgSHi0nAtc6rgr5rfizhA=
3+
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2 h1:TZ+7u106u7C7lgNctxG03ABliF46eLhcIZG5Mdo67/E=
4+
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2/go.mod h1:4u0WLwfkLob3dC/F8qNctqhtiEv2Mlyi8YgCDDzgYDs=
5+
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1 h1:LdC4xAuUaNdduzQr5VvhjsgrCfpW9IYxYsjyCF0ANs0=
6+
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1/go.mod h1:cCQ49+ttXE2MZ/ciRNb0tCG+F3kj2ZVbP+0/psbhrLY=
77
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
88
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
99
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
@@ -139,8 +139,6 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
139139
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
140140
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
141141
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
142-
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
143-
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
144142
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
145143
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
146144
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -407,8 +405,6 @@ golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
407405
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
408406
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
409407
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
410-
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
411-
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
412408
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
413409
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
414410
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -510,8 +506,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
510506
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
511507
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
512508
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
513-
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
514-
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
509+
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
510+
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
515511
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
516512
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
517513
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

flagd/pkg/service/flag-sync/handler.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"maps"
78
"google.golang.org/grpc/codes"
89
"google.golang.org/grpc/status"
910
"time"
@@ -43,7 +44,24 @@ func (s syncHandler) SyncFlags(req *syncv1.SyncFlagsRequest, server syncv1grpc.F
4344
for {
4445
select {
4546
case payload := <-muxPayload:
46-
err := server.Send(&syncv1.SyncFlagsResponse{FlagConfiguration: payload.flags})
47+
48+
metadataSrc := make(map[string]any)
49+
maps.Copy(metadataSrc, s.contextValues)
50+
51+
if sources := s.mux.SourcesAsMetadata(); sources != "" {
52+
metadataSrc["sources"] = sources
53+
}
54+
55+
metadata, err := structpb.NewStruct(metadataSrc)
56+
if err != nil {
57+
s.log.Error(fmt.Sprintf("error from struct creation: %v", err))
58+
return fmt.Errorf("error constructing metadata response")
59+
}
60+
61+
err = server.Send(&syncv1.SyncFlagsResponse{
62+
FlagConfiguration: payload.flags,
63+
SyncContext: metadata,
64+
})
4765
if err != nil {
4866
s.log.Debug(fmt.Sprintf("error sending stream response: %v", err))
4967
return fmt.Errorf("error sending stream response: %w", err)
@@ -74,6 +92,8 @@ func (s syncHandler) FetchAllFlags(_ context.Context, req *syncv1.FetchAllFlagsR
7492
}, nil
7593
}
7694

95+
// Deprecated - GetMetadata is deprecated and will be removed in a future release.
96+
// Use the sync_context field in syncv1.SyncFlagsResponse, providing same info.
7797
func (s syncHandler) GetMetadata(_ context.Context, _ *syncv1.GetMetadataRequest) (
7898
*syncv1.GetMetadataResponse, error,
7999
) {

0 commit comments

Comments
 (0)