Compare commits

...

54 Commits

Author SHA1 Message Date
Todd Baert 52f344fa01
chore: fix CODEOWNERS
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-08-18 11:34:22 -04:00
Todd Baert f9ce46f103
feat: multi-project support via selectors and flagSetId namespacing (#1702)
Sorry for how big this PR seems (a lot of the change lines are not
significant (DB schema or tests), so I've tried to highlight the
important parts. This is a substantial PR that builds on our recent
changes to use go-memdb for storage. It primarily supports 2 new crucial
features:

- support for duplicate flag keys from multiple sources (namespaced by
`flagSetId`) as described in [this
ADR](https://github.com/open-feature/flagd/blob/main/docs/architecture-decisions/duplicate-flag-keys.md)
- support for a robust query syntax in the "selector" fields and
headers, as proposed by @tangenti in [this
ADR](https://github.com/open-feature/flagd/blob/main/docs/architecture-decisions/decouple-flag-source-and-set.md)

Both of these were accomplished using the new go-memdb module. **The
built-in "watcher" functionality also allows us to _completely delete_
our "mux" layer, which was responsible for fanning out changes from
sync-sources to listeners**; this is something the go-memdb module
providers for free (the ability to watch a query for changes). Now, we
completely rely on this feature for all change notifications.
Additionally, unlike before, change notifications are now scoped to
particular _selectors_ (ie: if a client is only interested in changes
for flags from `flagSetId: x` or `source: y`, they will only get change
notifications pertaining to that selection. Currently, the only
supported query fields for the `selector` are `"source"` and
`"flagSetId"`, but this functionality can easily be extended. By
default, if no `selector` is specified, the previous key-overwrite by
source priority apples (this logic has also been simplified using the
new database). Most of the new functionality is tested
[here](https://github.com/open-feature/flagd/pull/1702/files#diff-0cdd4fe716f4b8a94466279fd1b11187fcf4d74e6434727c33d57ed78c89fe27R163-R478).

Selector in action for `Sync` API:


![demo](https://github.com/user-attachments/assets/9a4fd33e-80ef-4b5b-80d1-4fa3fc92b8e7)

Selector in action for `Evaluation` API:


![demo2](https://github.com/user-attachments/assets/abf49f9c-2247-4a03-9b8f-15b6f540aad1)

To test, run the new make target `make run-flagd-selector-demo`, then
use the OFREP or gRPC endpoints to experiment. This new functionality is
available on all APIs and endpoints. The command I ran in the gifs above
are:

streaming:
```shell
grpcurl -d '{"selector":"flagSetId=example"}' -import-path schemas/protobuf/flagd/sync/v1/ -proto sync.proto -plaintext localhost:8015 flagd.sync.v1.FlagSyncService/SyncFlags | jq
grpcurl -d '{"selector":"flagSetId=example,source=../config/samples/example_flags.flagd.json"}' -import-path schemas/protobuf/flagd/sync/v1/ -proto sync.proto -plaintext localhost:8015 flagd.sync.v1.FlagSyncService/SyncFlags | jq
grpcurl -d '{"selector":"flagSetId=other"}' -import-path schemas/protobuf/flagd/sync/v1/ -proto sync.proto -plaintext localhost:8015 flagd.sync.v1.FlagSyncService/SyncFlags | jq
```

single-evaluations:
```shell
curl -X POST  -d '{"context":{}}' 'http://localhost:8016/ofrep/v1/evaluate/flags' | jq
curl -X POST -H 'flagd-selector:flagSetId=other'  -d '{"context":{}}' 'http://localhost:8016/ofrep/v1/evaluate/flags' | jq
```

⚠️ There's no breaking changes here. Besides the new features,
there is one behavioral change - the top level "metadata" object
returned for bulk evaluations (and failed evaluations) was previously
very nonsensical in it's behavior (we basically just aggregated the
metadata from all sources, discarding duplicates, and sent it back. This
field was used by providers for telemetry purposes. Now, since flag
evaluations and subscripts are "query based" and can aggregate data from
multiple sources, we've opted to simply reflect the selector queries
contents here.

So if you used a selector like `"flagSetId=1234,source=../my/source"`,
the top-level metadata object in the response would be:

```
"metadata": {
  "flagSetId": 1234,
  "source": "../my/source"
}
```

This is useful for the provider's ability to report errors, etc in
telemetry.

Fixes: https://github.com/open-feature/flagd/issues/1675
Fixes: https://github.com/open-feature/flagd/issues/1695
Fixes: https://github.com/open-feature/flagd/issues/1611
Fixes: https://github.com/open-feature/flagd/issues/1700
Fixes: https://github.com/open-feature/flagd/issues/1610

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: chrfwow <christian.lutnik@dynatrace.com>
Co-authored-by: Giovanni Liva <giovanni.liva@dynatrace.com>
2025-08-13 16:03:07 -04:00
NeaguGeorgiana23 3228ad8951
chore!: removes the `fractionalEvaluation` operator since it has been replaced with `fractional`. (#1704)
## This PR
<!-- add the description of the PR here -->

- adds this new feature

### Related Issues
- removes the `fractionalEvaluation` operator since it has been replaced
with `fractional`.

Fixes #1677

---------

Signed-off-by: NeaguGeorgiana23 <115723925+NeaguGeorgiana23@users.noreply.github.com>
2025-08-06 17:50:43 -04:00
Todd Baert 5c5c1cfe84
chore(refactor): use memdb for flag storage (#1697)
This PR contains no behavioral changes, and no breaking changes.

It lays the groundwork for some of the upcoming improvements we want in
flagd, as specified in recent ADRs. It does this by re-implementing our
storage layer to use [go-memdb](https://github.com/hashicorp/go-memdb),
an open source in-memory database developed by Hashicorp and used in
[Consul](https://developer.hashicorp.com/consul) (as well as MANY other
projects).

### Why?

This will allow us to easily support:

- duplicate flag keys (namespaced by flagSetId), as mentioned in [this
ADR](https://github.com/open-feature/flagd/blob/main/docs/architecture-decisions/duplicate-flag-keys.md)
- robust flag selectors, as mentioned in [this
ADR](https://github.com/open-feature/flagd/pull/1644), by supporting
"watchers" which allow us to "listen" to change in flags returned by a
given query 🕺
- robust (and possibly, in the future, even customizable) indexing on
arbitrary attributes (to easily support fetching "all boolean flags", or
"flags with metadata = xyz", etc)
- cross-"table" transactions

I have already PoC'd that these are all practical.

### Changes in implementation

- the `store` package's `State` is no longer just a struct; it's a
object with methods wrapping the internal db
- the `store` package's `State` was renamed to `Store` and the file was
renamed from `flags.go` to `store.go`, since it's ceased to be a simple
stateful object, and for consistency
- a non-serialized (used only internally) `Key` field was added to the
flag type (for indexing)
- a new constructor for the `Store` was added which takes a logger and
returns an error, the old was deprecated to avoid breaking changes in
consumers (the Go flagd provider, mostly)

Note that the go-memdb dependency is MPL2, which is not allowed by the
CNCF, however, go-memdb is already used in CNCF projects and has a
[special
exception](347a55dc0a/license-exceptions/cncf-exceptions-2023-08-31.json (L7-L11)).

### Perfromance

There was no significant change in performance, see benchmark diff vs
main below:

<details>
  <summary>Benchmark diff vs main</summary>

```diff
-BenchmarkFractionalEvaluation/test_c@faas.com-16         	  559051	     13832 ns/op	    7229 B/op	     135 allocs/op
-BenchmarkFractionalEvaluation/test_d@faas.com-16         	  611665	     13106 ns/op	    7229 B/op	     135 allocs/op
-BenchmarkFractionalEvaluation/test_a@faas.com-16         	  383074	     13433 ns/op	    7229 B/op	     135 allocs/op
-BenchmarkFractionalEvaluation/test_b@faas.com-16         	  529185	     12190 ns/op	    7229 B/op	     135 allocs/op
-BenchmarkResolveBooleanValue/test_staticBoolFlag-16      	 3929409	      1712 ns/op	    1008 B/op	      11 allocs/op
-BenchmarkResolveBooleanValue/test_targetingBoolFlag-16   	  812671	     10276 ns/op	    6065 B/op	      87 allocs/op
-BenchmarkResolveBooleanValue/test_staticObjectFlag-16    	 4398327	      1700 ns/op	    1008 B/op	      11 allocs/op
-BenchmarkResolveBooleanValue/test_missingFlag-16         	 4541409	      1494 ns/op	     784 B/op	      12 allocs/op
-BenchmarkResolveBooleanValue/test_disabledFlag-16        	 2998599	      1815 ns/op	    1072 B/op	      13 allocs/op
-BenchmarkResolveStringValue/test_staticStringFlag-16     	 4378830	      1698 ns/op	    1040 B/op	      13 allocs/op
-BenchmarkResolveStringValue/test_targetingStringFlag-16  	  849668	      9165 ns/op	    6097 B/op	      89 allocs/op
-BenchmarkResolveStringValue/test_staticObjectFlag-16     	 4560192	      1363 ns/op	    1008 B/op	      11 allocs/op
-BenchmarkResolveStringValue/test_missingFlag-16          	 5283511	      1196 ns/op	     784 B/op	      12 allocs/op
-BenchmarkResolveStringValue/test_disabledFlag-16         	 4393116	      1446 ns/op	    1072 B/op	      13 allocs/op
-BenchmarkResolveFloatValue/test:_staticFloatFlag-16      	 4264772	      1501 ns/op	    1024 B/op	      13 allocs/op
-BenchmarkResolveFloatValue/test:_targetingFloatFlag-16   	  776436	      8191 ns/op	    6081 B/op	      89 allocs/op
-BenchmarkResolveFloatValue/test:_staticObjectFlag-16     	 4685841	      1285 ns/op	    1008 B/op	      11 allocs/op
-BenchmarkResolveFloatValue/test:_missingFlag-16          	 5001636	      1376 ns/op	     784 B/op	      12 allocs/op
-BenchmarkResolveFloatValue/test:_disabledFlag-16         	 3707120	      1897 ns/op	    1072 B/op	      13 allocs/op
-BenchmarkResolveIntValue/test_staticIntFlag-16           	 3770362	      1677 ns/op	    1008 B/op	      11 allocs/op
-BenchmarkResolveIntValue/test_targetingNumberFlag-16     	  739861	     11142 ns/op	    6065 B/op	      87 allocs/op
-BenchmarkResolveIntValue/test_staticObjectFlag-16        	 4221418	      1913 ns/op	    1008 B/op	      11 allocs/op
-BenchmarkResolveIntValue/test_missingFlag-16             	 4289516	      1488 ns/op	     768 B/op	      12 allocs/op
-BenchmarkResolveIntValue/test_disabledFlag-16            	 4027533	      1829 ns/op	    1072 B/op	      13 allocs/op
-BenchmarkResolveObjectValue/test_staticObjectFlag-16     	 1588855	      3880 ns/op	    2243 B/op	      37 allocs/op
-BenchmarkResolveObjectValue/test_targetingObjectFlag-16  	  562364	     11580 ns/op	    7283 B/op	     109 allocs/op
-BenchmarkResolveObjectValue/test_staticBoolFlag-16       	 5026976	      1791 ns/op	    1008 B/op	      11 allocs/op
-BenchmarkResolveObjectValue/test_missingFlag-16          	 4254043	      1553 ns/op	     784 B/op	      12 allocs/op
-BenchmarkResolveObjectValue/test_disabledFlag-16         	 3051976	      2250 ns/op	    1072 B/op	      13 allocs/op
+BenchmarkFractionalEvaluation/test_a@faas.com-16         	  478593	     14527 ns/op	    7467 B/op	     143 allocs/op
+BenchmarkFractionalEvaluation/test_b@faas.com-16         	  429560	     14728 ns/op	    7467 B/op	     143 allocs/op
+BenchmarkFractionalEvaluation/test_c@faas.com-16         	  574078	     14230 ns/op	    7467 B/op	     143 allocs/op
+BenchmarkFractionalEvaluation/test_d@faas.com-16         	  411690	     15296 ns/op	    7467 B/op	     143 allocs/op
+BenchmarkResolveBooleanValue/test_staticBoolFlag-16      	 4133443	      1973 ns/op	     960 B/op	      18 allocs/op
+BenchmarkResolveBooleanValue/test_targetingBoolFlag-16   	  822934	     10981 ns/op	    6033 B/op	      94 allocs/op
+BenchmarkResolveBooleanValue/test_staticObjectFlag-16    	 3955728	      1964 ns/op	     976 B/op	      18 allocs/op
+BenchmarkResolveBooleanValue/test_missingFlag-16         	 3068791	      2294 ns/op	    1064 B/op	      21 allocs/op
+BenchmarkResolveBooleanValue/test_disabledFlag-16        	 3500334	      2225 ns/op	    1024 B/op	      20 allocs/op
+BenchmarkResolveStringValue/test_staticStringFlag-16     	 3935048	      1781 ns/op	    1008 B/op	      20 allocs/op
+BenchmarkResolveStringValue/test_targetingStringFlag-16  	  770565	     10765 ns/op	    6065 B/op	      96 allocs/op
+BenchmarkResolveStringValue/test_staticObjectFlag-16     	 3896060	      2084 ns/op	     976 B/op	      18 allocs/op
+BenchmarkResolveStringValue/test_missingFlag-16          	 3103950	      2141 ns/op	    1064 B/op	      21 allocs/op
+BenchmarkResolveStringValue/test_disabledFlag-16         	 3717013	      2092 ns/op	    1024 B/op	      20 allocs/op
+BenchmarkResolveFloatValue/test:_staticFloatFlag-16      	 3971438	      2003 ns/op	     976 B/op	      20 allocs/op
+BenchmarkResolveFloatValue/test:_targetingFloatFlag-16   	  782996	     10153 ns/op	    6049 B/op	      96 allocs/op
+BenchmarkResolveFloatValue/test:_staticObjectFlag-16     	 3469644	      2115 ns/op	     976 B/op	      18 allocs/op
+BenchmarkResolveFloatValue/test:_missingFlag-16          	 3376167	      2157 ns/op	    1064 B/op	      21 allocs/op
+BenchmarkResolveFloatValue/test:_disabledFlag-16         	 3610095	      2032 ns/op	    1024 B/op	      20 allocs/op
+BenchmarkResolveIntValue/test_staticIntFlag-16           	 3883299	      1941 ns/op	     960 B/op	      18 allocs/op
+BenchmarkResolveIntValue/test_targetingNumberFlag-16     	  823038	     10725 ns/op	    6033 B/op	      94 allocs/op
+BenchmarkResolveIntValue/test_staticObjectFlag-16        	 3697454	      2028 ns/op	     976 B/op	      18 allocs/op
+BenchmarkResolveIntValue/test_missingFlag-16             	 3326895	      1986 ns/op	    1048 B/op	      21 allocs/op
+BenchmarkResolveIntValue/test_disabledFlag-16            	 3327046	      2142 ns/op	    1024 B/op	      20 allocs/op
+BenchmarkResolveObjectValue/test_staticObjectFlag-16     	 1534627	      4885 ns/op	    2211 B/op	      44 allocs/op
+BenchmarkResolveObjectValue/test_targetingObjectFlag-16  	  509614	     14640 ns/op	    7251 B/op	     116 allocs/op
+BenchmarkResolveObjectValue/test_staticBoolFlag-16       	 3871867	      1978 ns/op	     960 B/op	      18 allocs/op
+BenchmarkResolveObjectValue/test_missingFlag-16          	 3484065	      2080 ns/op	    1064 B/op	      21 allocs/op
+BenchmarkResolveObjectValue/test_disabledFlag-16         	 4013230	      2158 ns/op	    1024 B/op	      20 allocs/op
 PASS
-ok  	github.com/open-feature/flagd/core/pkg/evaluator	233.286s
+ok  	github.com/open-feature/flagd/core/pkg/evaluator	261.212s
 ?   	github.com/open-feature/flagd/core/pkg/evaluator/mock	[no test files]
 PASS
-ok  	github.com/open-feature/flagd/core/pkg/logger	0.003s
+ok  	github.com/open-feature/flagd/core/pkg/logger	0.002s
 ?   	github.com/open-feature/flagd/core/pkg/model	[no test files]
 ?   	github.com/open-feature/flagd/core/pkg/service	[no test files]
 PASS
-ok  	github.com/open-feature/flagd/core/pkg/service/ofrep	0.003s
+ok  	github.com/open-feature/flagd/core/pkg/service/ofrep	0.002s
 PASS
 ok  	github.com/open-feature/flagd/core/pkg/store	0.002s
 ?   	github.com/open-feature/flagd/core/pkg/sync	[no test files]
@@ -51,9 +51,9 @@ PASS
 ok  	github.com/open-feature/flagd/core/pkg/sync/builder	0.020s
 ?   	github.com/open-feature/flagd/core/pkg/sync/builder/mock	[no test files]
 PASS
-ok  	github.com/open-feature/flagd/core/pkg/sync/file	1.007s
+ok  	github.com/open-feature/flagd/core/pkg/sync/file	1.006s
 PASS
-ok  	github.com/open-feature/flagd/core/pkg/sync/grpc	8.014s
+ok  	github.com/open-feature/flagd/core/pkg/sync/grpc	8.013s
 PASS
 ok  	github.com/open-feature/flagd/core/pkg/sync/grpc/credentials	0.004s
 ?   	github.com/open-feature/flagd/core/pkg/sync/grpc/credentials/mock	[no test files]
@@ -61,10 +61,10 @@ ok  	github.com/open-feature/flagd/core/pkg/sync/grpc/credentials	0.004s
 PASS
 ok  	github.com/open-feature/flagd/core/pkg/sync/grpc/nameresolvers	0.002s
 PASS
-ok  	github.com/open-feature/flagd/core/pkg/sync/http	4.008s
+ok  	github.com/open-feature/flagd/core/pkg/sync/http	4.007s
 ?   	github.com/open-feature/flagd/core/pkg/sync/http/mock	[no test files]
 PASS
-ok  	github.com/open-feature/flagd/core/pkg/sync/kubernetes	0.015s
+ok  	github.com/open-feature/flagd/core/pkg/sync/kubernetes	0.016s
 ?   	github.com/open-feature/flagd/core/pkg/sync/testing	[no test files]
 PASS
 ok  	github.com/open-feature/flagd/core/pkg/telemetry	0.016s
```

</details>

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-30 09:08:47 -04:00
Todd Baert 2f49ea7ed7
Update decouple-flag-source-and-set.md
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-29 12:18:07 -04:00
Todd Baert 2d40e28b92
Update duplicate-flag-keys.md
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-29 12:16:03 -04:00
Hugo Huang 849ce39827
docs(ADR): decouple flag sources and flag sets (#1644)
The ADR proposes a different approach to solve the problems stated in
https://github.com/open-feature/flagd/pull/1634.

It's quite concise and simple at the point, assuming the reviewers
already have the context from the original discussions.

Will add more details after the initial feedback and inputs.

---------

Signed-off-by: Hugo Huang <lorqor@gmail.com>
2025-07-29 12:14:12 -04:00
github-actions[bot] 1ee636aa03
chore: release main (#1696)
🤖 I have created a release *beep* *boop*
---


<details><summary>flagd: 0.12.9</summary>

##
[0.12.9](https://github.com/open-feature/flagd/compare/flagd/v0.12.8...flagd/v0.12.9)
(2025-07-28)


###  New Features

* Add toggle for disabling getMetadata request
([#1693](https://github.com/open-feature/flagd/issues/1693))
([e8fd680](e8fd680860))
</details>

<details><summary>core: 0.12.1</summary>

##
[0.12.1](https://github.com/open-feature/flagd/compare/core/v0.12.0...core/v0.12.1)
(2025-07-28)


### 🧹 Chore

* add back file-delete test
([#1694](https://github.com/open-feature/flagd/issues/1694))
([750aa17](750aa176b5))
* fix benchmark
([#1698](https://github.com/open-feature/flagd/issues/1698))
([5e2d7d7](5e2d7d7176))
</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 <109696520+openfeaturebot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-28 16:00:14 -04:00
Todd Baert 5e2d7d7176
chore: fix benchmark (#1698)
This was out of date. I've updated it to use the correct format, and
update the asserts accordingly.

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-28 14:30:13 -04:00
lea konvalinka e8fd680860
feat: Add toggle for disabling getMetadata request (#1693)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

## This PR
<!-- add the description of the PR here -->

- adds a toggle `disable-sync-metadata` for the Sync Service to disable
or enable the deprecated `getMetadata` request

### Related Issues
[#1688 [FEATURE] Temporary Context Enrichment
toggle](https://github.com/open-feature/flagd/issues/1688)

---------

Signed-off-by: Konvalinka <lea.konvalinka@dynatrace.com>
2025-07-28 15:33:31 +02:00
Todd Baert 750aa176b5
chore: add back file-delete test (#1694)
Adds back a test that was inappropriately removed (as explained
[here](https://github.com/beeme1mr/flagd/pull/16/files#r2223712589)).

Also fixes some typos.

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-24 07:21:36 -04:00
github-actions[bot] c0a2940aef
chore: release main (#1686)
🤖 I have created a release *beep* *boop*
---


<details><summary>flagd: 0.12.8</summary>

##
[0.12.8](https://github.com/open-feature/flagd/compare/flagd/v0.12.7...flagd/v0.12.8)
(2025-07-21)


### 🐛 Bug Fixes

* update to latest otel semconv
([#1668](https://github.com/open-feature/flagd/issues/1668))
([81855d7](81855d76f9))


### 🧹 Chore

* **deps:** update module github.com/open-feature/flagd/core to v0.11.8
([#1685](https://github.com/open-feature/flagd/issues/1685))
([c07ffba](c07ffba554))
</details>

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

##
[0.8.0](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.6...flagd-proxy/v0.8.0)
(2025-07-21)


### ⚠ BREAKING CHANGES

* remove sync.Type
([#1691](https://github.com/open-feature/flagd/issues/1691))

###  New Features

* remove sync.Type
([#1691](https://github.com/open-feature/flagd/issues/1691))
([ac647e0](ac647e0656))


### 🧹 Chore

* **deps:** update module github.com/open-feature/flagd/core to v0.11.8
([#1685](https://github.com/open-feature/flagd/issues/1685))
([c07ffba](c07ffba554))
</details>

<details><summary>core: 0.12.0</summary>

##
[0.12.0](https://github.com/open-feature/flagd/compare/core/v0.11.8...core/v0.12.0)
(2025-07-21)


### ⚠ BREAKING CHANGES

* remove sync.Type
([#1691](https://github.com/open-feature/flagd/issues/1691))

### 🐛 Bug Fixes

* update to latest otel semconv
([#1668](https://github.com/open-feature/flagd/issues/1668))
([81855d7](81855d76f9))


###  New Features

* Add support for HTTP eTag header and 304 no change response
([#1645](https://github.com/open-feature/flagd/issues/1645))
([ea3be4f](ea3be4f901))
* remove sync.Type
([#1691](https://github.com/open-feature/flagd/issues/1691))
([ac647e0](ac647e0656))
</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 <109696520+openfeaturebot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-23 16:39:41 -04:00
Zhiwei Liang ea3be4f901
feat: Add support for HTTP eTag header and 304 no change response (#1645)
## This PR
- adds the support for the `eTag` request header and `304 Not Modified`
response.

### Related Issues
Fixes #1558

### Notes
This proposal includes some significant behavior changes; therefore, any
feedback, opinions, or objections are welcome and appreciated.

### How to test
```bash
make build
cd bin && ./flagd start --port 8013 --uri https://raw.githubusercontent.com/open-feature/flagd/main/samples/example_flags.flagd.json --debug
```

More specific test cases to be added when we all agree on proceeding
with the implementation of the change in this PR.

---------

Signed-off-by: Zhiwei Liang <zhiwei.liang27@pm.me>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-21 08:30:22 -04:00
Todd Baert ac647e0656
feat!: remove sync.Type (#1691)
The PR removes the obsolete `sync.Type`. This was intended to be used to
support partial updates from sync sources, but it's never been used and
it's lagging behind on features and would not work fully if implemented
by a source (`metadata` is not properly implemented). Moreover, we're
not convinced the value is worth the complexity it adds.

Specifically:

- removes `sync.Type` and all references
- removes tests for `sync.Type` and updates assertions for unrelated
tests which previously asserted on `sync.Type` (now we use the
payload/source in assertions)
- (unrelated) updates `CONTRIBUTING.md` to include a bunch of helpful
manual testing steps

Note that this is a breaking change, but only for the `flagd/core`
library, NOT for flagd or any sync source itself in terms of their
behavior. Since there was no change in `flagd/`, this will not show up
in the CHANGELOG.

Resolves: https://github.com/open-feature/flagd/issues/1678

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-21 08:01:56 -04:00
Michael Beemer 81855d76f9
fix: update to latest otel semconv (#1668)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-07-17 12:36:02 -04:00
renovate[bot] c07ffba554
chore(deps): update module github.com/open-feature/flagd/core to v0.11.8 (#1685)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[github.com/open-feature/flagd/core](https://redirect.github.com/open-feature/flagd)
| `v0.11.6` -> `v0.11.8` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fopen-feature%2fflagd%2fcore/v0.11.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fopen-feature%2fflagd%2fcore/v0.11.6/v0.11.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/flagd).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4yMy4yIiwidXBkYXRlZEluVmVyIjoiNDEuMjMuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 06:38:56 +00:00
github-actions[bot] 0f732e2217
chore: release main (#1684)
🤖 I have created a release *beep* *boop*
---


<details><summary>flagd: 0.12.7</summary>

##
[0.12.7](https://github.com/open-feature/flagd/compare/flagd/v0.12.6...flagd/v0.12.7)
(2025-07-15)


### 🧹 Chore

* **deps:** update module github.com/open-feature/flagd/core to v0.11.6
([#1683](https://github.com/open-feature/flagd/issues/1683))
([b6da282](b6da282f8a))
</details>

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

##
[0.7.6](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.5...flagd-proxy/v0.7.6)
(2025-07-15)


### 🧹 Chore

* **deps:** update module github.com/open-feature/flagd/core to v0.11.6
([#1683](https://github.com/open-feature/flagd/issues/1683))
([b6da282](b6da282f8a))
</details>

<details><summary>core: 0.11.8</summary>

##
[0.11.8](https://github.com/open-feature/flagd/compare/core/v0.11.7...core/v0.11.8)
(2025-07-15)


### 🧹 Chore

* **deps:** update github.com/open-feature/flagd-schemas digest to
08b4c52 ([#1682](https://github.com/open-feature/flagd/issues/1682))
([68d04e2](68d04e21e6))
</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 <109696520+openfeaturebot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-15 14:55:40 -04:00
renovate[bot] b6da282f8a
chore(deps): update module github.com/open-feature/flagd/core to v0.11.6 (#1683)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[github.com/open-feature/flagd/core](https://redirect.github.com/open-feature/flagd)
| `v0.11.5` -> `v0.11.6` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fopen-feature%2fflagd%2fcore/v0.11.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fopen-feature%2fflagd%2fcore/v0.11.5/v0.11.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/flagd).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4yMy4yIiwidXBkYXRlZEluVmVyIjoiNDEuMjMuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-15 14:50:43 -04:00
renovate[bot] 68d04e21e6
chore(deps): update github.com/open-feature/flagd-schemas digest to 08b4c52 (#1682)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[github.com/open-feature/flagd-schemas](https://redirect.github.com/open-feature/flagd-schemas)
| require | digest | `9b0ee43` -> `08b4c52` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/flagd).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4yMy4yIiwidXBkYXRlZEluVmVyIjoiNDEuMjMuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-15 14:40:45 -04:00
github-actions[bot] 4a822fc83a
chore: release main (#1681)
🤖 I have created a release *beep* *boop*
---


<details><summary>core: 0.11.7</summary>

##
[0.11.7](https://github.com/open-feature/flagd/compare/core/v0.11.6...core/v0.11.7)
(2025-07-15)


### 🐛 Bug Fixes

* general err if targeting variant not in variants
([#1680](https://github.com/open-feature/flagd/issues/1680))
([6cabfc8](6cabfc8ff3))
</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 <109696520+openfeaturebot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-15 14:32:13 -04:00
Todd Baert 6cabfc8ff3
fix: general err if targeting variant not in variants (#1680)
This makes the RPC mode consistent with our in-process evaluations when
a variant is returned from targeting which is not in the variants list.

Relates to
758fbd5b84

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-15 14:19:15 -04:00
github-actions[bot] 961f9a6e13
chore: release main (#1661)
🤖 I have created a release *beep* *boop*
---


<details><summary>flagd: 0.12.6</summary>

##
[0.12.6](https://github.com/open-feature/flagd/compare/flagd/v0.12.5...flagd/v0.12.6)
(2025-07-10)


### 🐛 Bug Fixes

* **security:** update module github.com/go-viper/mapstructure/v2 to
v2.3.0 [security]
([#1667](https://github.com/open-feature/flagd/issues/1667))
([caa0ed0](caa0ed04eb))


###  New Features

* add sync_context to SyncFlags
([#1642](https://github.com/open-feature/flagd/issues/1642))
([07a45d9](07a45d9b22))
</details>

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

##
[0.7.5](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.4...flagd-proxy/v0.7.5)
(2025-07-10)


### 🐛 Bug Fixes

* **security:** update module github.com/go-viper/mapstructure/v2 to
v2.3.0 [security]
([#1667](https://github.com/open-feature/flagd/issues/1667))
([caa0ed0](caa0ed04eb))


###  New Features

* add sync_context to SyncFlags
([#1642](https://github.com/open-feature/flagd/issues/1642))
([07a45d9](07a45d9b22))
</details>

<details><summary>core: 0.11.6</summary>

##
[0.11.6](https://github.com/open-feature/flagd/compare/core/v0.11.5...core/v0.11.6)
(2025-07-10)


###  New Features

* add sync_context to SyncFlags
([#1642](https://github.com/open-feature/flagd/issues/1642))
([07a45d9](07a45d9b22))
* allowing null/missing defaultValue
([#1659](https://github.com/open-feature/flagd/issues/1659))
([3f6b78c](3f6b78c8cc))
</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 <109696520+openfeaturebot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-14 08:03:09 -04:00
Todd Baert 2f330af516
chore: rename rejected ADR
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-10 14:27:12 -04:00
Dave Josephsen b604574adf
docs: first draft multi-sync-sources ADR (#1636)
Intent of this PR is to add the first draft of the multi-synch-sources
ADR, this is a docs-only change

---------

Signed-off-by: Dave Josephsen <dave.josephsen@gmail.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-09 16:12:03 -04:00
Todd Baert b6384416e8
docs(ADR): propose support for flag set selection (#1634)
The goal of this decision document is to establish flag sets as a first
class concept in `flagd`, and support the dynamic
addition/update/removal of flag sets at runtime.

See document for all justifications and background.

@dominikhaska @tangenti @cupofcat 

Alternative proposal here:
https://github.com/open-feature/flagd/pull/1644#discussion_r2149723952

---------

Signed-off-by: Alexandra Oberaigner <alexandra.oberaigner@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Alexandra Oberaigner <alexandra.oberaigner@dynatrace.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-07-09 15:46:04 -04:00
Rahul Baradol 3f6b78c8cc
feat: allowing null/missing defaultValue (#1659)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

## This PR
- adds support for null/missing default values 

### Related Issues
Fixes #1647 

### Notes
Points to be noted...
- If there is no `defaultValue` and no targetting rules, then
`FlagNotFound` is returned
- If targetting doesn't resolve a variant, and there is no
`defaultValue`, then `FlagNotFound` is returned

---------

Signed-off-by: Rahul Baradol <rahul.baradol.14@gmail.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-09 15:25:30 -04:00
Hugo Huang 797b48294b
docs(ADR): support flags with duplicate keys. (#1660)
This is proposal for support flags with duplicate keys, as a follow up
of the discussion on #1634 and #1644.

The selector semantics change proposed in #1644 will be addressed in a
separate ADR.

---------

Signed-off-by: Hugo Huang <lorqor@gmail.com>
2025-07-08 08:35:54 -04:00
renovate[bot] caa0ed04eb
fix(security): update module github.com/go-viper/mapstructure/v2 to v2.3.0 [security] (#1667)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[github.com/go-viper/mapstructure/v2](https://redirect.github.com/go-viper/mapstructure)
| `v2.2.1` -> `v2.3.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fgo-viper%2fmapstructure%2fv2/v2.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fgo-viper%2fmapstructure%2fv2/v2.2.1/v2.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

### GitHub Vulnerability Alerts

####
[GHSA-fv92-fjc5-jj9h](https://redirect.github.com/go-viper/mapstructure/security/advisories/GHSA-fv92-fjc5-jj9h)

### Summary

Use of this library in a security-critical context may result in leaking
sensitive information, if used to process sensitive fields.

### Details

OpenBao (and presumably HashiCorp Vault) have surfaced error messages
from `mapstructure` as follows:


98c3a59c04/sdk/framework/field_data.go (L43-L50)

```go
			_, _, err := d.getPrimitive(field, schema)
			if err != nil {
				return fmt.Errorf("error converting input for field %q: %w", field, err)
			}
```

where this calls `mapstructure.WeakDecode(...)`:
98c3a59c04/sdk/framework/field_data.go (L181-L193)

```go

func (d *FieldData) getPrimitive(k string, schema *FieldSchema) (interface{}, bool, error) {
	raw, ok := d.Raw[k]
	if !ok {
		return nil, false, nil
	}

	switch t := schema.Type; t {
	case TypeBool:
		var result bool
		if err := mapstructure.WeakDecode(raw, &result); err != nil {
			return nil, false, err
		}
		return result, true, nil
```

Notably, `WeakDecode(...)` eventually calls one of the decode helpers,
which surfaces the original value:


1a66224d5e/mapstructure.go (L679-L686)


1a66224d5e/mapstructure.go (L726-L730)


1a66224d5e/mapstructure.go (L783-L787)

& more.

### PoC

To reproduce with OpenBao:

```
$ podman run -p 8300:8300 openbao/openbao:latest server -dev -dev-root-token-id=root -dev-listen-address=0.0.0.0:8300
```

and in a new tab:

```
$ BAO_TOKEN=root BAO_ADDR=http://localhost:8300 bao auth enable userpass
Success! Enabled userpass auth method at: userpass/
$ curl -X PUT -H "X-Vault-Request: true" -H "X-Vault-Token: root" -d '{"password":{"asdf":"my-sensitive-value"}}' "http://localhost:8300/v1/auth/userpass/users/adsf"
{"errors":["error converting input for field \"password\": '' expected type 'string', got unconvertible type 'map[string]interface {}', value: 'map[asdf:my-sensitive-value]'"]}
```

### Impact

This is an information disclosure bug with little mitigation. See
https://discuss.hashicorp.com/t/hcsec-2025-09-vault-may-expose-sensitive-information-in-error-logs-when-processing-malformed-data-with-the-kv-v2-plugin/74717
for a previous version. That version was fixed, but this is in the
second part of that error message (starting at `'' expected a map, got
'string'` -- when the field type is `string` and a `map` is provided, we
see the above information leak -- the previous example had a `map` type
field with a `string` value provided).

This was rated 4.5 Medium by HashiCorp in the past iteration.

---

### Release Notes

<details>
<summary>go-viper/mapstructure
(github.com/go-viper/mapstructure/v2)</summary>

###
[`v2.3.0`](https://redirect.github.com/go-viper/mapstructure/releases/tag/v2.3.0)

[Compare
Source](https://redirect.github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0)

#### What's Changed

- build(deps): bump actions/checkout from 4.1.7 to 4.2.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/46](https://redirect.github.com/go-viper/mapstructure/pull/46)
- build(deps): bump golangci/golangci-lint-action from 6.1.0 to 6.1.1 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/47](https://redirect.github.com/go-viper/mapstructure/pull/47)
- \[enhancement] Add check for `reflect.Value` in
`ComposeDecodeHookFunc` by
[@&#8203;mahadzaryab1](https://redirect.github.com/mahadzaryab1) in
[https://github.com/go-viper/mapstructure/pull/52](https://redirect.github.com/go-viper/mapstructure/pull/52)
- build(deps): bump actions/setup-go from 5.0.2 to 5.1.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/51](https://redirect.github.com/go-viper/mapstructure/pull/51)
- build(deps): bump actions/checkout from 4.2.0 to 4.2.2 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/50](https://redirect.github.com/go-viper/mapstructure/pull/50)
- build(deps): bump actions/setup-go from 5.1.0 to 5.2.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/55](https://redirect.github.com/go-viper/mapstructure/pull/55)
- build(deps): bump actions/setup-go from 5.2.0 to 5.3.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/58](https://redirect.github.com/go-viper/mapstructure/pull/58)
- ci: add Go 1.24 to the test matrix by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/go-viper/mapstructure/pull/74](https://redirect.github.com/go-viper/mapstructure/pull/74)
- build(deps): bump golangci/golangci-lint-action from 6.1.1 to 6.5.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/72](https://redirect.github.com/go-viper/mapstructure/pull/72)
- build(deps): bump golangci/golangci-lint-action from 6.5.0 to 6.5.1 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/76](https://redirect.github.com/go-viper/mapstructure/pull/76)
- build(deps): bump actions/setup-go from 5.3.0 to 5.4.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/78](https://redirect.github.com/go-viper/mapstructure/pull/78)
- feat: add decode hook for netip.Prefix by
[@&#8203;tklauser](https://redirect.github.com/tklauser) in
[https://github.com/go-viper/mapstructure/pull/85](https://redirect.github.com/go-viper/mapstructure/pull/85)
- Updates by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/go-viper/mapstructure/pull/86](https://redirect.github.com/go-viper/mapstructure/pull/86)
- build(deps): bump github/codeql-action from 2.13.4 to 3.28.15 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/87](https://redirect.github.com/go-viper/mapstructure/pull/87)
- build(deps): bump actions/setup-go from 5.4.0 to 5.5.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/93](https://redirect.github.com/go-viper/mapstructure/pull/93)
- build(deps): bump github/codeql-action from 3.28.15 to 3.28.17 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/92](https://redirect.github.com/go-viper/mapstructure/pull/92)
- build(deps): bump github/codeql-action from 3.28.17 to 3.28.19 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/97](https://redirect.github.com/go-viper/mapstructure/pull/97)
- build(deps): bump ossf/scorecard-action from 2.4.1 to 2.4.2 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/96](https://redirect.github.com/go-viper/mapstructure/pull/96)
- Update README.md by
[@&#8203;peczenyj](https://redirect.github.com/peczenyj) in
[https://github.com/go-viper/mapstructure/pull/90](https://redirect.github.com/go-viper/mapstructure/pull/90)
- Add omitzero tag. by
[@&#8203;Crystalix007](https://redirect.github.com/Crystalix007) in
[https://github.com/go-viper/mapstructure/pull/98](https://redirect.github.com/go-viper/mapstructure/pull/98)
- Use error structs instead of duplicated strings by
[@&#8203;m1k1o](https://redirect.github.com/m1k1o) in
[https://github.com/go-viper/mapstructure/pull/102](https://redirect.github.com/go-viper/mapstructure/pull/102)
- build(deps): bump github/codeql-action from 3.28.19 to 3.29.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-viper/mapstructure/pull/101](https://redirect.github.com/go-viper/mapstructure/pull/101)
- feat: add common error interface by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/go-viper/mapstructure/pull/105](https://redirect.github.com/go-viper/mapstructure/pull/105)
- update linter by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/go-viper/mapstructure/pull/106](https://redirect.github.com/go-viper/mapstructure/pull/106)
- Feature allow unset pointer by
[@&#8203;rostislaved](https://redirect.github.com/rostislaved) in
[https://github.com/go-viper/mapstructure/pull/80](https://redirect.github.com/go-viper/mapstructure/pull/80)

#### New Contributors

- [@&#8203;tklauser](https://redirect.github.com/tklauser) made their
first contribution in
[https://github.com/go-viper/mapstructure/pull/85](https://redirect.github.com/go-viper/mapstructure/pull/85)
- [@&#8203;peczenyj](https://redirect.github.com/peczenyj) made their
first contribution in
[https://github.com/go-viper/mapstructure/pull/90](https://redirect.github.com/go-viper/mapstructure/pull/90)
- [@&#8203;Crystalix007](https://redirect.github.com/Crystalix007) made
their first contribution in
[https://github.com/go-viper/mapstructure/pull/98](https://redirect.github.com/go-viper/mapstructure/pull/98)
- [@&#8203;rostislaved](https://redirect.github.com/rostislaved) made
their first contribution in
[https://github.com/go-viper/mapstructure/pull/80](https://redirect.github.com/go-viper/mapstructure/pull/80)

**Full Changelog**:
https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "" (UTC), Automerge - At any time (no
schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/flagd).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNy4yIiwidXBkYXRlZEluVmVyIjoiNDEuMTcuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-05 02:51:18 +00:00
renovate[bot] 76ac517446
fix(security): update vulnerable-dependencies (#1664)
This PR contains the following updates:

| Package | Change | Age | Confidence | Type | Update |
|---|---|---|---|---|---|
| buf.build/gen/go/open-feature/flagd/connectrpc/go |
`v1.18.1-20250127221518-be6d1143b690.1` ->
`v1.18.1-20250529171031-ebdc14163473.1` |
[![age](https://developer.mend.io/api/mc/badges/age/go/buf.build%2fgen%2fgo%2fopen-feature%2fflagd%2fconnectrpc%2fgo/v1.18.1-20250529171031-ebdc14163473.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/buf.build%2fgen%2fgo%2fopen-feature%2fflagd%2fconnectrpc%2fgo/v1.18.1-20250127221518-be6d1143b690.1/v1.18.1-20250529171031-ebdc14163473.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | patch |
| buf.build/gen/go/open-feature/flagd/grpc/go |
`v1.5.1-20250127221518-be6d1143b690.2` ->
`v1.5.1-20250529171031-ebdc14163473.2` |
[![age](https://developer.mend.io/api/mc/badges/age/go/buf.build%2fgen%2fgo%2fopen-feature%2fflagd%2fgrpc%2fgo/v1.5.1-20250529171031-ebdc14163473.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/buf.build%2fgen%2fgo%2fopen-feature%2fflagd%2fgrpc%2fgo/v1.5.1-20250127221518-be6d1143b690.2/v1.5.1-20250529171031-ebdc14163473.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | patch |
| buf.build/gen/go/open-feature/flagd/protocolbuffers/go |
`v1.36.5-20250127221518-be6d1143b690.1` ->
`v1.36.6-20250529171031-ebdc14163473.1` |
[![age](https://developer.mend.io/api/mc/badges/age/go/buf.build%2fgen%2fgo%2fopen-feature%2fflagd%2fprotocolbuffers%2fgo/v1.36.6-20250529171031-ebdc14163473.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/buf.build%2fgen%2fgo%2fopen-feature%2fflagd%2fprotocolbuffers%2fgo/v1.36.5-20250127221518-be6d1143b690.1/v1.36.6-20250529171031-ebdc14163473.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | patch |
|
[github.com/diegoholiveira/jsonlogic/v3](https://redirect.github.com/diegoholiveira/jsonlogic)
| `v3.7.4` -> `v3.8.4` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fdiegoholiveira%2fjsonlogic%2fv3/v3.8.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fdiegoholiveira%2fjsonlogic%2fv3/v3.7.4/v3.8.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[github.com/fsnotify/fsnotify](https://redirect.github.com/fsnotify/fsnotify)
| `v1.8.0` -> `v1.9.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2ffsnotify%2ffsnotify/v1.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2ffsnotify%2ffsnotify/v1.8.0/v1.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[github.com/open-feature/flagd/core](https://redirect.github.com/open-feature/flagd)
| `v0.11.2` -> `v0.11.5` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fopen-feature%2fflagd%2fcore/v0.11.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fopen-feature%2fflagd%2fcore/v0.11.2/v0.11.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | patch |
|
[github.com/open-feature/open-feature-operator/apis](https://redirect.github.com/open-feature/open-feature-operator)
| `v0.2.44` -> `v0.2.45` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fopen-feature%2fopen-feature-operator%2fapis/v0.2.45?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fopen-feature%2fopen-feature-operator%2fapis/v0.2.44/v0.2.45?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | patch |
|
[github.com/prometheus/client_golang](https://redirect.github.com/prometheus/client_golang)
| `v1.21.1` -> `v1.22.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fprometheus%2fclient_golang/v1.22.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fprometheus%2fclient_golang/v1.21.1/v1.22.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| [github.com/spf13/viper](https://redirect.github.com/spf13/viper) |
`v1.19.0` -> `v1.20.1` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fspf13%2fviper/v1.20.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fspf13%2fviper/v1.19.0/v1.20.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | `1.24.2` -> `1.24.4`
|
[![age](https://developer.mend.io/api/mc/badges/age/golang-version/go/1.24.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/golang-version/go/1.24.2/1.24.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| toolchain | patch |
|
[go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp](https://redirect.github.com/open-telemetry/opentelemetry-go-contrib)
| `v0.60.0` -> `v0.62.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fcontrib%2finstrumentation%2fnet%2fhttp%2fotelhttp/v0.62.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fcontrib%2finstrumentation%2fnet%2fhttp%2fotelhttp/v0.60.0/v0.62.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v1.35.0` -> `v1.37.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel/v1.35.0/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v1.35.0` -> `v1.37.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel%2fexporters%2fotlp%2fotlpmetric%2fotlpmetricgrpc/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel%2fexporters%2fotlp%2fotlpmetric%2fotlpmetricgrpc/v1.35.0/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel/exporters/otlp/otlptrace](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v1.35.0` -> `v1.37.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel%2fexporters%2fotlp%2fotlptrace/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel%2fexporters%2fotlp%2fotlptrace/v1.35.0/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v1.35.0` -> `v1.37.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel%2fexporters%2fotlp%2fotlptrace%2fotlptracegrpc/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel%2fexporters%2fotlp%2fotlptrace%2fotlptracegrpc/v1.35.0/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel/exporters/prometheus](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v0.57.0` -> `v0.59.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel%2fexporters%2fprometheus/v0.59.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel%2fexporters%2fprometheus/v0.57.0/v0.59.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel/metric](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v1.35.0` -> `v1.37.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel%2fmetric/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel%2fmetric/v1.35.0/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel/sdk](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v1.35.0` -> `v1.37.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel%2fsdk/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel%2fsdk/v1.35.0/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel/sdk/metric](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v1.35.0` -> `v1.37.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel%2fsdk%2fmetric/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel%2fsdk%2fmetric/v1.35.0/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[go.opentelemetry.io/otel/trace](https://redirect.github.com/open-telemetry/opentelemetry-go)
| `v1.35.0` -> `v1.37.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.opentelemetry.io%2fotel%2ftrace/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.opentelemetry.io%2fotel%2ftrace/v1.35.0/v1.37.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| [go.uber.org/mock](https://redirect.github.com/uber/mock) | `v0.5.0`
-> `v0.5.2` |
[![age](https://developer.mend.io/api/mc/badges/age/go/go.uber.org%2fmock/v0.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/go.uber.org%2fmock/v0.5.0/v0.5.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | patch |
| [gocloud.dev](https://redirect.github.com/google/go-cloud) | `v0.40.0`
-> `v0.42.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/gocloud.dev/v0.42.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/gocloud.dev/v0.40.0/v0.42.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| golang.org/x/crypto | `v0.33.0` -> `v0.35.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fcrypto/v0.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fcrypto/v0.33.0/v0.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| indirect | minor |
| golang.org/x/crypto | `v0.33.0` -> `v0.35.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fcrypto/v0.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fcrypto/v0.33.0/v0.35.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| golang.org/x/mod | `v0.23.0` -> `v0.25.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fmod/v0.25.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fmod/v0.23.0/v0.25.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| golang.org/x/net | `v0.35.0` -> `v0.38.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fnet/v0.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fnet/v0.35.0/v0.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| golang.org/x/net | `v0.35.0` -> `v0.38.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fnet/v0.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fnet/v0.35.0/v0.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| indirect | minor |
| golang.org/x/sync | `v0.11.0` -> `v0.15.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fsync/v0.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fsync/v0.11.0/v0.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| [google.golang.org/grpc](https://redirect.github.com/grpc/grpc-go) |
`v1.71.0` -> `v1.73.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/google.golang.org%2fgrpc/v1.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/google.golang.org%2fgrpc/v1.71.0/v1.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
|
[k8s.io/apimachinery](https://redirect.github.com/kubernetes/apimachinery)
| `v0.31.4` -> `v0.33.2` |
[![age](https://developer.mend.io/api/mc/badges/age/go/k8s.io%2fapimachinery/v0.33.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/k8s.io%2fapimachinery/v0.31.4/v0.33.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |
| [k8s.io/client-go](https://redirect.github.com/kubernetes/client-go) |
`v0.31.4` -> `v0.33.2` |
[![age](https://developer.mend.io/api/mc/badges/age/go/k8s.io%2fclient-go/v0.33.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/k8s.io%2fclient-go/v0.31.4/v0.33.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| require | minor |

### GitHub Vulnerability Alerts

#### [CVE-2025-22869](https://nvd.nist.gov/vuln/detail/CVE-2025-22869)

SSH servers which implement file transfer protocols are vulnerable to a
denial of service attack from clients which complete the key exchange
slowly, or not at all, causing pending content to be read into memory,
but never transmitted.

#### [CVE-2025-22870](https://nvd.nist.gov/vuln/detail/CVE-2025-22870)

Matching of hosts against proxy patterns can improperly treat an IPv6
zone ID as a hostname component. For example, when the NO_PROXY
environment variable is set to "*.example.com", a request to
"[::1%25.example.com]:80` will incorrectly match and not be proxied.

#### [CVE-2025-22872](https://nvd.nist.gov/vuln/detail/CVE-2025-22872)

The tokenizer incorrectly interprets tags with unquoted attribute values
that end with a solidus character (/) as self-closing. When directly
using Tokenizer, this can result in such tags incorrectly being marked
as self-closing, and when using the Parse functions, this can result in
content following such tags as being placed in the wrong scope during
DOM construction, but only when tags are in foreign content (e.g.
<math>, <svg>, etc contexts).

---

### Release Notes

<details>
<summary>diegoholiveira/jsonlogic
(github.com/diegoholiveira/jsonlogic/v3)</summary>

###
[`v3.8.4`](https://redirect.github.com/diegoholiveira/jsonlogic/releases/tag/v3.8.4)

[Compare
Source](https://redirect.github.com/diegoholiveira/jsonlogic/compare/v3.8.3...v3.8.4)

#### What's Changed

- operation.go: guard global mem from concurent writes on startup and
config by [@&#8203;Moisi](https://redirect.github.com/Moisi) in
[https://github.com/diegoholiveira/jsonlogic/pull/124](https://redirect.github.com/diegoholiveira/jsonlogic/pull/124)

#### New Contributors

- [@&#8203;Moisi](https://redirect.github.com/Moisi) made their first
contribution in
[https://github.com/diegoholiveira/jsonlogic/pull/124](https://redirect.github.com/diegoholiveira/jsonlogic/pull/124)

**Full Changelog**:
https://github.com/diegoholiveira/jsonlogic/compare/v3.8.3...v3.8.4

###
[`v3.8.3`](https://redirect.github.com/diegoholiveira/jsonlogic/releases/tag/v3.8.3)

[Compare
Source](https://redirect.github.com/diegoholiveira/jsonlogic/compare/v3.8.2...v3.8.3)

#### What's Changed

- fix(122): Negating an empty slice should return true by
[@&#8203;juannorris](https://redirect.github.com/juannorris) in
[https://github.com/diegoholiveira/jsonlogic/pull/123](https://redirect.github.com/diegoholiveira/jsonlogic/pull/123)

**Full Changelog**:
https://github.com/diegoholiveira/jsonlogic/compare/v3.8.2...v3.8.3

###
[`v3.8.2`](https://redirect.github.com/diegoholiveira/jsonlogic/releases/tag/v3.8.2)

[Compare
Source](https://redirect.github.com/diegoholiveira/jsonlogic/compare/v3.8.1...v3.8.2)

#### What's Changed

- fix: allow 'map primitives' in ValidateJsonLogic by
[@&#8203;juannorris](https://redirect.github.com/juannorris) in
[https://github.com/diegoholiveira/jsonlogic/pull/121](https://redirect.github.com/diegoholiveira/jsonlogic/pull/121)

**Full Changelog**:
https://github.com/diegoholiveira/jsonlogic/compare/v3.8.1...v3.8.2

###
[`v3.8.1`](https://redirect.github.com/diegoholiveira/jsonlogic/releases/tag/v3.8.1)

[Compare
Source](https://redirect.github.com/diegoholiveira/jsonlogic/compare/v3.8.0...v3.8.1)

#### What's Changed

- Enhancement proposal for test names in TestRulesFromJsonLogic test
suite by [@&#8203;juannorris](https://redirect.github.com/juannorris) in
[https://github.com/diegoholiveira/jsonlogic/pull/116](https://redirect.github.com/diegoholiveira/jsonlogic/pull/116)
- disable setup-go dependency cache by
[@&#8203;diegoholiveira](https://redirect.github.com/diegoholiveira) in
[https://github.com/diegoholiveira/jsonlogic/pull/117](https://redirect.github.com/diegoholiveira/jsonlogic/pull/117)
- Make IF operator behave lazily (like AND and OR) by
[@&#8203;juannorris](https://redirect.github.com/juannorris) in
[https://github.com/diegoholiveira/jsonlogic/pull/118](https://redirect.github.com/diegoholiveira/jsonlogic/pull/118)
- Remove some unused code by
[@&#8203;diegoholiveira](https://redirect.github.com/diegoholiveira) in
[https://github.com/diegoholiveira/jsonlogic/pull/119](https://redirect.github.com/diegoholiveira/jsonlogic/pull/119)

**Full Changelog**:
https://github.com/diegoholiveira/jsonlogic/compare/v3.8.0...v3.8.1

###
[`v3.8.0`](https://redirect.github.com/diegoholiveira/jsonlogic/releases/tag/v3.8.0)

[Compare
Source](https://redirect.github.com/diegoholiveira/jsonlogic/compare/v3.7.5...v3.8.0)

#### What's Changed

##### Performance Enhancements

- Reduced memory usage by ~2% across all operations
- Decreased allocation count by ~5% through better memory pre-allocation
- Improved execution time by ~2.5% on average
- Most significant speedups in equality operations (+10%) and complex
condition evaluation (+8%)

To better understand this numbers, I published the benchmark suite in
https://github.com/diegoholiveira/jsonlogic/tree/main/benchmark

##### Code Improvements

- Renamed 'arrays.go' to 'lists.go' for better semantic clarity
- Added pre-allocation of slices with appropriate capacity for improved
performance
- Optimized common operations with early returns for empty or
single-element arrays
- Enhanced memory efficiency in list operations (filter, map, merge)
- Improved type handling and nil checks in equality operations
- Added optimized paths for sum and concatenation operations

This release focuses on performance optimization and code quality
improvements, making the library more efficient while maintaining full
compatibility with the JSONLogic specification.

###
[`v3.7.5`](https://redirect.github.com/diegoholiveira/jsonlogic/releases/tag/v3.7.5)

[Compare
Source](https://redirect.github.com/diegoholiveira/jsonlogic/compare/v3.7.4...v3.7.5)

#### What's Changed

- create the javascript package to encapsulate specific JS behavior by
[@&#8203;diegoholiveira](https://redirect.github.com/diegoholiveira) in
[https://github.com/diegoholiveira/jsonlogic/pull/106](https://redirect.github.com/diegoholiveira/jsonlogic/pull/106)
- create the `typing` package with types conversion helpers by
[@&#8203;diegoholiveira](https://redirect.github.com/diegoholiveira) in
[https://github.com/diegoholiveira/jsonlogic/pull/107](https://redirect.github.com/diegoholiveira/jsonlogic/pull/107)
- Improve go docs by
[@&#8203;diegoholiveira](https://redirect.github.com/diegoholiveira) in
[https://github.com/diegoholiveira/jsonlogic/pull/108](https://redirect.github.com/diegoholiveira/jsonlogic/pull/108)
- fix: ensure default values are used only when required by
[@&#8203;diegoholiveira](https://redirect.github.com/diegoholiveira) in
[https://github.com/diegoholiveira/jsonlogic/pull/111](https://redirect.github.com/diegoholiveira/jsonlogic/pull/111)

**Full Changelog**:
https://github.com/diegoholiveira/jsonlogic/compare/v3.7.4...v3.7.5

</details>

<details>
<summary>fsnotify/fsnotify (github.com/fsnotify/fsnotify)</summary>

###
[`v1.9.0`](https://redirect.github.com/fsnotify/fsnotify/releases/tag/v1.9.0)

[Compare
Source](https://redirect.github.com/fsnotify/fsnotify/compare/v1.8.0...v1.9.0)

##### Changes and fixes

- all: make BufferedWatcher buffered again ([#&#8203;657])

- inotify: fix race when adding/removing watches while a watched path is
being deleted ([#&#8203;678], [#&#8203;686])

- inotify: don't send empty event if a watched path is unmounted
([#&#8203;655])

- inotify: don't register duplicate watches when watching both a symlink
and its target; previously that would get "half-added" and removing the
second would panic ([#&#8203;679])

- kqueue: fix watching relative symlinks ([#&#8203;681])

- kqueue: correctly mark pre-existing entries when watching a link to a
dir on kqueue ([#&#8203;682])

- illumos: don't send error if changed file is deleted while processing
the event ([#&#8203;678])

[#&#8203;657]: https://redirect.github.com/fsnotify/fsnotify/pull/657

[#&#8203;678]: https://redirect.github.com/fsnotify/fsnotify/pull/678

[#&#8203;686]: https://redirect.github.com/fsnotify/fsnotify/pull/686

[#&#8203;655]: https://redirect.github.com/fsnotify/fsnotify/pull/655

[#&#8203;681]: https://redirect.github.com/fsnotify/fsnotify/pull/681

[#&#8203;679]: https://redirect.github.com/fsnotify/fsnotify/pull/679

[#&#8203;682]: https://redirect.github.com/fsnotify/fsnotify/pull/682

</details>

<details>
<summary>prometheus/client_golang
(github.com/prometheus/client_golang)</summary>

###
[`v1.22.0`](https://redirect.github.com/prometheus/client_golang/releases/tag/v1.22.0):
- 2025-04-07

[Compare
Source](https://redirect.github.com/prometheus/client_golang/compare/v1.21.1...v1.22.0)

⚠️ This release contains potential breaking change if you use
experimental `zstd` support introduce in
[#&#8203;1496](https://redirect.github.com/prometheus/client_golang/issues/1496)
⚠️

Experimental support for `zstd` on scrape was added, controlled by the
request `Accept-Encoding` header.
It was enabled by default since version 1.20, but now you need to add a
blank import to enable it.
The decision to make it opt-in by default was originally made because
the Go standard library was expected to have default zstd support added
soon,

[https://github.com/golang/go/issues/62513](https://redirect.github.com/golang/go/issues/62513)
however, the work took longer than anticipated and it will be postponed
to upcoming major Go versions.

e.g.:

> ```go
> import (
>   _ "github.com/prometheus/client_golang/prometheus/promhttp/zstd"
> )
> ```

- \[FEATURE] prometheus: Add new CollectorFunc utility
[#&#8203;1724](https://redirect.github.com/prometheus/client_golang/issues/1724)
- \[CHANGE] Minimum required Go version is now 1.22 (we also test
client\_golang against latest go version - 1.24)
[#&#8203;1738](https://redirect.github.com/prometheus/client_golang/issues/1738)
- \[FEATURE] api: `WithLookbackDelta` and `WithStats` options have been
added to API client.
[#&#8203;1743](https://redirect.github.com/prometheus/client_golang/issues/1743)
- \[CHANGE] ⚠️ promhttp: Isolate zstd support and
klauspost/compress library use to promhttp/zstd package.
[#&#8203;1765](https://redirect.github.com/prometheus/client_golang/issues/1765)

<details>
<summary> All Changes </summary>

- build(deps): bump golang.org/x/sys from 0.28.0 to 0.29.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1720](https://redirect.github.com/prometheus/client_golang/pull/1720)0
- build(deps): bump google.golang.org/protobuf from 1.36.1 to 1.36.3 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1719](https://redirect.github.com/prometheus/client_golang/pull/1719)9
- Update RELEASE.md by
[@&#8203;bwplotka](https://redirect.github.com/bwplotka) in
[https://github.com/prometheus/client_golang/pull/1721](https://redirect.github.com/prometheus/client_golang/pull/1721)1
- chore(docs): Add links for the upstream PRs by
[@&#8203;kakkoyun](https://redirect.github.com/kakkoyun) in
[https://github.com/prometheus/client_golang/pull/1722](https://redirect.github.com/prometheus/client_golang/pull/1722)2
- Added tips on releasing client and checking with k8s. by
[@&#8203;bwplotka](https://redirect.github.com/bwplotka) in
[https://github.com/prometheus/client_golang/pull/1723](https://redirect.github.com/prometheus/client_golang/pull/1723)3
- feat: Add new CollectorFunc utility by
[@&#8203;Saumya40-codes](https://redirect.github.com/Saumya40-codes) in
[https://github.com/prometheus/client_golang/pull/1724](https://redirect.github.com/prometheus/client_golang/pull/1724)4
- build(deps): bump google.golang.org/protobuf from 1.36.3 to 1.36.4 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1725](https://redirect.github.com/prometheus/client_golang/pull/1725)5
- build(deps): bump the github-actions group with 5 updates by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1726](https://redirect.github.com/prometheus/client_golang/pull/1726)6
- Synchronize common files from prometheus/prometheus by
[@&#8203;prombot](https://redirect.github.com/prombot) in
[https://github.com/prometheus/client_golang/pull/1727](https://redirect.github.com/prometheus/client_golang/pull/1727)7
- Synchronize common files from prometheus/prometheus by
[@&#8203;prombot](https://redirect.github.com/prombot) in
[https://github.com/prometheus/client_golang/pull/1731](https://redirect.github.com/prometheus/client_golang/pull/1731)1
- build(deps): bump golang.org/x/sys from 0.29.0 to 0.30.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1739](https://redirect.github.com/prometheus/client_golang/pull/1739)9
- build(deps): bump google.golang.org/protobuf from 1.36.4 to 1.36.5 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1740](https://redirect.github.com/prometheus/client_golang/pull/1740)0
- Cleanup dependabot config by
[@&#8203;SuperQ](https://redirect.github.com/SuperQ) in
[https://github.com/prometheus/client_golang/pull/1741](https://redirect.github.com/prometheus/client_golang/pull/1741)1
- Upgrade Golang version v1.24 by
[@&#8203;dongjiang1989](https://redirect.github.com/dongjiang1989) in
[https://github.com/prometheus/client_golang/pull/1738](https://redirect.github.com/prometheus/client_golang/pull/1738)8
- build(deps): bump the github-actions group with 2 updates by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1742](https://redirect.github.com/prometheus/client_golang/pull/1742)2
- Merging 1.21 release back to main. by
[@&#8203;bwplotka](https://redirect.github.com/bwplotka) in
[https://github.com/prometheus/client_golang/pull/1744](https://redirect.github.com/prometheus/client_golang/pull/1744)4
- Synchronize common files from prometheus/prometheus by
[@&#8203;prombot](https://redirect.github.com/prombot) in
[https://github.com/prometheus/client_golang/pull/1745](https://redirect.github.com/prometheus/client_golang/pull/1745)5
- Add support for undocumented query options for API by
[@&#8203;mahendrapaipuri](https://redirect.github.com/mahendrapaipuri)
in
[https://github.com/prometheus/client_golang/pull/1743](https://redirect.github.com/prometheus/client_golang/pull/1743)3
- exp/api: Add experimental exp module; Add remote API with write client
and handler. by [@&#8203;bwplotka](https://redirect.github.com/bwplotka)
in
[https://github.com/prometheus/client_golang/pull/1658](https://redirect.github.com/prometheus/client_golang/pull/1658)8
- exp/api: Add accepted msg type validation to handler by
[@&#8203;saswatamcode](https://redirect.github.com/saswatamcode) in
[https://github.com/prometheus/client_golang/pull/1750](https://redirect.github.com/prometheus/client_golang/pull/1750)0
- build(deps): bump the github-actions group with 5 updates by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1751](https://redirect.github.com/prometheus/client_golang/pull/1751)1
- build(deps): bump github.com/klauspost/compress from 1.17.11 to 1.18.0
by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1752](https://redirect.github.com/prometheus/client_golang/pull/1752)2
- build(deps): bump github.com/google/go-cmp from 0.6.0 to 0.7.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1753](https://redirect.github.com/prometheus/client_golang/pull/1753)3
- exp: Reset snappy buf by
[@&#8203;saswatamcode](https://redirect.github.com/saswatamcode) in
[https://github.com/prometheus/client_golang/pull/1756](https://redirect.github.com/prometheus/client_golang/pull/1756)6
- Merge release 1.21.1 to main. by
[@&#8203;bwplotka](https://redirect.github.com/bwplotka) in
[https://github.com/prometheus/client_golang/pull/1762](https://redirect.github.com/prometheus/client_golang/pull/1762)2
- exp: Add dependabot config by
[@&#8203;saswatamcode](https://redirect.github.com/saswatamcode) in
[https://github.com/prometheus/client_golang/pull/1754](https://redirect.github.com/prometheus/client_golang/pull/1754)4
- build(deps): bump peter-evans/create-pull-request from 7.0.7 to 7.0.8
in the github-actions group by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/prometheus/client_golang/pull/1764](https://redirect.github.com/prometheus/client_golang/pull/1764)4
- promhttp: Isolate zstd support and klauspost/compress library use to
promhttp/zstd package by
[@&#8203;liggitt](https://redirect.github.com/liggitt) in
[https://github.com/prometheus/client_golang/pull/1765](https://redirect.github.com/prometheus/client_golang/pull/1765)5
- Cut 1.22.0-rc.0 by
[@&#8203;kakkoyun](https://redirect.github.com/kakkoyun) in
[https://github.com/prometheus/client_golang/pull/1768](https://redirect.github.com/prometheus/client_golang/pull/1768)8

</details>

#### New Contributors
* @&#8203;Saumya40-codes made their first
contributi[https://github.com/prometheus/client_golang/pull/1724](https://redirect.github.com/prometheus/client_golang/pull/1724)l/1724
* @&#8203;mahendrapaipuri made their first
contributi[https://github.com/prometheus/client_golang/pull/1743](https://redirect.github.com/prometheus/client_golang/pull/1743)l/1743
* @&#8203;liggitt made their first
contributi[https://github.com/prometheus/client_golang/pull/1765](https://redirect.github.com/prometheus/client_golang/pull/1765)l/1765

**Full Changelog**:
https://github.com/prometheus/client\_golang/compare/v1.21.1...v1.22.0-rc.0

</details>

<details>
<summary>spf13/viper (github.com/spf13/viper)</summary>

###
[`v1.20.1`](https://redirect.github.com/spf13/viper/releases/tag/v1.20.1)

[Compare
Source](https://redirect.github.com/spf13/viper/compare/v1.20.0...v1.20.1)

<!-- Release notes generated using configuration in .github/release.yml
at v1.20.1 -->

##### What's Changed

##### Bug Fixes 🐛

- Backport config type fixes to 1.20.x by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/2005](https://redirect.github.com/spf13/viper/pull/2005)

**Full Changelog**:
https://github.com/spf13/viper/compare/v1.20.0...v1.20.1

###
[`v1.20.0`](https://redirect.github.com/spf13/viper/releases/tag/v1.20.0)

[Compare
Source](https://redirect.github.com/spf13/viper/compare/v1.19.0...v1.20.0)

<!-- Release notes generated using configuration in .github/release.yml
at v1.20.0 -->

> \[!WARNING]
> This release includes a few minor breaking changes. Read the [upgrade
guide](https://redirect.github.com/spf13/viper/blob/master/UPGRADE.md#v120x)
for details.

#### What's Changed

##### Exciting New Features 🎉

- New encoding layer by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1869](https://redirect.github.com/spf13/viper/pull/1869)

##### Enhancements 🚀

- Drop Go 1.20 support by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1846](https://redirect.github.com/spf13/viper/pull/1846)
- Drop slog shim by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1848](https://redirect.github.com/spf13/viper/pull/1848)
- Replace file searching API with a finder by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1849](https://redirect.github.com/spf13/viper/pull/1849)
- Finder feature flag by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1852](https://redirect.github.com/spf13/viper/pull/1852)
- Allow setting options on the global Viper instance by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1856](https://redirect.github.com/spf13/viper/pull/1856)
- Add experimental flag for bind struct by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1854](https://redirect.github.com/spf13/viper/pull/1854)
- Make the remote package a separate module by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1860](https://redirect.github.com/spf13/viper/pull/1860)
- Add decoder hook option by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1872](https://redirect.github.com/spf13/viper/pull/1872)
- Encoder improvements by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1885](https://redirect.github.com/spf13/viper/pull/1885)
- Get uint8 by
[@&#8203;martinconic](https://redirect.github.com/martinconic) in
[https://github.com/spf13/viper/pull/1894](https://redirect.github.com/spf13/viper/pull/1894)

##### Bug Fixes 🐛

- Fix missing config type when reading from a buffer by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1857](https://redirect.github.com/spf13/viper/pull/1857)
- fix: do not allow setting dependencies to nil values by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1871](https://redirect.github.com/spf13/viper/pull/1871)
- feat: copy keydelim from parent chart in viper.Sub() by
[@&#8203;obs-gh-alexlew](https://redirect.github.com/obs-gh-alexlew) in
[https://github.com/spf13/viper/pull/1887](https://redirect.github.com/spf13/viper/pull/1887)

##### Breaking Changes 🛠

- Drop encoding formats: HCL, Java properties, INI by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1870](https://redirect.github.com/spf13/viper/pull/1870)

##### Dependency Updates ⬆️

- chore: update mapstructure by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1723](https://redirect.github.com/spf13/viper/pull/1723)
- chore: update crypt by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1834](https://redirect.github.com/spf13/viper/pull/1834)
- build(deps): bump github/codeql-action from 3.25.7 to 3.25.8 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1853](https://redirect.github.com/spf13/viper/pull/1853)
- Revert to go-difflib and go-spew releases by
[@&#8203;skitt](https://redirect.github.com/skitt) in
[https://github.com/spf13/viper/pull/1861](https://redirect.github.com/spf13/viper/pull/1861)
- build(deps): bump actions/dependency-review-action from 4.3.2 to 4.3.3
by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1862](https://redirect.github.com/spf13/viper/pull/1862)
- build(deps): bump github/codeql-action from 3.25.8 to 3.25.10 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1865](https://redirect.github.com/spf13/viper/pull/1865)
- build(deps): bump actions/checkout from 4.1.6 to 4.1.7 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1864](https://redirect.github.com/spf13/viper/pull/1864)
- chore: update crypt by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1866](https://redirect.github.com/spf13/viper/pull/1866)
- build(deps): bump github/codeql-action from 3.25.10 to 3.25.11 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1876](https://redirect.github.com/spf13/viper/pull/1876)
- build(deps): bump google.golang.org/grpc from 1.64.0 to 1.64.1 in
/remote by [@&#8203;dependabot](https://redirect.github.com/dependabot)
in
[https://github.com/spf13/viper/pull/1878](https://redirect.github.com/spf13/viper/pull/1878)
- build(deps): bump actions/setup-go from 5.0.1 to 5.0.2 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1879](https://redirect.github.com/spf13/viper/pull/1879)
- build(deps): bump actions/dependency-review-action from 4.3.3 to 4.3.4
by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1881](https://redirect.github.com/spf13/viper/pull/1881)
- build(deps): bump github/codeql-action from 3.25.11 to 3.25.12 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1880](https://redirect.github.com/spf13/viper/pull/1880)
- build(deps): bump github/codeql-action from 3.25.12 to 3.25.13 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1883](https://redirect.github.com/spf13/viper/pull/1883)
- chore(deps): update crypt by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1884](https://redirect.github.com/spf13/viper/pull/1884)
- chore: update dependencies by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1888](https://redirect.github.com/spf13/viper/pull/1888)
- build(deps): bump github.com/go-viper/mapstructure/v2 from 2.0.0 to
2.1.0 by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1901](https://redirect.github.com/spf13/viper/pull/1901)
- build(deps): bump github.com/spf13/cast from 1.6.0 to 1.7.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1899](https://redirect.github.com/spf13/viper/pull/1899)
- build(deps): bump github/codeql-action from 3.25.13 to 3.26.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1897](https://redirect.github.com/spf13/viper/pull/1897)
- build(deps): bump golangci/golangci-lint-action from 6.0.1 to 6.1.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1893](https://redirect.github.com/spf13/viper/pull/1893)
- build(deps): bump github/codeql-action from 3.26.0 to 3.26.2 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1903](https://redirect.github.com/spf13/viper/pull/1903)
- build(deps): bump github/codeql-action from 3.26.2 to 3.26.3 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1905](https://redirect.github.com/spf13/viper/pull/1905)
- build(deps): bump github/codeql-action from 3.26.3 to 3.26.5 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1909](https://redirect.github.com/spf13/viper/pull/1909)
- Update Go by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1913](https://redirect.github.com/spf13/viper/pull/1913)
- chore: update crypt package by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1914](https://redirect.github.com/spf13/viper/pull/1914)
- build(deps): bump github/codeql-action from 3.26.5 to 3.26.6 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1915](https://redirect.github.com/spf13/viper/pull/1915)
- build(deps): bump mheap/github-action-required-labels from 5.4.1 to
5.4.2 by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1916](https://redirect.github.com/spf13/viper/pull/1916)
- build(deps): bump cachix/install-nix-action from 27 to 28 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1919](https://redirect.github.com/spf13/viper/pull/1919)
- build(deps): bump github/codeql-action from 3.26.6 to 3.26.7 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1920](https://redirect.github.com/spf13/viper/pull/1920)
- chore: update crypt by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1921](https://redirect.github.com/spf13/viper/pull/1921)
- build(deps): bump github/codeql-action from 3.26.7 to 3.26.8 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1923](https://redirect.github.com/spf13/viper/pull/1923)
- build(deps): bump github.com/go-viper/mapstructure/v2 from 2.1.0 to
2.2.1 by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1925](https://redirect.github.com/spf13/viper/pull/1925)
- build(deps): bump github/codeql-action from 3.26.8 to 3.26.11 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1932](https://redirect.github.com/spf13/viper/pull/1932)
- build(deps): bump golangci/golangci-lint-action from 6.1.0 to 6.1.1 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1930](https://redirect.github.com/spf13/viper/pull/1930)
- build(deps): bump actions/checkout from 4.1.7 to 4.2.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1928](https://redirect.github.com/spf13/viper/pull/1928)
- build(deps): bump actions/checkout from 4.2.0 to 4.2.1 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1936](https://redirect.github.com/spf13/viper/pull/1936)
- build(deps): bump github/codeql-action from 3.26.11 to 3.27.2 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1948](https://redirect.github.com/spf13/viper/pull/1948)
- build(deps): bump github.com/fsnotify/fsnotify from 1.7.0 to 1.8.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1944](https://redirect.github.com/spf13/viper/pull/1944)
- build(deps): bump actions/setup-go from 5.0.2 to 5.1.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1942](https://redirect.github.com/spf13/viper/pull/1942)
- build(deps): bump actions/dependency-review-action from 4.3.4 to 4.4.0
by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1943](https://redirect.github.com/spf13/viper/pull/1943)
- build(deps): bump actions/checkout from 4.2.1 to 4.2.2 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1941](https://redirect.github.com/spf13/viper/pull/1941)
- build(deps): bump github/codeql-action from 3.27.2 to 3.27.3 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1949](https://redirect.github.com/spf13/viper/pull/1949)
- build(deps): bump github/codeql-action from 3.27.3 to 3.27.7 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1958](https://redirect.github.com/spf13/viper/pull/1958)
- build(deps): bump mheap/github-action-required-labels from 5.4.2 to
5.5.0 by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1957](https://redirect.github.com/spf13/viper/pull/1957)
- build(deps): bump actions/dependency-review-action from 4.4.0 to 4.5.0
by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1953](https://redirect.github.com/spf13/viper/pull/1953)
- build(deps): bump actions/setup-go from 5.1.0 to 5.2.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1959](https://redirect.github.com/spf13/viper/pull/1959)
- build(deps): bump github.com/stretchr/testify from 1.9.0 to 1.10.0 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1954](https://redirect.github.com/spf13/viper/pull/1954)
- build(deps): bump golang.org/x/crypto from 0.27.0 to 0.31.0 in /remote
by [@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1960](https://redirect.github.com/spf13/viper/pull/1960)
- build(deps): bump github/codeql-action from 3.27.7 to 3.27.9 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1964](https://redirect.github.com/spf13/viper/pull/1964)
- chore: update afero by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1973](https://redirect.github.com/spf13/viper/pull/1973)
- build(deps): bump github.com/spf13/cast from 1.7.0 to 1.7.1 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1968](https://redirect.github.com/spf13/viper/pull/1968)
- build(deps): bump github.com/spf13/pflag from 1.0.5 to 1.0.6 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/spf13/viper/pull/1979](https://redirect.github.com/spf13/viper/pull/1979)
- ci: add Go 1.24 to the test matrix by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1983](https://redirect.github.com/spf13/viper/pull/1983)

##### Other Changes

- refactor: move remote code to separate file by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1847](https://redirect.github.com/spf13/viper/pull/1847)
- refactor: cleanup unused encoding code by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1889](https://redirect.github.com/spf13/viper/pull/1889)
- Fix issues reported by testifylint by
[@&#8203;deining](https://redirect.github.com/deining) in
[https://github.com/spf13/viper/pull/1965](https://redirect.github.com/spf13/viper/pull/1965)
- docs: add update instructions for 1.20 by
[@&#8203;sagikazarmark](https://redirect.github.com/sagikazarmark) in
[https://github.com/spf13/viper/pull/1992](https://redirect.github.com/spf13/viper/pull/1992)

#### New Contributors

- [@&#8203;obs-gh-alexlew](https://redirect.github.com/obs-gh-alexlew)
made their first contribution in
[https://github.com/spf13/viper/pull/1887](https://redirect.github.com/spf13/viper/pull/1887)
- [@&#8203;martinconic](https://redirect.github.com/martinconic) made
their first contribution in
[https://github.com/spf13/viper/pull/1894](https://redirect.github.com/spf13/viper/pull/1894)
- [@&#8203;deining](https://redirect.github.com/deining) made their
first contribution in
[https://github.com/spf13/viper/pull/1965](https://redirect.github.com/spf13/viper/pull/1965)

**Full Changelog**:
https://github.com/spf13/viper/compare/v1.19.0...v1.20.0

</details>

<details>
<summary>golang/go (go)</summary>

###
[`v1.24.4`](https://redirect.github.com/golang/go/compare/go1.24.3...go1.24.4)

###
[`v1.24.3`](https://redirect.github.com/golang/go/compare/go1.24.2...go1.24.3)

</details>

<details>
<summary>open-telemetry/opentelemetry-go
(go.opentelemetry.io/otel)</summary>

###
[`v1.37.0`](https://redirect.github.com/open-telemetry/opentelemetry-go/releases/tag/v1.37.0):
/v0.59.0/v0.13.0

[Compare
Source](https://redirect.github.com/open-telemetry/opentelemetry-go/compare/v1.36.0...v1.37.0)

##### Added

- The `go.opentelemetry.io/otel/semconv/v1.33.0` package.
The package contains semantic conventions from the `v1.33.0` version of
the OpenTelemetry Semantic Conventions.
See the [migration documentation](./semconv/v1.33.0/MIGRATION.md) for
information on how to upgrade from
`go.opentelemetry.io/otel/semconv/v1.32.0.`([#&#8203;6799](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6799))
- The `go.opentelemetry.io/otel/semconv/v1.34.0` package.
The package contains semantic conventions from the `v1.34.0` version of
the OpenTelemetry Semantic Conventions.
([#&#8203;6812](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6812))
- Add metric's schema URL as `otel_scope_schema_url` label in
`go.opentelemetry.io/otel/exporters/prometheus`.
([#&#8203;5947](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/5947))
- Add metric's scope attributes as `otel_scope_[attribute]` labels in
`go.opentelemetry.io/otel/exporters/prometheus`.
([#&#8203;5947](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/5947))
- Add `EventName` to `EnabledParameters` in
`go.opentelemetry.io/otel/log`.
([#&#8203;6825](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6825))
- Add `EventName` to `EnabledParameters` in
`go.opentelemetry.io/otel/sdk/log`.
([#&#8203;6825](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6825))
- Changed handling of `go.opentelemetry.io/otel/exporters/prometheus`
metric renaming to add unit suffixes when it doesn't match one of the
pre-defined values in the unit suffix map.
([#&#8203;6839](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6839))

##### Changed

- The semantic conventions have been upgraded from `v1.26.0` to
`v1.34.0` in `go.opentelemetry.io/otel/bridge/opentracing`.
([#&#8203;6827](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6827))
- The semantic conventions have been upgraded from `v1.26.0` to
`v1.34.0` in `go.opentelemetry.io/otel/exporters/zipkin`.
([#&#8203;6829](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6829))
- The semantic conventions have been upgraded from `v1.26.0` to
`v1.34.0` in `go.opentelemetry.io/otel/metric`.
([#&#8203;6832](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6832))
- The semantic conventions have been upgraded from `v1.26.0` to
`v1.34.0` in `go.opentelemetry.io/otel/sdk/resource`.
([#&#8203;6834](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6834))
- The semantic conventions have been upgraded from `v1.26.0` to
`v1.34.0` in `go.opentelemetry.io/otel/sdk/trace`.
([#&#8203;6835](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6835))
- The semantic conventions have been upgraded from `v1.26.0` to
`v1.34.0` in `go.opentelemetry.io/otel/trace`.
([#&#8203;6836](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6836))
- `Record.Resource` now returns `*resource.Resource` instead of
`resource.Resource` in `go.opentelemetry.io/otel/sdk/log`.
([#&#8203;6864](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6864))
- Retry now shows error cause for context timeout in
`go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`,
`go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`,
`go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`,
`go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`,
`go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`,
`go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`.
([#&#8203;6898](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6898))

##### Fixed

- Stop stripping trailing slashes from configured endpoint URL in
`go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`.
([#&#8203;6710](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6710))
- Stop stripping trailing slashes from configured endpoint URL in
`go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`.
([#&#8203;6710](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6710))
- Stop stripping trailing slashes from configured endpoint URL in
`go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
([#&#8203;6710](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6710))
- Stop stripping trailing slashes from configured endpoint URL in
`go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
([#&#8203;6710](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6710))
- Validate exponential histogram scale range for Prometheus
compatibility in `go.opentelemetry.io/otel/exporters/prometheus`.
([#&#8203;6822](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6822))
- Context cancellation during metric pipeline produce does not corrupt
data in `go.opentelemetry.io/otel/sdk/metric`.
([#&#8203;6914](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6914))

##### Removed

- `go.opentelemetry.io/otel/exporters/prometheus` no longer exports
`otel_scope_info` metric.
([#&#8203;6770](https://redirect.github.com/open-telemetry/opentelemetry-go/issues/6770))

#### What's Changed

- Fix dependencies to unreleased sdk/logtest by
[@&#8203;dmathieu](https://redirect.github.com/dmathieu) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6800](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6800)
- Release experimental logs 0.12.1 by
[@&#8203;dmathieu](https://redirect.github.com/dmathieu) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6802](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6802)
- Fix broken link in changelog by
[@&#8203;MrAlias](https://redirect.github.com/MrAlias) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6805](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6805)
- Retract v0.12.0 for log exporters by
[@&#8203;MrAlias](https://redirect.github.com/MrAlias) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6804](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6804)
- chore(deps): update python:3.13.3-slim-bullseye docker digest to
[`45338d2`](https://redirect.github.com/open-telemetry/opentelemetry-go/commit/45338d2)
by [@&#8203;renovate](https://redirect.github.com/renovate) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6807](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6807)
- remove internal/matchers by
[@&#8203;codeimmortal](https://redirect.github.com/codeimmortal) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6777](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6777)
- Release log/v0.12.2 by
[@&#8203;MrAlias](https://redirect.github.com/MrAlias) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6806](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6806)
- chore(deps): update python:3.13.3-slim-bullseye docker digest to
[`f0acec6`](https://redirect.github.com/open-telemetry/opentelemetry-go/commit/f0acec6)
by [@&#8203;renovate](https://redirect.github.com/renovate) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6810](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6810)
- Update the required approvals policy by
[@&#8203;MrAlias](https://redirect.github.com/MrAlias) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6783](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6783)
- Generate `semconv/v1.33.0` by
[@&#8203;MrAlias](https://redirect.github.com/MrAlias) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6799](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6799)
- chore(deps): update module github.com/jgautheron/goconst to v1.8.2 by
[@&#8203;renovate](https://redirect.github.com/renovate) in
[https://github.com/open-telemetry/opentelemetry-go/pull/6815](https://redirect.github.com/open-telemetry/opentelemetry-go/pull/6815)
- chore(deps): update module

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/flagd).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNy4yIiwidXBkYXRlZEluVmVyIjoiNDEuMTcuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUiXX0=-->

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 16:10:16 -04:00
Todd Baert 7a7977555d
Update renovate.json
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-07-04 14:59:00 -04:00
Brianna Bland 07a45d9b22
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>
2025-07-03 15:36:07 -04:00
Todd Baert 1ccc07e98d
docs: adr for fractional (#1640)
Adds ADR for fractional.

_As written_ this is historical, however, we can modify this one in this
PR if you would like. I know @tangenti @dominikhaska @cupofcat are
interested in some changes here. Feel free to suggest them and if we
have a consensus we can turn the changes into roadmap items.

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-06-26 16:09:45 -04:00
Todd Baert 607290951d
docs: add flag config adr (#1637)
Another "retrospective" ADR, like the cucumber one.

This explains our JSONLogic choice, shared `$evaluators`, as well as the
the semantics of flagd's use of OpenFeature's [resolution
reasons](https://openfeature.dev/specification/types#resolution-reason).

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-26 16:09:25 -04:00
Michael Beemer c90c91f730
docs: refines the code default adr (#1648)
## This PR

- updates the code default ADR to use `FLAG_NOT_FOUND` errors codes
instead of an undefined value.

### Notes

This update will have nearly identical end results but will be easier to
implement and more consistent. Core to this change is switching from
using an empty value as the indicator to using the `FLAG_NOT_FOUND`
error code. While this may seem odd at first, it's in line with the
behavior a user sees when first releasing a disabled flag. This will
allow users to `create a new disabled flag` -> `enable the flag for a
subset of users without impacting others` -> `enable the flag for
everyone` -> `deprecate the flag`.

---------

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-24 19:23:10 -04:00
Michael Beemer 0642ac8c70
docs: add code default adr (#1639)
## This PR

- ADR to add support for explicitly deferring to the code default.

### Notes

This has been a challenge for users because enabling a feature for
targeting requires a default variant to be used, which may not match the
default used in code.

---------

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-24 12:23:15 -04:00
github-actions[bot] 23e15540a7
chore: release main (#1643)
🤖 I have created a release *beep* *boop*
---


<details><summary>flagd: 0.12.5</summary>

##
[0.12.5](https://github.com/open-feature/flagd/compare/flagd/v0.12.4...flagd/v0.12.5)
(2025-06-13)


###  New Features

* add server-side deadline to sync service
([#1638](https://github.com/open-feature/flagd/issues/1638))
([b70fa06](b70fa06b66))
* updating context using headers
([#1641](https://github.com/open-feature/flagd/issues/1641))
([ba34815](ba348152b6))
</details>

<details><summary>core: 0.11.5</summary>

##
[0.11.5](https://github.com/open-feature/flagd/compare/core/v0.11.4...core/v0.11.5)
(2025-06-13)


###  New Features

* add server-side deadline to sync service
([#1638](https://github.com/open-feature/flagd/issues/1638))
([b70fa06](b70fa06b66))
* updating context using headers
([#1641](https://github.com/open-feature/flagd/issues/1641))
([ba34815](ba348152b6))
</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 <109696520+openfeaturebot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-19 11:04:29 -04:00
alexandraoberaigner b70fa06b66
feat: add server-side deadline to sync service (#1638)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

## This PR
<!-- add the description of the PR here -->

- adds server side deadline for sync and event streams configurable via
cmd argument `--stream-deadline`

### Related Issues

#1582

### Notes
<!-- any additional notes for this PR -->


### How to test

1. Run flagd with `--stream-deadline 3s` // 3s can be replaced with any
duration the deadline should have
2. Test Event Stream deadline: run `grpcurl -v --proto
schemas/protobuf/flagd/evaluation/v1/evaluation.proto -plaintext
localhost:8013 flagd.evaluation.v1.Service/EventStream` or similar
depending on your flagd settings to check if the deadline exceeded is
returned after the specified duration
3. Test Sync Service Stream deadline: run `grpcurl -v --proto
schemas/protobuf/flagd/sync/v1/sync.proto -plaintext localhost:8015
flagd.sync.v1.FlagSyncService/SyncFlags` or similar depending on your
flagd settings to check if the deadline exceeded is returned after the
specified duration

Signed-off-by: alexandra.oberaigner <alexandra.oberaigner@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-06-13 15:50:48 -04:00
Rahul Baradol ba348152b6
feat: updating context using headers (#1641)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

Able to update context map using headers present in 
- OFREP requests
- Connect Requests (via Flag Evaluator V2 service)

### Related Issues
Fixes #1583 

### Notes
Context values passed via headers is high priority

If same context key is updated via
- Headers
- Request Body
- Static Config

_Context via Headers will be considered_

### Usage 
```
flagd start --port 8013 --uri file:./samples/example_flags.flagd.json -H Header=contextKey
```
or
```
flagd start --port 8013 --uri file:./samples/example_flags.flagd.json --context-from-header Header=contextKey
```

---------

Signed-off-by: Rahul Baradol <rahul.baradol.14@gmail.com>
2025-06-13 10:02:35 -04:00
Todd Baert bf10ff30dc
docs(ADR): add gherkin ADR (#1631)
Adds an ADR based on the new template, explaining the reasoning behind
the use of cucumber/gherkin.

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: André Silva <2493377+askpt@users.noreply.github.com>
2025-06-05 14:52:57 -04:00
github-actions[bot] cb2b8eeb9c
chore: release main (#1605)
🤖 I have created a release *beep* *boop*
---


<details><summary>flagd: 0.12.4</summary>

##
[0.12.4](https://github.com/open-feature/flagd/compare/flagd/v0.12.3...flagd/v0.12.4)
(2025-05-28)


### 🐛 Bug Fixes

* Bump OpenTelemetry instrumentation dependencies
([#1616](https://github.com/open-feature/flagd/issues/1616))
([11db29d](11db29dc3a))


###  New Features

* add traces to ofrep endpoint
([#1593](https://github.com/open-feature/flagd/issues/1593))
([a5d43bc](a5d43bc1de))


### 🧹 Chore

* **deps:** update dependency go to v1.24.1
([#1559](https://github.com/open-feature/flagd/issues/1559))
([cd46044](cd4604471b))
* **security:** upgrade dependency versions
([#1632](https://github.com/open-feature/flagd/issues/1632))
([761d870](761d870a3c))
</details>

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

##
[0.7.4](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.3...flagd-proxy/v0.7.4)
(2025-05-28)


### 🧹 Chore

* **deps:** update dependency go to v1.24.1
([#1559](https://github.com/open-feature/flagd/issues/1559))
([cd46044](cd4604471b))
* **security:** upgrade dependency versions
([#1632](https://github.com/open-feature/flagd/issues/1632))
([761d870](761d870a3c))
</details>

<details><summary>core: 0.11.4</summary>

##
[0.11.4](https://github.com/open-feature/flagd/compare/core/v0.11.3...core/v0.11.4)
(2025-05-28)


### 🐛 Bug Fixes

* incorrect comparison used for time
([#1608](https://github.com/open-feature/flagd/issues/1608))
([8c5ac2f](8c5ac2f2c3))


### 🧹 Chore

* **deps:** update dependency go to v1.24.1
([#1559](https://github.com/open-feature/flagd/issues/1559))
([cd46044](cd4604471b))
* **security:** upgrade dependency versions
([#1632](https://github.com/open-feature/flagd/issues/1632))
([761d870](761d870a3c))


### 🔄 Refactoring

* Refactor the cron function in http sync
([#1600](https://github.com/open-feature/flagd/issues/1600))
([babcacf](babcacfe4d))
* removed hardcoded metric export interval and use otel default
([#1621](https://github.com/open-feature/flagd/issues/1621))
([81c66eb](81c66ebf2b))
</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 <109696520+openfeaturebot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-02 15:47:43 -04:00
i-m-addycoder 761d870a3c
chore(security): upgrade dependency versions (#1632)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

## This PR
<!-- add the description of the PR here -->

- upgrades 2 dependency versions to fix CVEs

### Notes
<!-- any additional notes for this PR -->

Dependency upgrades and corresponding CVEs
- github.com/golang-jwt/jwt/v5 | v5.2.1 -
https://www.cve.org/CVERecord?id=CVE-2025-30204 - `go get
github.com/golang-jwt/jwt/v5@v5.2.2` in core, flagd and flagd-proxy
directories
- golang.org/x/oauth2/jws | v0.25.0 and v0.26.0 -
https://www.cve.org/CVERecord?id=CVE-2025-22868 - `go get
golang.org/x/oauth2@v0.27.0` in core, flagd and flagd-proxy directories

### How to test
<!-- if applicable, add testing instructions under this section -->

1. `make workspace-init`
2. `make test`

Signed-off-by: Aditya Thakur <aditya.t.01011@gmail.com>
2025-05-28 15:44:01 -04:00
Michael Beemer 7566f518c8
docs(ADR): add architecture decision template (#1630)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-05-15 14:09:08 -04:00
Michael Beemer 81c66ebf2b
refactor: removed hardcoded metric export interval and use otel default (#1621)
Signed-off-by: Michael Beemer <michael.beemer@dynatrace.com>
2025-05-06 23:29:06 -04:00
Kyle e8d6d6d892
docs: improve content tab anchors on getting started (#1628)
Signed-off-by: Kyle Julian <38759683+kylejuliandev@users.noreply.github.com>
2025-04-29 14:09:53 -04:00
Kyle e0cf061cb8
docs: add documentation on running flagd within docker-compose (#1626)
Signed-off-by: Kyle Julian <38759683+kylejuliandev@users.noreply.github.com>
2025-04-28 14:38:39 -04:00
Todd Baert 3887a53c49
chore: use publish env
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
2025-04-16 12:42:45 -04:00
Michael Beemer f94ebeed3e
docs: clarify that bucketing keys are strings (#1619)
Signed-off-by: Michael Beemer <michael.beemer@dynatrace.com>
2025-04-10 11:03:31 +02:00
Simon Schrottner a5d43bc1de
feat: add traces to ofrep endpoint (#1593)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-04-10 09:19:15 +02:00
Jorge Creixell 11db29dc3a
fix: Bump OpenTelemetry instrumentation dependencies (#1616)
Signed-off-by: Jorge Creixell <jorge.creixell@grafana.com>
2025-04-08 23:05:23 +02:00
Simon Schrottner ac52674454
chore: add global maintainers to the codeowners (#1617)
global maintainers approvals etc should have the same effect as the one
of the flagd maintainers

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
2025-04-05 17:16:28 +01:00
Zhiwei Liang babcacfe4d
refactor: Refactor the cron function in http sync (#1600)
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

## This PR
Refactor the cron function in the `Sync` function for the HTTP sync.
This might improve the performance as well because it reduces the number
of HTTP requests.

Signed-off-by: Zhiwei Liang <zhiwei.liang27@pm.me>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-27 16:02:48 -04:00
Matthew Wilson 8c5ac2f2c3
fix: incorrect comparison used for time (#1608)
Signed-off-by: Matthew Wilson <54033231+wilson-matthew@users.noreply.github.com>
2025-03-27 14:28:26 -04:00
Michael Beemer ddfa0203ce
docs: add providerId as a configuration option (#1567)
Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
2025-03-27 12:28:07 -04:00
renovate[bot] cd4604471b
chore(deps): update dependency go to v1.24.1 (#1559)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | toolchain | minor |
`1.22.9` -> `1.24.1` |

---

### Release Notes

<details>
<summary>golang/go (go)</summary>

###
[`v1.24.1`](https://redirect.github.com/golang/go/compare/go1.24.0...go1.24.1)

###
[`v1.24.0`](https://redirect.github.com/golang/go/compare/go1.23.6...go1.24.0)

###
[`v1.23.7`](https://redirect.github.com/golang/go/compare/go1.23.6...go1.23.7)

###
[`v1.23.6`](https://redirect.github.com/golang/go/compare/go1.23.5...go1.23.6)

###
[`v1.23.5`](https://redirect.github.com/golang/go/compare/go1.23.4...go1.23.5)

###
[`v1.23.4`](https://redirect.github.com/golang/go/compare/go1.23.3...go1.23.4)

###
[`v1.23.3`](https://redirect.github.com/golang/go/compare/go1.23.2...go1.23.3)

###
[`v1.23.2`](https://redirect.github.com/golang/go/compare/go1.23.1...go1.23.2)

###
[`v1.23.1`](https://redirect.github.com/golang/go/compare/go1.23.0...go1.23.1)

###
[`v1.23.0`](https://redirect.github.com/golang/go/compare/go1.22.12...go1.23.0)

###
[`v1.22.12`](https://redirect.github.com/golang/go/compare/go1.22.11...go1.22.12)

###
[`v1.22.11`](https://redirect.github.com/golang/go/compare/go1.22.10...go1.22.11)

###
[`v1.22.10`](https://redirect.github.com/golang/go/compare/go1.22.9...go1.22.10)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/open-feature/flagd).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNjQuMSIsInVwZGF0ZWRJblZlciI6IjM5LjIwMC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Simon Schrottner <simon.schrottner@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
2025-03-25 14:55:03 -04:00
104 changed files with 8071 additions and 3740 deletions

View File

@ -63,6 +63,7 @@ jobs:
container-release:
name: Build and push containers to GHCR
needs: release-please
environment: publish
runs-on: ubuntu-latest
if: ${{ needs.release-please.outputs.items_to_publish != '' && toJson(fromJson(needs.release-please.outputs.items_to_publish)) != '[]' }}
strategy:

5
.gitignore vendored
View File

@ -20,4 +20,7 @@ site
.cache/
# coverage results
*coverage.out
*coverage.out
# benchmark results
benchmark.txt

30
.golangci.bck.yml Normal file
View File

@ -0,0 +1,30 @@
run:
timeout: 3m
linters-settings:
funlen:
statements: 50
golint:
min-confidence: 0.6
enable-all: true
issues:
exclude:
- pkg/generated
exclude-rules:
- path: _test.go
linters:
- funlen
- maligned
- noctx
- scopelint
- bodyclose
- lll
- goconst
- gocognit
- gocyclo
- dupl
- staticcheck
exclude-dirs:
- (^|/)bin($|/)
- (^|/)examples($|/)
- (^|/)schemas($|/)
- (^|/)test-harness($|/)

View File

@ -1,67 +1,43 @@
run:
skip-dirs:
- (^|/)bin($|/)
- (^|/)examples($|/)
- (^|/)schemas($|/)
- (^|/)test-harness($|/)
version: "2"
linters:
enable:
- asciicheck
- asasalint
- bidichk
- bodyclose
- contextcheck
- dogsled
- dupl
- dupword
- durationcheck
- errchkjson
- exhaustive
- funlen
- gci
- goconst
- gocritic
- gocyclo
- interfacebloat
- gosec
- lll
- misspell
- nakedret
- nilerr
- nilnil
- noctx
- nosprintfhostport
- prealloc
- promlinter
- revive
- rowserrcheck
- exportloopref
- stylecheck
- unconvert
- unparam
- whitespace
- wrapcheck
- gofumpt
- tenv
linters-settings:
funlen:
statements: 50
golint:
min-confidence: 0.6
issues:
exclude:
- pkg/generated
exclude-rules:
- path: _test.go
linters:
- funlen
- maligned
- noctx
- scopelint
- bodyclose
- lll
- goconst
- gocognit
- gocyclo
- dupl
- staticcheck
settings:
funlen:
statements: 50
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- bodyclose
- dupl
- funlen
- gocognit
- goconst
- gocyclo
- lll
- maligned
- noctx
- scopelint
- staticcheck
path: _test.go
- path: (.+)\.go$
text: pkg/generated
paths:
- (^|/)bin($|/)
- (^|/)examples($|/)
- (^|/)schemas($|/)
- (^|/)test-harness($|/)
- third_party$
- builtin$
- examples$
formatters:
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View File

@ -13,6 +13,7 @@ config:
max-one-sentence-per-line: true
code-block-style: false # not compatible with mkdocs "details" panes
no-alt-text: false
descriptive-link-text: false
MD007:
indent: 4

View File

@ -1,5 +1,5 @@
{
"flagd": "0.12.3",
"flagd-proxy": "0.7.3",
"core": "0.11.3"
"flagd": "0.12.9",
"flagd-proxy": "0.8.0",
"core": "0.12.1"
}

View File

@ -3,4 +3,4 @@
#
# Managed by Peribolos: https://github.com/open-feature/community/blob/main/config/open-feature/cloud-native/workgroup.yaml
#
* @open-feature/cloud-native-maintainers
* @open-feature/flagd-maintainers @open-feature/maintainers

View File

@ -8,6 +8,24 @@ TLDR: be respectful.
Any contributions are expected to include unit tests.
These can be validated with `make test` or the automated github workflow will run them on PR creation.
## Development
### Prerequisites
You'll need:
- Go
- make
- docker
You'll want:
- curl (for calling HTTP endpoints)
- [grpcurl](https://github.com/fullstorydev/grpcurl) (for making gRPC calls)
- jq (for pretty printing responses)
### Workspace Initialization
This project uses a go workspace, to setup the project run
```shell
@ -22,6 +40,70 @@ The project uses remote buf packages, changing the remote generation source will
export GOPRIVATE=buf.build/gen/go
```
### Manual testing
flagd has a number of interfaces (you can read more about them at [flagd.dev](https://flagd.dev/)) which can be used to evaluate flags, or deliver flag configurations so that they can be evaluated by _in-process_ providers.
You can manually test this functionality by starting flagd (from the flagd/ directory) with `go run main.go start -f file:../config/samples/example_flags.flagd.json`.
NOTE: you will need `go, curl`
#### Remote single flag evaluation via HTTP1.1/Connect
```sh
# evaluates a single boolean flag
curl -X POST -d '{"flagKey":"myBoolFlag","context":{}}' -H "Content-Type: application/json" "http://localhost:8013/flagd.evaluation.v1.Service/ResolveBoolean" | jq
```
#### Remote single flag evaluation via HTTP1.1/OFREP
```sh
# evaluates a single boolean flag
curl -X POST -d '{"context":{}}' 'http://localhost:8016/ofrep/v1/evaluate/flags/myBoolFlag' | jq
```
#### Remote single flag evaluation via gRPC
```sh
# evaluates a single boolean flag
grpcurl -import-path schemas/protobuf/flagd/evaluation/v1/ -proto evaluation.proto -plaintext -d '{"flagKey":"myBoolFlag"}' localhost:8013 flagd.evaluation.v1.Service/ResolveBoolean | jq
```
#### Remote bulk evaluation via HTTP1.1/OFREP
```sh
# evaluates flags in bulk
curl -X POST -d '{"context":{}}' 'http://localhost:8016/ofrep/v1/evaluate/flags' | jq
```
#### Remote bulk evaluation via gRPC
```sh
# evaluates flags in bulk
grpcurl -import-path schemas/protobuf/flagd/evaluation/v1/ -proto evaluation.proto -plaintext -d '{}' localhost:8013 flagd.evaluation.v1.Service/ResolveAll | jq
```
#### Remote event streaming via gRPC
```sh
# notifies of flag changes (but does not evaluate)
grpcurl -import-path schemas/protobuf/flagd/evaluation/v1/ -proto evaluation.proto -plaintext -d '{}' localhost:8013 flagd.evaluation.v1.Service/EventStream
```
#### Flag configuration fetch via gRPC
```sh
# sends back a representation of all flags
grpcurl -import-path schemas/protobuf/flagd/sync/v1/ -proto sync.proto -plaintext localhost:8015 flagd.sync.v1.FlagSyncService/FetchAllFlags | jq
```
#### Flag synchronization stream via gRPC
```sh
# will open a persistent stream which sends flag changes when the watched source is modified
grpcurl -import-path schemas/protobuf/flagd/sync/v1/ -proto sync.proto -plaintext localhost:8015 flagd.sync.v1.FlagSyncService/SyncFlags | jq
```
## DCO Sign-Off
A DCO (Developer Certificate of Origin) sign-off is a line placed at the end of

View File

@ -47,12 +47,19 @@ test-flagd:
go test -race -covermode=atomic -cover -short ./flagd/pkg/... -coverprofile=flagd-coverage.out
test-flagd-proxy:
go test -race -covermode=atomic -cover -short ./flagd-proxy/pkg/... -coverprofile=flagd-proxy-coverage.out
flagd-integration-test: # dependent on ./bin/flagd start -f file:test-harness/flags/testing-flags.json -f file:test-harness/flags/custom-ops.json -f file:test-harness/flags/evaluator-refs.json -f file:test-harness/flags/zero-flags.json
go test -cover ./test/integration $(ARGS)
flagd-benchmark-test:
go test -bench=Bench -short -benchtime=5s -benchmem ./core/... | tee benchmark.txt
flagd-integration-test-harness:
# target used to start a locally built flagd with the e2e flags
cd flagd; go run main.go start -f file:../test-harness/flags/testing-flags.json -f file:../test-harness/flags/custom-ops.json -f file:../test-harness/flags/evaluator-refs.json -f file:../test-harness/flags/zero-flags.json -f file:../test-harness/flags/edge-case-flags.json
flagd-integration-test: # dependent on flagd-e2e-test-harness if not running in github actions
go test -count=1 -cover ./test/integration $(ARGS)
run: # default to flagd
make run-flagd
run-flagd:
cd flagd; go run main.go start -f file:../config/samples/example_flags.flagd.json
cd flagd; go run main.go start -f file:../config/samples/example_flags.flagd.json
run-flagd-selector-demo:
cd flagd; go run main.go start -f file:../config/samples/example_flags.flagd.json -f file:../config/samples/example_flags.flagd.2.json
install:
cp systemd/flagd.service /etc/systemd/system/flagd.service
mkdir -p /etc/flagd
@ -66,11 +73,11 @@ uninstall:
rm /etc/systemd/system/flagd.service
rm -f $(DESTDIR)$(PREFIX)/bin/flagd
lint:
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
$(foreach module, $(ALL_GO_MOD_DIRS), ${GOPATH}/bin/golangci-lint run --deadline=5m --timeout=5m $(module)/... || exit;)
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.2.1
$(foreach module, $(ALL_GO_MOD_DIRS), ${GOPATH}/bin/golangci-lint run $(module)/...;)
lint-fix:
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
$(foreach module, $(ALL_GO_MOD_DIRS), ${GOPATH}/bin/golangci-lint run --fix --deadline=5m --timeout=5m $(module)/... || exit;)
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.2.1
$(foreach module, $(ALL_GO_MOD_DIRS), ${GOPATH}/bin/golangci-lint run --fix $(module)/...;)
install-mockgen:
go install go.uber.org/mock/mockgen@v0.4.0
mockgen: install-mockgen

72
benchmark.txt Normal file
View File

@ -0,0 +1,72 @@
PASS
ok github.com/open-feature/flagd/core/pkg/certreloader 15.986s
goos: linux
goarch: amd64
pkg: github.com/open-feature/flagd/core/pkg/evaluator
cpu: 11th Gen Intel(R) Core(TM) i9-11950H @ 2.60GHz
BenchmarkFractionalEvaluation/test_a@faas.com-16 423930 13316 ns/op 7229 B/op 135 allocs/op
BenchmarkFractionalEvaluation/test_b@faas.com-16 469594 13677 ns/op 7229 B/op 135 allocs/op
BenchmarkFractionalEvaluation/test_c@faas.com-16 569103 13286 ns/op 7229 B/op 135 allocs/op
BenchmarkFractionalEvaluation/test_d@faas.com-16 412386 13023 ns/op 7229 B/op 135 allocs/op
BenchmarkResolveBooleanValue/test_staticBoolFlag-16 3106903 1792 ns/op 1008 B/op 11 allocs/op
BenchmarkResolveBooleanValue/test_targetingBoolFlag-16 448164 11250 ns/op 6065 B/op 87 allocs/op
BenchmarkResolveBooleanValue/test_staticObjectFlag-16 3958750 1476 ns/op 1008 B/op 11 allocs/op
BenchmarkResolveBooleanValue/test_missingFlag-16 5331808 1353 ns/op 784 B/op 12 allocs/op
BenchmarkResolveBooleanValue/test_disabledFlag-16 4530751 1301 ns/op 1072 B/op 13 allocs/op
BenchmarkResolveStringValue/test_staticStringFlag-16 4583056 1525 ns/op 1040 B/op 13 allocs/op
BenchmarkResolveStringValue/test_targetingStringFlag-16 839954 10388 ns/op 6097 B/op 89 allocs/op
BenchmarkResolveStringValue/test_staticObjectFlag-16 4252830 1677 ns/op 1008 B/op 11 allocs/op
BenchmarkResolveStringValue/test_missingFlag-16 3743324 1495 ns/op 784 B/op 12 allocs/op
BenchmarkResolveStringValue/test_disabledFlag-16 3495699 1709 ns/op 1072 B/op 13 allocs/op
BenchmarkResolveFloatValue/test:_staticFloatFlag-16 4382868 1511 ns/op 1024 B/op 13 allocs/op
BenchmarkResolveFloatValue/test:_targetingFloatFlag-16 867987 10344 ns/op 6081 B/op 89 allocs/op
BenchmarkResolveFloatValue/test:_staticObjectFlag-16 3913120 1695 ns/op 1008 B/op 11 allocs/op
BenchmarkResolveFloatValue/test:_missingFlag-16 3910468 1349 ns/op 784 B/op 12 allocs/op
BenchmarkResolveFloatValue/test:_disabledFlag-16 3642919 1666 ns/op 1072 B/op 13 allocs/op
BenchmarkResolveIntValue/test_staticIntFlag-16 4077288 1349 ns/op 1008 B/op 11 allocs/op
BenchmarkResolveIntValue/test_targetingNumberFlag-16 922383 7601 ns/op 6065 B/op 87 allocs/op
BenchmarkResolveIntValue/test_staticObjectFlag-16 4995128 1229 ns/op 1008 B/op 11 allocs/op
BenchmarkResolveIntValue/test_missingFlag-16 5574153 1274 ns/op 768 B/op 12 allocs/op
BenchmarkResolveIntValue/test_disabledFlag-16 3633708 1734 ns/op 1072 B/op 13 allocs/op
BenchmarkResolveObjectValue/test_staticObjectFlag-16 1624102 4559 ns/op 2243 B/op 37 allocs/op
BenchmarkResolveObjectValue/test_targetingObjectFlag-16 443880 11995 ns/op 7283 B/op 109 allocs/op
BenchmarkResolveObjectValue/test_staticBoolFlag-16 3462445 1665 ns/op 1008 B/op 11 allocs/op
BenchmarkResolveObjectValue/test_missingFlag-16 4207567 1458 ns/op 784 B/op 12 allocs/op
BenchmarkResolveObjectValue/test_disabledFlag-16 3407262 1848 ns/op 1072 B/op 13 allocs/op
PASS
ok github.com/open-feature/flagd/core/pkg/evaluator 239.506s
? github.com/open-feature/flagd/core/pkg/evaluator/mock [no test files]
PASS
ok github.com/open-feature/flagd/core/pkg/logger 0.003s
? github.com/open-feature/flagd/core/pkg/model [no test files]
? github.com/open-feature/flagd/core/pkg/service [no test files]
PASS
ok github.com/open-feature/flagd/core/pkg/service/ofrep 0.002s
PASS
ok github.com/open-feature/flagd/core/pkg/store 0.003s
? github.com/open-feature/flagd/core/pkg/sync [no test files]
PASS
ok github.com/open-feature/flagd/core/pkg/sync/blob 0.016s
PASS
ok github.com/open-feature/flagd/core/pkg/sync/builder 0.018s
? github.com/open-feature/flagd/core/pkg/sync/builder/mock [no test files]
PASS
ok github.com/open-feature/flagd/core/pkg/sync/file 1.007s
PASS
ok github.com/open-feature/flagd/core/pkg/sync/grpc 8.011s
PASS
ok github.com/open-feature/flagd/core/pkg/sync/grpc/credentials 0.008s
? github.com/open-feature/flagd/core/pkg/sync/grpc/credentials/mock [no test files]
? github.com/open-feature/flagd/core/pkg/sync/grpc/mock [no test files]
PASS
ok github.com/open-feature/flagd/core/pkg/sync/grpc/nameresolvers 0.002s
PASS
ok github.com/open-feature/flagd/core/pkg/sync/http 4.006s
? github.com/open-feature/flagd/core/pkg/sync/http/mock [no test files]
PASS
ok github.com/open-feature/flagd/core/pkg/sync/kubernetes 0.016s
? github.com/open-feature/flagd/core/pkg/sync/testing [no test files]
PASS
ok github.com/open-feature/flagd/core/pkg/telemetry 0.016s
PASS
ok github.com/open-feature/flagd/core/pkg/utils 0.002s

View File

@ -0,0 +1,17 @@
{
"$schema": "https://flagd.dev/schema/v0/flags.json",
"metadata": {
"flagSetId": "other",
"version": "v1"
},
"flags": {
"myStringFlag": {
"state": "ENABLED",
"variants": {
"dupe1": "dupe1",
"dupe2": "dupe2"
},
"defaultVariant": "dupe1"
}
}
}

View File

@ -1,5 +1,79 @@
# Changelog
## [0.12.1](https://github.com/open-feature/flagd/compare/core/v0.12.0...core/v0.12.1) (2025-07-28)
### 🧹 Chore
* add back file-delete test ([#1694](https://github.com/open-feature/flagd/issues/1694)) ([750aa17](https://github.com/open-feature/flagd/commit/750aa176b5a8dd24a9daaff985ff6efeb084c758))
* fix benchmark ([#1698](https://github.com/open-feature/flagd/issues/1698)) ([5e2d7d7](https://github.com/open-feature/flagd/commit/5e2d7d7176ba05e667cd92acd7decb531a8de2f6))
## [0.12.0](https://github.com/open-feature/flagd/compare/core/v0.11.8...core/v0.12.0) (2025-07-21)
### ⚠ BREAKING CHANGES
* remove sync.Type ([#1691](https://github.com/open-feature/flagd/issues/1691))
### 🐛 Bug Fixes
* update to latest otel semconv ([#1668](https://github.com/open-feature/flagd/issues/1668)) ([81855d7](https://github.com/open-feature/flagd/commit/81855d76f94a09251a19a05f830cc1d11ab6b566))
### ✨ New Features
* Add support for HTTP eTag header and 304 no change response ([#1645](https://github.com/open-feature/flagd/issues/1645)) ([ea3be4f](https://github.com/open-feature/flagd/commit/ea3be4f9010644132795bb60b36fb7705f901b62))
* remove sync.Type ([#1691](https://github.com/open-feature/flagd/issues/1691)) ([ac647e0](https://github.com/open-feature/flagd/commit/ac647e065636071f5bc065a9a084461cea692166))
## [0.11.8](https://github.com/open-feature/flagd/compare/core/v0.11.7...core/v0.11.8) (2025-07-15)
### 🧹 Chore
* **deps:** update github.com/open-feature/flagd-schemas digest to 08b4c52 ([#1682](https://github.com/open-feature/flagd/issues/1682)) ([68d04e2](https://github.com/open-feature/flagd/commit/68d04e21e63c63d6054fcd6aebfb864e8b3a597e))
## [0.11.7](https://github.com/open-feature/flagd/compare/core/v0.11.6...core/v0.11.7) (2025-07-15)
### 🐛 Bug Fixes
* general err if targeting variant not in variants ([#1680](https://github.com/open-feature/flagd/issues/1680)) ([6cabfc8](https://github.com/open-feature/flagd/commit/6cabfc8ff3bd4ad69699a72724495e84cdec0cc3))
## [0.11.6](https://github.com/open-feature/flagd/compare/core/v0.11.5...core/v0.11.6) (2025-07-10)
### ✨ New Features
* add sync_context to SyncFlags ([#1642](https://github.com/open-feature/flagd/issues/1642)) ([07a45d9](https://github.com/open-feature/flagd/commit/07a45d9b2275584fa92ff33cbe5e5c7d7864db38))
* allowing null/missing defaultValue ([#1659](https://github.com/open-feature/flagd/issues/1659)) ([3f6b78c](https://github.com/open-feature/flagd/commit/3f6b78c8ccab75e9c07d26741c4b206fd0b722ee))
## [0.11.5](https://github.com/open-feature/flagd/compare/core/v0.11.4...core/v0.11.5) (2025-06-13)
### ✨ New Features
* add server-side deadline to sync service ([#1638](https://github.com/open-feature/flagd/issues/1638)) ([b70fa06](https://github.com/open-feature/flagd/commit/b70fa06b66e1fe8a28728441a7ccd28c6fe6a0c6))
* updating context using headers ([#1641](https://github.com/open-feature/flagd/issues/1641)) ([ba34815](https://github.com/open-feature/flagd/commit/ba348152b6e7b6bd7473bb11846aac7db316c88e))
## [0.11.4](https://github.com/open-feature/flagd/compare/core/v0.11.3...core/v0.11.4) (2025-05-28)
### 🐛 Bug Fixes
* incorrect comparison used for time ([#1608](https://github.com/open-feature/flagd/issues/1608)) ([8c5ac2f](https://github.com/open-feature/flagd/commit/8c5ac2f2c31e092cbe6ddb4d3c1adeeeb04e9ef9))
### 🧹 Chore
* **deps:** update dependency go to v1.24.1 ([#1559](https://github.com/open-feature/flagd/issues/1559)) ([cd46044](https://github.com/open-feature/flagd/commit/cd4604471bba0a1df67bf87653a38df3caf9d20f))
* **security:** upgrade dependency versions ([#1632](https://github.com/open-feature/flagd/issues/1632)) ([761d870](https://github.com/open-feature/flagd/commit/761d870a3c563b8eb1b83ee543b41316c98a1d48))
### 🔄 Refactoring
* Refactor the cron function in http sync ([#1600](https://github.com/open-feature/flagd/issues/1600)) ([babcacf](https://github.com/open-feature/flagd/commit/babcacfe4dd1244dda954823d8a3ed2019c8752b))
* removed hardcoded metric export interval and use otel default ([#1621](https://github.com/open-feature/flagd/issues/1621)) ([81c66eb](https://github.com/open-feature/flagd/commit/81c66ebf2b82fc6874ab325569f52801d5ab8e5e))
## [0.11.3](https://github.com/open-feature/flagd/compare/core/v0.11.2...core/v0.11.3) (2025-03-25)

View File

@ -1,113 +1,118 @@
module github.com/open-feature/flagd/core
go 1.22.7
go 1.24.0
toolchain go1.22.9
toolchain go1.24.4
require (
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1
connectrpc.com/connect v1.18.1
connectrpc.com/otelconnect v0.7.2
github.com/diegoholiveira/jsonlogic/v3 v3.7.4
github.com/fsnotify/fsnotify v1.8.0
github.com/diegoholiveira/jsonlogic/v3 v3.8.4
github.com/fsnotify/fsnotify v1.9.0
github.com/google/go-cmp v0.7.0
github.com/open-feature/flagd-schemas v0.2.9-0.20250319190911-9b0ee43ecc47
github.com/open-feature/open-feature-operator/apis v0.2.44
github.com/prometheus/client_golang v1.21.1
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86
github.com/open-feature/open-feature-operator/apis v0.2.45
github.com/prometheus/client_golang v1.22.0
github.com/robfig/cron v1.2.0
github.com/stretchr/testify v1.10.0
github.com/twmb/murmur3 v1.1.8
github.com/xeipuuv/gojsonschema v1.2.0
github.com/zeebo/xxh3 v1.0.2
go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0
go.opentelemetry.io/otel/exporters/prometheus v0.57.0
go.opentelemetry.io/otel/metric v1.35.0
go.opentelemetry.io/otel/sdk v1.35.0
go.opentelemetry.io/otel/sdk/metric v1.35.0
go.opentelemetry.io/otel/trace v1.35.0
go.uber.org/mock v0.5.0
go.opentelemetry.io/otel v1.37.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0
go.opentelemetry.io/otel/exporters/prometheus v0.59.0
go.opentelemetry.io/otel/metric v1.37.0
go.opentelemetry.io/otel/sdk v1.37.0
go.opentelemetry.io/otel/sdk/metric v1.37.0
go.opentelemetry.io/otel/trace v1.37.0
go.uber.org/mock v0.5.2
go.uber.org/zap v1.27.0
gocloud.dev v0.40.0
golang.org/x/crypto v0.33.0
gocloud.dev v0.42.0
golang.org/x/crypto v0.39.0
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
golang.org/x/mod v0.23.0
golang.org/x/sync v0.11.0
google.golang.org/grpc v1.71.0
golang.org/x/mod v0.25.0
golang.org/x/sync v0.15.0
google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.31.4
k8s.io/client-go v0.31.4
k8s.io/apimachinery v0.33.2
k8s.io/client-go v0.33.2
)
require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/auth v0.8.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.1.13 // indirect
cloud.google.com/go/storage v1.43.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
cel.dev/expr v0.23.0 // indirect
cloud.google.com/go v0.121.1 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/aws/aws-sdk-go v1.55.6 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.12 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/google/wire v0.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@ -116,43 +121,47 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/oauth2 v0.26.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/api v0.191.0 // indirect
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/protobuf v1.36.5 // indirect
google.golang.org/api v0.235.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.31.4 // indirect
k8s.io/apiextensions-apiserver v0.31.0 // indirect
k8s.io/api v0.33.2 // indirect
k8s.io/apiextensions-apiserver v0.31.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/controller-runtime v0.19.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/controller-runtime v0.19.3 // indirect
sigs.k8s.io/gateway-api v1.2.1 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

View File

@ -1,127 +1,153 @@
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2 h1:D3HI5RQbqgffyf+Z77+hReDx5kigFVAKGvttULD9/ms=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2/go.mod h1:b9rfG6rbGXZAlLwQwedvZ0kI0nUcR+aLaYF70pj920E=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.4-20250127221518-be6d1143b690.1 h1:0vXmOkGv8nO5H1W8TkSz+GtZhvD2LNXiQaiucEio6vk=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.4-20250127221518-be6d1143b690.1/go.mod h1:wemFLfCpuNfhrBQ7NwzbtYxbg+IihAYqJcNeS+fLpLI=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1 h1:eZKupK8gUTuc6zifAFQon8Gnt44fR4cd0GnTWjELvEw=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1/go.mod h1:wJvVIADHM0IaBc5sYf8wgMMgSHi0nAtc6rgr5rfizhA=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2 h1:TZ+7u106u7C7lgNctxG03ABliF46eLhcIZG5Mdo67/E=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2/go.mod h1:4u0WLwfkLob3dC/F8qNctqhtiEv2Mlyi8YgCDDzgYDs=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1 h1:LdC4xAuUaNdduzQr5VvhjsgrCfpW9IYxYsjyCF0ANs0=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1/go.mod h1:cCQ49+ttXE2MZ/ciRNb0tCG+F3kj2ZVbP+0/psbhrLY=
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4=
cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus=
cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nTWy3TYE=
cloud.google.com/go/longrunning v0.5.12/go.mod h1:S5hMV8CDJ6r50t2ubVJSKQVv5u0rmik5//KgLO3k4lU=
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY=
connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms=
connectrpc.com/otelconnect v0.7.2 h1:WlnwFzaW64dN06JXU+hREPUGeEzpz3Acz2ACOmN8cMI=
connectrpc.com/otelconnect v0.7.2/go.mod h1:JS7XUKfuJs2adhCnXhNHPHLz6oAaZniCJdSF00OZSew=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc=
github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
github.com/aws/aws-sdk-go-v2/config v1.29.12 h1:Y/2a+jLPrPbHpFkpAAYkVEtJmxORlXoo5k2g1fa2sUo=
github.com/aws/aws-sdk-go-v2/config v1.29.12/go.mod h1:xse1YTjmORlb/6fhkWi8qJh3cvZi4JoVNhc+NbJt4kI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 h1:q+nV2yYegofO/SUXruT+pn4KxkxmaQ++1B/QedcKBFM=
github.com/aws/aws-sdk-go-v2/credentials v1.17.65/go.mod h1:4zyjAuGOdikpNYiSGpsGz8hLGmUzlY8pc8r9QQ/RXYQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69 h1:6VFPH/Zi9xYFMJKPQOX5URYkQoXRWeJ7V/7Y6ZDYoms=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69/go.mod h1:GJj8mmO6YT6EqgduWocwhMoxTLFitkhIrK+owzrYL2I=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 h1:pdgODsAhGo4dvzC3JAG5Ce0PX8kWXrTZGx+jxADD+5E=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 h1:90uX0veLKcdHVfvxhkWUQSCi5VabtwMLFutYiRke4oo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0=
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diegoholiveira/jsonlogic/v3 v3.7.3 h1:orvGaTW5Qdcowpr7vgfMNuqYNjQYcqtw9W1Bk4QyI38=
github.com/diegoholiveira/jsonlogic/v3 v3.7.3/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
github.com/diegoholiveira/jsonlogic/v3 v3.7.4 h1:92HSmB9bwM/o0ZvrCpcvTP2EsPXSkKtAniIr2W/dcIM=
github.com/diegoholiveira/jsonlogic/v3 v3.7.4/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/diegoholiveira/jsonlogic/v3 v3.8.4 h1:IVVU/VLz2hn10ImbmibjiUkdVsSFIB1vfDaOVsaipH4=
github.com/diegoholiveira/jsonlogic/v3 v3.8.4/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
@ -136,12 +162,12 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -154,8 +180,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -163,8 +189,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo=
@ -176,26 +200,22 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@ -204,12 +224,12 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -227,49 +247,49 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/open-feature/flagd-schemas v0.2.9-0.20250127221449-bb763438abc5 h1:0RKCLYeQpvSsKR95kc894tm8GAZmq7bcG48v0KJ0HCs=
github.com/open-feature/flagd-schemas v0.2.9-0.20250127221449-bb763438abc5/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd-schemas v0.2.9-0.20250310135615-e840a037dc49 h1:hJaCK9RPXzxKVrHXinB7dt5MT6RRbEA6fF1ZZbUFWPE=
github.com/open-feature/flagd-schemas v0.2.9-0.20250310135615-e840a037dc49/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/open-feature/flagd-schemas v0.2.9-0.20250319190911-9b0ee43ecc47 h1:c6nodciz/xeU0xiAcDQ5MBW34DnPoi5/lEgjV5kZeZA=
github.com/open-feature/flagd-schemas v0.2.9-0.20250319190911-9b0ee43ecc47/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/open-feature-operator/apis v0.2.44 h1:0r4Z+RnJltuHdRBv79NFgAckhna6/M3Wcec6gzNX5vI=
github.com/open-feature/open-feature-operator/apis v0.2.44/go.mod h1:xB2uLzvUkbydieX7q6/NqannBz3bt/e5BS2DeOyyw4Q=
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86 h1:r3e+qs3QUdf4+lUi2ZZnSHgYkjeLIb5yu5jo+ypA8iw=
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/open-feature-operator/apis v0.2.45 h1:URnUf22ZoAx7/W8ek8dXCBYgY8FmnFEuEOSDLROQafY=
github.com/open-feature/open-feature-operator/apis v0.2.45/go.mod h1:PYh/Hfyna1lZYZUeu/8LM0qh0ZgpH7kKEXRLYaaRhGs=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -292,79 +312,61 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E=
go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk=
go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk=
go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo=
go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng=
gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ=
gocloud.dev v0.42.0 h1:qzG+9ItUL3RPB62/Amugws28n+4vGZXEoJEAMfjutzw=
gocloud.dev v0.42.0/go.mod h1:zkaYAapZfQisXOA4bzhsbA4ckiStGQ3Psvs9/OQ5dPM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -376,10 +378,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -395,17 +395,11 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -415,10 +409,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -432,20 +424,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -453,12 +441,10 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -471,42 +457,36 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk=
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E=
google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo=
google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM=
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -516,10 +496,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@ -535,25 +513,30 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.31.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM=
k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw=
k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM=
k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ=
k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg=
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40=
k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99 h1:w6nThEmGo9zcL+xH1Tu6pjxJ3K1jXFW+V0u4peqN8ks=
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw=
sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM=
sigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM=
sigs.k8s.io/gateway-api v1.2.1/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -11,6 +11,8 @@ import (
)
func TestFractionalEvaluation(t *testing.T) {
const source = "testSource"
var sources = []string{source}
ctx := context.Background()
commonFlags := Flags{
@ -458,8 +460,13 @@ func TestFractionalEvaluation(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
log := logger.NewLogger(nil, false)
je := NewJSON(log, store.NewFlags())
je.store.Flags = tt.flags.Flags
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
je := NewJSON(log, s)
je.store.Update(source, tt.flags.Flags, model.Metadata{})
value, variant, reason, _, err := resolve[string](ctx, reqID, tt.flagKey, tt.context, je.evaluateVariant)
@ -486,6 +493,8 @@ func TestFractionalEvaluation(t *testing.T) {
}
func BenchmarkFractionalEvaluation(b *testing.B) {
const source = "testSource"
var sources = []string{source}
ctx := context.Background()
flags := Flags{
@ -508,7 +517,7 @@ func BenchmarkFractionalEvaluation(b *testing.B) {
},
{
"fractional": [
"email",
{"var": "email"},
[
"red",
25
@ -542,41 +551,41 @@ func BenchmarkFractionalEvaluation(b *testing.B) {
expectedReason string
expectedErrorCode string
}{
"test@faas.com": {
"test_a@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test@faas.com",
"email": "test_a@faas.com",
},
expectedVariant: "blue",
expectedValue: "#0000FF",
expectedReason: model.TargetingMatchReason,
},
"test_b@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test_b@faas.com",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"test2@faas.com": {
"test_c@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test2@faas.com",
"email": "test_c@faas.com",
},
expectedVariant: "yellow",
expectedValue: "#FFFF00",
expectedVariant: "green",
expectedValue: "#00FF00",
expectedReason: model.TargetingMatchReason,
},
"test3@faas.com": {
"test_d@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test3@faas.com",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"test4@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test4@faas.com",
"email": "test_d@faas.com",
},
expectedVariant: "blue",
expectedValue: "#0000FF",
@ -587,7 +596,13 @@ func BenchmarkFractionalEvaluation(b *testing.B) {
for name, tt := range tests {
b.Run(name, func(b *testing.B) {
log := logger.NewLogger(nil, false)
je := NewJSON(log, &store.State{Flags: tt.flags.Flags})
s, err := store.NewStore(log, sources)
if err != nil {
b.Fatalf("NewStore failed: %v", err)
}
je := NewJSON(log, s)
je.store.Update(source, tt.flags.Flags, model.Metadata{})
for i := 0; i < b.N; i++ {
value, variant, reason, _, err := resolve[string](
ctx, reqID, tt.flagKey, tt.context, je.evaluateVariant)

View File

@ -35,7 +35,7 @@ IEvaluator is an extension of IResolver, allowing storage updates and retrievals
*/
type IEvaluator interface {
GetState() (string, error)
SetState(payload sync.DataSync) (model.Metadata, bool, error)
SetState(payload sync.DataSync) (map[string]interface{}, bool, error)
IResolver
}

View File

@ -64,13 +64,13 @@ func WithEvaluator(name string, evalFunc func(interface{}, interface{}) interfac
// JSON evaluator
type JSON struct {
store *store.State
store *store.Store
Logger *logger.Logger
jsonEvalTracer trace.Tracer
Resolver
}
func NewJSON(logger *logger.Logger, s *store.State, opts ...JSONEvaluatorOption) *JSON {
func NewJSON(logger *logger.Logger, s *store.Store, opts ...JSONEvaluatorOption) *JSON {
logger = logger.WithFields(
zap.String("component", "evaluator"),
zap.String("evaluator", "json"),
@ -103,8 +103,7 @@ func (je *JSON) SetState(payload sync.DataSync) (map[string]interface{}, bool, e
_, span := je.jsonEvalTracer.Start(
context.Background(),
"flagSync",
trace.WithAttributes(attribute.String("feature_flag.source", payload.Source)),
trace.WithAttributes(attribute.String("feature_flag.sync_type", payload.Type.String())))
trace.WithAttributes(attribute.String("feature_flag.source", payload.Source)))
defer span.End()
var definition Definition
@ -119,19 +118,7 @@ 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.
switch payload.Type {
case sync.ALL:
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, definition.Flags)
case sync.UPDATE:
events = je.store.Update(je.Logger, payload.Source, payload.Selector, definition.Flags)
case sync.DELETE:
events = je.store.DeleteFlags(je.Logger, payload.Source, definition.Flags)
default:
return nil, false, fmt.Errorf("unsupported sync type: %d", payload.Type)
}
events, reSync = je.store.Update(payload.Source, definition.Flags, definition.Metadata)
// Number of events correlates to the number of flags changed through this sync, record it
span.SetAttributes(attribute.Int("feature_flag.change_count", len(events)))
@ -152,7 +139,6 @@ func NewResolver(store store.IStore, logger *logger.Logger, jsonEvalTracer trace
jsonlogic.AddOperator(StartsWithEvaluationName, NewStringComparisonEvaluator(logger).StartsWithEvaluation)
jsonlogic.AddOperator(EndsWithEvaluationName, NewStringComparisonEvaluator(logger).EndsWithEvaluation)
jsonlogic.AddOperator(SemVerEvaluationName, NewSemVerComparison(logger).SemVerEvaluation)
jsonlogic.AddOperator(LegacyFractionEvaluationName, NewLegacyFractional(logger).LegacyFractionalEvaluation)
return Resolver{store: store, Logger: logger, tracer: jsonEvalTracer}
}
@ -163,8 +149,12 @@ func (je *Resolver) ResolveAllValues(ctx context.Context, reqID string, context
_, span := je.tracer.Start(ctx, "resolveAll")
defer span.End()
var err error
allFlags, flagSetMetadata, err := je.store.GetAll(ctx)
var selector store.Selector
s := ctx.Value(store.SelectorContextKey{})
if s != nil {
selector = s.(store.Selector)
}
allFlags, flagSetMetadata, err := je.store.GetAll(ctx, &selector)
if err != nil {
return nil, flagSetMetadata, fmt.Errorf("error retreiving flags from the store: %w", err)
}
@ -315,19 +305,19 @@ func resolve[T constraints](ctx context.Context, reqID string, key string, conte
func (je *Resolver) evaluateVariant(ctx context.Context, reqID string, flagKey string, evalCtx map[string]any) (
variant string, variants map[string]interface{}, reason string, metadata map[string]interface{}, err error,
) {
flag, metadata, ok := je.store.Get(ctx, flagKey)
if !ok {
var selector store.Selector
s := ctx.Value(store.SelectorContextKey{})
if s != nil {
selector = s.(store.Selector)
}
flag, metadata, err := je.store.Get(ctx, flagKey, &selector)
if err != nil {
// flag not found
je.Logger.DebugWithID(reqID, fmt.Sprintf("requested flag could not be found: %s", flagKey))
return "", map[string]interface{}{}, model.ErrorReason, metadata, errors.New(model.FlagNotFoundErrorCode)
}
// add selector to evaluation metadata
selector := je.store.SelectorForFlag(ctx, flag)
if selector != "" {
metadata[SelectorMetadataKey] = selector
}
for key, value := range flag.Metadata {
// If value is not nil or empty, copy to metadata
if value != nil {
@ -372,7 +362,12 @@ func (je *Resolver) evaluateVariant(ctx context.Context, reqID string, flagKey s
// check if string is "null" before we strip quotes, so we can differentiate between JSON null and "null"
trimmed := strings.TrimSpace(result.String())
if trimmed == "null" {
if flag.DefaultVariant == "" {
return "", flag.Variants, model.ErrorReason, metadata, errors.New(model.FlagNotFoundErrorCode)
}
return flag.DefaultVariant, flag.Variants, model.DefaultReason, metadata, nil
}
@ -385,8 +380,13 @@ func (je *Resolver) evaluateVariant(ctx context.Context, reqID string, flagKey s
}
je.Logger.ErrorWithID(reqID,
fmt.Sprintf("invalid or missing variant: %s for flagKey: %s, variant is not valid", variant, flagKey))
return "", flag.Variants, model.ErrorReason, metadata, errors.New(model.ParseErrorCode)
return "", flag.Variants, model.ErrorReason, metadata, errors.New(model.GeneralErrorCode)
}
if flag.DefaultVariant == "" {
return "", flag.Variants, model.ErrorReason, metadata, errors.New(model.FlagNotFoundErrorCode)
}
return flag.DefaultVariant, flag.Variants, model.StaticReason, metadata, nil
}
@ -479,6 +479,11 @@ func configToFlagDefinition(log *logger.Logger, config string, definition *Defin
// validateDefaultVariants returns an error if any of the default variants aren't valid
func validateDefaultVariants(flags *Definition) error {
for name, flag := range flags.Flags {
// Default Variant is not provided in the config
if flag.DefaultVariant == "" {
continue
}
if _, ok := flag.Variants[flag.DefaultVariant]; !ok {
return fmt.Errorf(
"default variant: '%s' isn't a valid variant of flag: '%s'", flag.DefaultVariant, name,

View File

@ -44,9 +44,90 @@ const ValidFlags = `{
}
}`
const NullDefault = `{
"flags": {
"validFlag": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
},
"defaultVariant": null
}
}
}`
const UndefinedDefault = `{
"flags": {
"validFlag": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
}
}
}
}`
const NullDefaultWithTargetting = `{
"flags": {
"validFlag": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
},
"defaultVariant": null,
"targeting": {
"if": [
{
"==": [
{
"var": [
"key"
]
},
"value"
]
},
"on"
]
}
}
}
}`
const UndefinedDefaultWithTargetting = `{
"flags": {
"validFlag": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
},
"targeting": {
"if": [
{
"==": [
{
"var": [
"key"
]
},
"value"
]
},
"on"
]
}
}
}
}`
const (
FlagSetID = "testSetId"
Version = "v33"
ValidFlag = "validFlag"
MissingFlag = "missingFlag"
StaticBoolFlag = "staticBoolFlag"
StaticBoolValue = true
@ -302,7 +383,7 @@ var Flags = fmt.Sprintf(`{
func TestGetState_Valid_ContainsFlag(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("Expected no error")
}
@ -324,9 +405,9 @@ func TestSetState_Invalid_Error(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
// set state with an invalid flag definition
_, _, err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags})
if err == nil {
t.Fatalf("expected error")
_, _, err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("unexpected error")
}
}
@ -334,7 +415,7 @@ func TestSetState_Valid_NoError(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
// set state with a valid flag definition
_, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@ -342,7 +423,7 @@ func TestSetState_Valid_NoError(t *testing.T) {
func TestResolveAllValues(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@ -395,62 +476,6 @@ func TestResolveAllValues(t *testing.T) {
}
}
func TestMetadataResolveType(t *testing.T) {
tests := []struct {
flagKey string
metadata model.Metadata
}{
{StaticBoolFlag, model.Metadata{"flagSetId": FlagSetID, "version": Version}},
{MetadataFlag, model.Metadata{"flagSetId": FlagSetID, "version": VersionOverride}},
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
if err != nil {
t.Fatalf("expected no error")
}
for _, test := range tests {
_, _, _, metadata, _ := evaluator.ResolveBooleanValue(context.TODO(), reqID, test.flagKey, nil)
if !reflect.DeepEqual(test.metadata, metadata) {
t.Errorf("expected metadata to be %v, but got %v", test.metadata, metadata)
}
}
}
func TestMetadataResolveAll(t *testing.T) {
expectedFlagSetMetadata := model.Metadata{"flagSetId": FlagSetID, "version": Version}
tests := []struct {
flagKey string
metadata model.Metadata
}{
{StaticBoolFlag, model.Metadata{"flagSetId": FlagSetID, "version": Version}},
{MetadataFlag, model.Metadata{"flagSetId": FlagSetID, "version": VersionOverride}},
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
if err != nil {
t.Fatalf("expected no error")
}
for _, test := range tests {
resolutions, flagSetMetadata, _ := evaluator.ResolveAllValues(context.TODO(), reqID, nil)
for _, resolved := range resolutions {
if resolved.FlagKey == test.flagKey {
if !reflect.DeepEqual(test.metadata, resolved.Metadata) {
t.Errorf("expected flag metadata to be %v, but got %v", test.metadata, resolved.Metadata)
}
}
}
if !reflect.DeepEqual(expectedFlagSetMetadata, flagSetMetadata) {
t.Errorf("expected flag set metadata to be %v, but got %v", expectedFlagSetMetadata, flagSetMetadata)
}
}
}
func TestResolveBooleanValue(t *testing.T) {
tests := []struct {
flagKey string
@ -467,7 +492,7 @@ func TestResolveBooleanValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@ -502,7 +527,7 @@ func BenchmarkResolveBooleanValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@ -542,7 +567,7 @@ func TestResolveStringValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@ -578,7 +603,7 @@ func BenchmarkResolveStringValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@ -618,7 +643,7 @@ func TestResolveFloatValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@ -654,7 +679,7 @@ func BenchmarkResolveFloatValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@ -694,7 +719,7 @@ func TestResolveIntValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@ -730,7 +755,7 @@ func BenchmarkResolveIntValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@ -770,7 +795,7 @@ func TestResolveObjectValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@ -809,7 +834,7 @@ func BenchmarkResolveObjectValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@ -854,7 +879,7 @@ func TestResolveAsAnyValue(t *testing.T) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags})
_, _, err := evaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@ -873,6 +898,37 @@ func TestResolveAsAnyValue(t *testing.T) {
}
}
func TestResolve_DefaultVariant(t *testing.T) {
tests := []struct {
flags string
flagKey string
context map[string]interface{}
reason string
errorCode string
}{
{NullDefault, ValidFlag, nil, model.ErrorReason, model.FlagNotFoundErrorCode},
{UndefinedDefault, ValidFlag, nil, model.ErrorReason, model.FlagNotFoundErrorCode},
{NullDefaultWithTargetting, ValidFlag, nil, model.ErrorReason, model.FlagNotFoundErrorCode},
{UndefinedDefaultWithTargetting, ValidFlag, nil, model.ErrorReason, model.FlagNotFoundErrorCode},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: test.flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
anyResult := evaluator.ResolveAsAnyValue(context.TODO(), "", test.flagKey, test.context)
assert.Equal(t, model.ErrorReason, anyResult.Reason)
assert.EqualError(t, anyResult.Error, test.errorCode)
})
}
}
func TestSetState_DefaultVariantValidation(t *testing.T) {
tests := map[string]struct {
jsonFlags string
@ -926,7 +982,7 @@ func TestSetState_DefaultVariantValidation(t *testing.T) {
t.Run(name, func(t *testing.T) {
jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags})
_, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags, Source: "testSource"})
if tt.valid && err != nil {
t.Error(err)
@ -938,7 +994,6 @@ func TestSetState_DefaultVariantValidation(t *testing.T) {
func TestState_Evaluator(t *testing.T) {
tests := map[string]struct {
inputState string
inputSyncType sync.Type
expectedOutputState string
expectedError bool
expectedResync bool
@ -974,7 +1029,6 @@ func TestState_Evaluator(t *testing.T) {
}
}
`,
inputSyncType: sync.ALL,
expectedOutputState: `
{
"flags": {
@ -987,7 +1041,7 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
"source":"",
"source":"testSource",
"selector":"",
"targeting": {
"if": [
@ -1035,7 +1089,6 @@ func TestState_Evaluator(t *testing.T) {
}
}
`,
inputSyncType: sync.ALL,
expectedOutputState: `
{
"flags": {
@ -1048,7 +1101,7 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
"source":"",
"source":"testSource",
"selector":"",
"targeting": {
"if": [
@ -1092,7 +1145,6 @@ func TestState_Evaluator(t *testing.T) {
}
}
`,
inputSyncType: sync.ALL,
expectedError: true,
},
"invalid targeting": {
@ -1125,7 +1177,7 @@ func TestState_Evaluator(t *testing.T) {
"off": false
},
"defaultVariant": "off",
"source":"",
"source":"testSource",
"targeting": {
"if": [
{
@ -1146,7 +1198,6 @@ func TestState_Evaluator(t *testing.T) {
"flagSources":null
}
`,
inputSyncType: sync.ALL,
expectedError: false,
expectedOutputState: `
{
@ -1160,7 +1211,7 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
"source":"",
"source":"testSource",
"selector":"",
"targeting": {
"if": [
@ -1179,7 +1230,7 @@ func TestState_Evaluator(t *testing.T) {
"off": false
},
"defaultVariant": "off",
"source":"",
"source":"testSource",
"selector":"",
"targeting": {
"if": [
@ -1229,47 +1280,15 @@ func TestState_Evaluator(t *testing.T) {
}
}
`,
inputSyncType: sync.ALL,
expectedError: true,
},
"unexpected sync type": {
inputState: `
{
"flags": {
"fibAlgo": {
"variants": {
"recursive": "recursive",
"memo": "memo",
"loop": "loop",
"binet": "binet"
},
"defaultVariant": "recursive",
"state": "ENABLED",
"targeting": {
"if": [
{
"$ref": "emailWithFaas"
}, "binet", null
]
}
}
},
"$evaluators": {
"emailWithFaas": ""
}
}
`,
inputSyncType: 999,
expectedError: true,
expectedResync: false,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, resync, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState})
_, resync, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState, Source: "testSource"})
if err != nil {
if !tt.expectedError {
t.Error(err)
@ -1302,8 +1321,8 @@ func TestState_Evaluator(t *testing.T) {
t.Fatal(err)
}
if !reflect.DeepEqual(expectedOutputJSON["flags"], gotOutputJSON["flags"]) {
t.Errorf("expected state: %v got state: %v", expectedOutputJSON, gotOutputJSON)
if !reflect.DeepEqual(expectedOutputJSON["flags"], gotOutputJSON) {
t.Errorf("expected state: %v got state: %v", expectedOutputJSON["flags"], gotOutputJSON)
}
})
}
@ -1311,11 +1330,9 @@ func TestState_Evaluator(t *testing.T) {
func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
tests := map[string]struct {
dataSyncType sync.Type
flagResolution func(evaluator *evaluator.JSON) error
}{
"Add_ResolveAllValues": {
dataSyncType: sync.ADD,
flagResolution: func(evaluator *evaluator.JSON) error {
_, _, err := evaluator.ResolveAllValues(context.TODO(), "", nil)
if err != nil {
@ -1325,7 +1342,6 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
},
},
"Update_ResolveAllValues": {
dataSyncType: sync.UPDATE,
flagResolution: func(evaluator *evaluator.JSON) error {
_, _, err := evaluator.ResolveAllValues(context.TODO(), "", nil)
if err != nil {
@ -1335,7 +1351,6 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
},
},
"Delete_ResolveAllValues": {
dataSyncType: sync.DELETE,
flagResolution: func(evaluator *evaluator.JSON) error {
_, _, err := evaluator.ResolveAllValues(context.TODO(), "", nil)
if err != nil {
@ -1345,35 +1360,30 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
},
},
"Add_ResolveBooleanValue": {
dataSyncType: sync.ADD,
flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveBooleanValue(context.TODO(), "", StaticBoolFlag, nil)
return err
},
},
"Update_ResolveStringValue": {
dataSyncType: sync.UPDATE,
flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveBooleanValue(context.TODO(), "", StaticStringValue, nil)
return err
},
},
"Delete_ResolveIntValue": {
dataSyncType: sync.DELETE,
flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveIntValue(context.TODO(), "", StaticIntFlag, nil)
return err
},
},
"Add_ResolveFloatValue": {
dataSyncType: sync.ADD,
flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveFloatValue(context.TODO(), "", StaticFloatFlag, nil)
return err
},
},
"Update_ResolveObjectValue": {
dataSyncType: sync.UPDATE,
flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveObjectValue(context.TODO(), "", StaticObjectFlag, nil)
return err
@ -1385,7 +1395,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
t.Run(name, func(t *testing.T) {
jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Type: sync.ADD})
_, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
t.Fatal(err)
}
@ -1408,7 +1418,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
errChan <- nil
return
default:
_, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Type: tt.dataSyncType})
_, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: Flags, Source: "testSource"})
if err != nil {
errChan <- err
return
@ -1450,7 +1460,7 @@ func TestFlagdAmbientProperties(t *testing.T) {
t.Run("flagKeyIsInTheContext", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: `{
_, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"welcome-banner": {
"state": "ENABLED",
@ -1490,7 +1500,7 @@ func TestFlagdAmbientProperties(t *testing.T) {
t.Run("timestampIsInTheContext", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: `{
_, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"welcome-banner": {
"state": "ENABLED",
@ -1524,7 +1534,7 @@ func TestTargetingVariantBehavior(t *testing.T) {
t.Run("missing variant error", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: `{
_, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"missing-variant": {
"state": "ENABLED",
@ -1552,7 +1562,7 @@ func TestTargetingVariantBehavior(t *testing.T) {
t.Run("null fallback", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
_, _, err := evaluator.SetState(sync.DataSync{FlagData: `{
_, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"null-fallback": {
"state": "ENABLED",
@ -1585,7 +1595,7 @@ func TestTargetingVariantBehavior(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
//nolint:dupword
_, _, err := evaluator.SetState(sync.DataSync{FlagData: `{
_, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"match-boolean": {
"state": "ENABLED",

View File

@ -1,145 +0,0 @@
// This evaluation type is deprecated and will be removed before v1.
// Do not enhance it or use it for reference.
package evaluator
import (
"errors"
"fmt"
"math"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/zeebo/xxh3"
)
const (
LegacyFractionEvaluationName = "fractionalEvaluation"
LegacyFractionEvaluationLink = "https://flagd.dev/concepts/#migrating-from-legacy-fractionalevaluation"
)
// Deprecated: LegacyFractional is deprecated. This will be removed prior to v1 release.
type LegacyFractional struct {
Logger *logger.Logger
}
type legacyFractionalEvaluationDistribution struct {
variant string
percentage int
}
func NewLegacyFractional(logger *logger.Logger) *LegacyFractional {
return &LegacyFractional{Logger: logger}
}
func (fe *LegacyFractional) LegacyFractionalEvaluation(values, data interface{}) interface{} {
fe.Logger.Warn(
fmt.Sprintf("%s is deprecated, please use %s, see: %s",
LegacyFractionEvaluationName,
FractionEvaluationName,
LegacyFractionEvaluationLink))
valueToDistribute, feDistributions, err := parseLegacyFractionalEvaluationData(values, data)
if err != nil {
fe.Logger.Error(fmt.Sprintf("parse fractional evaluation data: %v", err))
return nil
}
return distributeLegacyValue(valueToDistribute, feDistributions)
}
func parseLegacyFractionalEvaluationData(values, data interface{}) (string,
[]legacyFractionalEvaluationDistribution, error,
) {
valuesArray, ok := values.([]interface{})
if !ok {
return "", nil, errors.New("fractional evaluation data is not an array")
}
if len(valuesArray) < 2 {
return "", nil, errors.New("fractional evaluation data has length under 2")
}
bucketBy, ok := valuesArray[0].(string)
if !ok {
return "", nil, errors.New("first element of fractional evaluation data isn't of type string")
}
dataMap, ok := data.(map[string]interface{})
if !ok {
return "", nil, errors.New("data isn't of type map[string]interface{}")
}
v, ok := dataMap[bucketBy]
if !ok {
return "", nil, nil
}
valueToDistribute, ok := v.(string)
if !ok {
return "", nil, fmt.Errorf("var: %s isn't of type string", bucketBy)
}
feDistributions, err := parseLegacyFractionalEvaluationDistributions(valuesArray)
if err != nil {
return "", nil, err
}
return valueToDistribute, feDistributions, nil
}
func parseLegacyFractionalEvaluationDistributions(values []interface{}) (
[]legacyFractionalEvaluationDistribution, error,
) {
sumOfPercentages := 0
var feDistributions []legacyFractionalEvaluationDistribution
for i := 1; i < len(values); i++ {
distributionArray, ok := values[i].([]interface{})
if !ok {
return nil, errors.New("distribution elements aren't of type []interface{}")
}
if len(distributionArray) != 2 {
return nil, errors.New("distribution element isn't length 2")
}
variant, ok := distributionArray[0].(string)
if !ok {
return nil, errors.New("first element of distribution element isn't string")
}
percentage, ok := distributionArray[1].(float64)
if !ok {
return nil, errors.New("second element of distribution element isn't float")
}
sumOfPercentages += int(percentage)
feDistributions = append(feDistributions, legacyFractionalEvaluationDistribution{
variant: variant,
percentage: int(percentage),
})
}
if sumOfPercentages != 100 {
return nil, fmt.Errorf("percentages must sum to 100, got: %d", sumOfPercentages)
}
return feDistributions, nil
}
func distributeLegacyValue(value string, feDistribution []legacyFractionalEvaluationDistribution) string {
hashValue := xxh3.HashString(value)
hashRatio := float64(hashValue) / math.Pow(2, 64) // divide the hash value by the largest possible value, integer 2^64
bucket := int(hashRatio * 100) // integer in range [0, 99]
rangeEnd := 0
for _, dist := range feDistribution {
rangeEnd += dist.percentage
if bucket < rangeEnd {
return dist.variant
}
}
return ""
}

View File

@ -1,300 +0,0 @@
package evaluator
import (
"context"
"testing"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/store"
)
func TestLegacyFractionalEvaluation(t *testing.T) {
ctx := context.Background()
flags := Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"if": [
{
"in": ["@faas.com", {
"var": ["email"]
}]
},
{
"fractionalEvaluation": [
"email",
[
"red",
25
],
[
"blue",
25
],
[
"green",
25
],
[
"yellow",
25
]
]
}, null
]
}`),
},
},
}
tests := map[string]struct {
flags Flags
flagKey string
context map[string]any
expectedValue string
expectedVariant string
expectedReason string
expectedErrorCode string
}{
"test@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test@faas.com",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"test2@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test2@faas.com",
},
expectedVariant: "yellow",
expectedValue: "#FFFF00",
expectedReason: model.TargetingMatchReason,
},
"test3@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test3@faas.com",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"test4@faas.com": {
flags: flags,
flagKey: "headerColor",
context: map[string]any{
"email": "test4@faas.com",
},
expectedVariant: "blue",
expectedValue: "#0000FF",
expectedReason: model.TargetingMatchReason,
},
"non even split": {
flags: Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"if": [
{
"in": ["@faas.com", {
"var": ["email"]
}]
},
{
"fractionalEvaluation": [
"email",
[
"red",
50
],
[
"blue",
25
],
[
"green",
25
]
]
}, null
]
}`),
},
},
},
flagKey: "headerColor",
context: map[string]any{
"email": "test4@faas.com",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.TargetingMatchReason,
},
"fallback to default variant if no email provided": {
flags: Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"fractionalEvaluation": [
"email",
[
"red",
25
],
[
"blue",
25
],
[
"green",
25
],
[
"yellow",
25
]
]
}`),
},
},
},
flagKey: "headerColor",
context: map[string]any{},
expectedVariant: "",
expectedValue: "",
expectedReason: model.ErrorReason,
expectedErrorCode: model.ParseErrorCode,
},
"fallback to default variant if invalid variant as result of fractional evaluation": {
flags: Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"fractionalEvaluation": [
"email",
[
"black",
100
]
]
}`),
},
},
},
flagKey: "headerColor",
context: map[string]any{
"email": "foo@foo.com",
},
expectedVariant: "",
expectedValue: "",
expectedReason: model.ErrorReason,
expectedErrorCode: model.ParseErrorCode,
},
"fallback to default variant if percentages don't sum to 100": {
flags: Flags{
Flags: map[string]model.Flag{
"headerColor": {
State: "ENABLED",
DefaultVariant: "red",
Variants: map[string]any{
"red": "#FF0000",
"blue": "#0000FF",
"green": "#00FF00",
"yellow": "#FFFF00",
},
Targeting: []byte(`{
"fractionalEvaluation": [
"email",
[
"red",
25
],
[
"blue",
25
]
]
}`),
},
},
},
flagKey: "headerColor",
context: map[string]any{
"email": "foo@foo.com",
},
expectedVariant: "red",
expectedValue: "#FF0000",
expectedReason: model.DefaultReason,
},
}
const reqID = "default"
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
log := logger.NewLogger(nil, false)
je := NewJSON(log, store.NewFlags())
je.store.Flags = tt.flags.Flags
value, variant, reason, _, err := resolve[string](ctx, reqID, tt.flagKey, tt.context, je.evaluateVariant)
if value != tt.expectedValue {
t.Errorf("expected value '%s', got '%s'", tt.expectedValue, value)
}
if variant != tt.expectedVariant {
t.Errorf("expected variant '%s', got '%s'", tt.expectedVariant, variant)
}
if reason != tt.expectedReason {
t.Errorf("expected reason '%s', got '%s'", tt.expectedReason, reason)
}
if err != nil {
errorCode := err.Error()
if errorCode != tt.expectedErrorCode {
t.Errorf("expected err '%v', got '%v'", tt.expectedErrorCode, err)
}
}
})
}
}

View File

@ -316,6 +316,8 @@ func TestSemVerOperator_Compare(t *testing.T) {
}
func TestJSONEvaluator_semVerEvaluation(t *testing.T) {
const source = "testSource"
var sources = []string{source}
ctx := context.Background()
tests := map[string]struct {
@ -922,8 +924,12 @@ func TestJSONEvaluator_semVerEvaluation(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
log := logger.NewLogger(nil, false)
je := NewJSON(log, store.NewFlags())
je.store.Flags = tt.flags.Flags
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
je := NewJSON(log, s)
je.store.Update(source, tt.flags.Flags, model.Metadata{})
value, variant, reason, _, err := resolve[string](ctx, reqID, tt.flagKey, tt.context, je.evaluateVariant)

View File

@ -13,6 +13,8 @@ import (
)
func TestJSONEvaluator_startsWithEvaluation(t *testing.T) {
const source = "testSource"
var sources = []string{source}
ctx := context.Background()
tests := map[string]struct {
@ -185,8 +187,12 @@ func TestJSONEvaluator_startsWithEvaluation(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
log := logger.NewLogger(nil, false)
je := NewJSON(log, store.NewFlags())
je.store.Flags = tt.flags.Flags
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
je := NewJSON(log, s)
je.store.Update(source, tt.flags.Flags, model.Metadata{})
value, variant, reason, _, err := resolve[string](ctx, reqID, tt.flagKey, tt.context, je.evaluateVariant)
@ -210,6 +216,8 @@ func TestJSONEvaluator_startsWithEvaluation(t *testing.T) {
}
func TestJSONEvaluator_endsWithEvaluation(t *testing.T) {
const source = "testSource"
var sources = []string{source}
ctx := context.Background()
tests := map[string]struct {
@ -382,9 +390,12 @@ func TestJSONEvaluator_endsWithEvaluation(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
log := logger.NewLogger(nil, false)
je := NewJSON(log, store.NewFlags())
je.store.Flags = tt.flags.Flags
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
je := NewJSON(log, s)
je.store.Update(source, tt.flags.Flags, model.Metadata{})
value, variant, reason, _, err := resolve[string](ctx, reqID, tt.flagKey, tt.context, je.evaluateVariant)

View File

@ -2,7 +2,15 @@ package model
import "encoding/json"
const Key = "Key"
const FlagSetId = "FlagSetId"
const Source = "Source"
const Priority = "Priority"
type Flag struct {
Key string `json:"-"` // not serialized, used only for indexing
FlagSetId string `json:"-"` // not serialized, used only for indexing
Priority int `json:"-"` // not serialized, used only for indexing
State string `json:"state"`
DefaultVariant string `json:"defaultVariant"`
Variants map[string]any `json:"variants"`

View File

@ -0,0 +1,52 @@
package notifications
import (
"reflect"
"github.com/open-feature/flagd/core/pkg/model"
)
const typeField = "type"
// Use to represent change notifications for mode PROVIDER_CONFIGURATION_CHANGE events.
type Notifications map[string]any
// Generate notifications (deltas) from old and new flag sets for use in RPC mode PROVIDER_CONFIGURATION_CHANGE events.
func NewFromFlags(oldFlags, newFlags map[string]model.Flag) Notifications {
notifications := map[string]interface{}{}
// flags removed
for key := range oldFlags {
if _, ok := newFlags[key]; !ok {
notifications[key] = map[string]interface{}{
typeField: string(model.NotificationDelete),
}
}
}
// flags added or modified
for key, newFlag := range newFlags {
oldFlag, exists := oldFlags[key]
if !exists {
notifications[key] = map[string]interface{}{
typeField: string(model.NotificationCreate),
}
} else if !flagsEqual(oldFlag, newFlag) {
notifications[key] = map[string]interface{}{
typeField: string(model.NotificationUpdate),
}
}
}
return notifications
}
func flagsEqual(a, b model.Flag) bool {
return a.State == b.State &&
a.DefaultVariant == b.DefaultVariant &&
reflect.DeepEqual(a.Variants, b.Variants) &&
reflect.DeepEqual(a.Targeting, b.Targeting) &&
a.Source == b.Source &&
a.Selector == b.Selector &&
reflect.DeepEqual(a.Metadata, b.Metadata)
}

View File

@ -0,0 +1,102 @@
package notifications
import (
"testing"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/stretchr/testify/assert"
)
func TestNewFromFlags(t *testing.T) {
flagA := model.Flag{
Key: "flagA",
State: "ENABLED",
DefaultVariant: "on",
Source: "source1",
}
flagAUpdated := model.Flag{
Key: "flagA",
State: "DISABLED",
DefaultVariant: "on",
Source: "source1",
}
flagB := model.Flag{
Key: "flagB",
State: "ENABLED",
DefaultVariant: "off",
Source: "source1",
}
tests := []struct {
name string
oldFlags map[string]model.Flag
newFlags map[string]model.Flag
want Notifications
}{
{
name: "flag added",
oldFlags: map[string]model.Flag{},
newFlags: map[string]model.Flag{"flagA": flagA},
want: Notifications{
"flagA": map[string]interface{}{
"type": string(model.NotificationCreate),
},
},
},
{
name: "flag deleted",
oldFlags: map[string]model.Flag{"flagA": flagA},
newFlags: map[string]model.Flag{},
want: Notifications{
"flagA": map[string]interface{}{
"type": string(model.NotificationDelete),
},
},
},
{
name: "flag changed",
oldFlags: map[string]model.Flag{"flagA": flagA},
newFlags: map[string]model.Flag{"flagA": flagAUpdated},
want: Notifications{
"flagA": map[string]interface{}{
"type": string(model.NotificationUpdate),
},
},
},
{
name: "flag unchanged",
oldFlags: map[string]model.Flag{"flagA": flagA},
newFlags: map[string]model.Flag{"flagA": flagA},
want: Notifications{},
},
{
name: "mixed changes",
oldFlags: map[string]model.Flag{
"flagA": flagA,
"flagB": flagB,
},
newFlags: map[string]model.Flag{
"flagA": flagAUpdated, // updated
"flagC": flagA, // added
},
want: Notifications{
"flagA": map[string]interface{}{
"type": string(model.NotificationUpdate),
},
"flagB": map[string]interface{}{
"type": string(model.NotificationDelete),
},
"flagC": map[string]interface{}{
"type": string(model.NotificationCreate),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewFromFlags(tt.oldFlags, tt.newFlags)
assert.Equal(t, tt.want, got)
})
}
}

View File

@ -2,6 +2,7 @@ package service
import (
"context"
"time"
"connectrpc.com/connect"
)
@ -23,16 +24,18 @@ type Notification struct {
type ReadinessProbe func() bool
type Configuration struct {
ReadinessProbe ReadinessProbe
Port uint16
ManagementPort uint16
ServiceName string
CertPath string
KeyPath string
SocketPath string
CORS []string
Options []connect.HandlerOption
ContextValues map[string]any
ReadinessProbe ReadinessProbe
Port uint16
ManagementPort uint16
ServiceName string
CertPath string
KeyPath string
SocketPath string
CORS []string
Options []connect.HandlerOption
ContextValues map[string]any
HeaderToContextKeyMappings map[string]string
StreamDeadline time.Duration
}
/*

View File

@ -1,357 +0,0 @@
package store
import (
"context"
"encoding/json"
"fmt"
"maps"
"reflect"
"sync"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
)
type del = struct{}
var deleteMarker *del
type IStore interface {
GetAll(ctx context.Context) (map[string]model.Flag, model.Metadata, error)
Get(ctx context.Context, key string) (model.Flag, model.Metadata, bool)
SelectorForFlag(ctx context.Context, flag model.Flag) string
}
type State struct {
mx sync.RWMutex
Flags map[string]model.Flag `json:"flags"`
FlagSources []string
SourceDetails map[string]SourceDetails `json:"sourceMetadata,omitempty"`
MetadataPerSource map[string]model.Metadata `json:"metadata,omitempty"`
}
type SourceDetails struct {
Source string
Selector string
}
func (f *State) hasPriority(stored string, new string) bool {
if stored == new {
return true
}
for i := len(f.FlagSources) - 1; i >= 0; i-- {
switch f.FlagSources[i] {
case stored:
return false
case new:
return true
}
}
return true
}
func NewFlags() *State {
return &State{
Flags: map[string]model.Flag{},
SourceDetails: map[string]SourceDetails{},
MetadataPerSource: map[string]model.Metadata{},
}
}
func (f *State) Set(key string, flag model.Flag) {
f.mx.Lock()
defer f.mx.Unlock()
f.Flags[key] = flag
}
func (f *State) Get(_ context.Context, key string) (model.Flag, model.Metadata, bool) {
f.mx.RLock()
defer f.mx.RUnlock()
metadata := f.getMetadata()
flag, ok := f.Flags[key]
if ok {
metadata = f.GetMetadataForSource(flag.Source)
}
return flag, metadata, ok
}
func (f *State) SelectorForFlag(_ context.Context, flag model.Flag) string {
f.mx.RLock()
defer f.mx.RUnlock()
return f.SourceDetails[flag.Source].Selector
}
func (f *State) Delete(key string) {
f.mx.Lock()
defer f.mx.Unlock()
delete(f.Flags, key)
}
func (f *State) String() (string, error) {
f.mx.RLock()
defer f.mx.RUnlock()
bytes, err := json.Marshal(f)
if err != nil {
return "", fmt.Errorf("unable to marshal flags: %w", err)
}
return string(bytes), nil
}
// GetAll returns a copy of the store's state (copy in order to be concurrency safe)
func (f *State) GetAll(_ context.Context) (map[string]model.Flag, model.Metadata, error) {
f.mx.RLock()
defer f.mx.RUnlock()
flags := make(map[string]model.Flag, len(f.Flags))
for key, flag := range f.Flags {
flags[key] = flag
}
return flags, f.getMetadata(), nil
}
// Add new flags from source.
func (f *State) Add(logger *logger.Logger, source string, selector string, flags map[string]model.Flag,
) map[string]interface{} {
notifications := map[string]interface{}{}
for k, newFlag := range flags {
storedFlag, _, ok := f.Get(context.Background(), k)
if ok && !f.hasPriority(storedFlag.Source, source) {
logger.Debug(
fmt.Sprintf(
"not overwriting: flag %s from source %s does not have priority over %s",
k,
source,
storedFlag.Source,
),
)
continue
}
notifications[k] = map[string]interface{}{
"type": string(model.NotificationCreate),
"source": source,
}
// Store the new version of the flag
newFlag.Source = source
newFlag.Selector = selector
f.Set(k, newFlag)
}
return notifications
}
// Update existing flags from source.
func (f *State) Update(logger *logger.Logger, source string, selector string, flags map[string]model.Flag,
) map[string]interface{} {
notifications := map[string]interface{}{}
for k, flag := range flags {
storedFlag, _, ok := f.Get(context.Background(), k)
if !ok {
logger.Warn(
fmt.Sprintf("failed to update the flag, flag with key %s from source %s does not exist.",
k,
source))
continue
}
if !f.hasPriority(storedFlag.Source, source) {
logger.Debug(
fmt.Sprintf(
"not updating: flag %s from source %s does not have priority over %s",
k,
source,
storedFlag.Source,
),
)
continue
}
notifications[k] = map[string]interface{}{
"type": string(model.NotificationUpdate),
"source": source,
}
flag.Source = source
flag.Selector = selector
f.Set(k, flag)
}
return notifications
}
// DeleteFlags matching flags from source.
func (f *State) DeleteFlags(logger *logger.Logger, source string, flags map[string]model.Flag) map[string]interface{} {
logger.Debug(
fmt.Sprintf(
"store resync triggered: delete event from source %s",
source,
),
)
ctx := context.Background()
_, ok := f.MetadataPerSource[source]
if ok {
delete(f.MetadataPerSource, source)
}
notifications := map[string]interface{}{}
if len(flags) == 0 {
allFlags, _, err := f.GetAll(ctx)
if err != nil {
logger.Error(fmt.Sprintf("error while retrieving flags from the store: %v", err))
return notifications
}
for key, flag := range allFlags {
if flag.Source != source {
continue
}
notifications[key] = map[string]interface{}{
"type": string(model.NotificationDelete),
"source": source,
}
f.Delete(key)
}
}
for k := range flags {
flag, _, ok := f.Get(ctx, k)
if ok {
if !f.hasPriority(flag.Source, source) {
logger.Debug(
fmt.Sprintf(
"not deleting: flag %s from source %s cannot be deleted by %s",
k,
flag.Source,
source,
),
)
continue
}
notifications[k] = map[string]interface{}{
"type": string(model.NotificationDelete),
"source": source,
}
f.Delete(k)
} else {
logger.Warn(
fmt.Sprintf("failed to remove flag, flag with key %s from source %s does not exist.",
k,
source))
}
}
return notifications
}
// Merge provided flags from source with currently stored flags.
// nolint: funlen
func (f *State) Merge(
logger *logger.Logger,
source string,
selector string,
flags map[string]model.Flag,
metadata model.Metadata,
) (map[string]interface{}, bool) {
notifications := map[string]interface{}{}
resyncRequired := false
f.mx.Lock()
f.setSourceMetadata(source, metadata)
for k, v := range f.Flags {
if v.Source == source && v.Selector == selector {
if _, ok := flags[k]; !ok {
// flag has been deleted
delete(f.Flags, k)
notifications[k] = map[string]interface{}{
"type": string(model.NotificationDelete),
"source": source,
}
resyncRequired = true
logger.Debug(
fmt.Sprintf(
"store resync triggered: flag %s has been deleted from source %s",
k, source,
),
)
continue
}
}
}
f.mx.Unlock()
for k, newFlag := range flags {
newFlag.Source = source
newFlag.Selector = selector
storedFlag, _, ok := f.Get(context.Background(), k)
if ok {
if !f.hasPriority(storedFlag.Source, source) {
logger.Debug(
fmt.Sprintf(
"not merging: flag %s from source %s does not have priority over %s",
k, source, storedFlag.Source,
),
)
continue
}
if reflect.DeepEqual(storedFlag, newFlag) {
continue
}
}
if !ok {
notifications[k] = map[string]interface{}{
"type": string(model.NotificationCreate),
"source": source,
}
} else {
notifications[k] = map[string]interface{}{
"type": string(model.NotificationUpdate),
"source": source,
}
}
// Store the new version of the flag
f.Set(k, newFlag)
}
return notifications, resyncRequired
}
func (f *State) GetMetadataForSource(source string) model.Metadata {
perSource, ok := f.MetadataPerSource[source]
if ok && perSource != nil {
return maps.Clone(perSource)
}
return model.Metadata{}
}
func (f *State) getMetadata() model.Metadata {
metadata := model.Metadata{}
for _, perSource := range f.MetadataPerSource {
for key, entry := range perSource {
_, exists := metadata[key]
if !exists {
metadata[key] = entry
} else {
metadata[key] = deleteMarker
}
}
}
// keys that exist across multiple sources are deleted
maps.DeleteFunc(metadata, func(key string, _ interface{}) bool {
return metadata[key] == deleteMarker
})
return metadata
}
func (f *State) setSourceMetadata(source string, metadata model.Metadata) {
if f.MetadataPerSource == nil {
f.MetadataPerSource = map[string]model.Metadata{}
}
f.MetadataPerSource[source] = metadata
}

View File

@ -1,545 +0,0 @@
package store
import (
"reflect"
"testing"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/stretchr/testify/require"
)
func TestHasPriority(t *testing.T) {
tests := []struct {
name string
currentState *State
storedSource string
newSource string
hasPriority bool
}{
{
name: "same source",
currentState: &State{},
storedSource: "A",
newSource: "A",
hasPriority: true,
},
{
name: "no priority",
currentState: &State{
FlagSources: []string{
"B",
"A",
},
},
storedSource: "A",
newSource: "B",
hasPriority: false,
},
{
name: "priority",
currentState: &State{
FlagSources: []string{
"A",
"B",
},
},
storedSource: "A",
newSource: "B",
hasPriority: true,
},
{
name: "not in sources",
currentState: &State{
FlagSources: []string{
"A",
"B",
},
},
storedSource: "C",
newSource: "D",
hasPriority: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
p := tt.currentState.hasPriority(tt.storedSource, tt.newSource)
require.Equal(t, p, tt.hasPriority)
})
}
}
func TestMergeFlags(t *testing.T) {
t.Parallel()
tests := []struct {
name string
current *State
new map[string]model.Flag
newSource string
newSelector string
want *State
wantNotifs map[string]interface{}
wantResync bool
}{
{
name: "both nil",
current: &State{Flags: nil},
new: nil,
want: &State{Flags: nil},
wantNotifs: map[string]interface{}{},
},
{
name: "both empty flags",
current: &State{Flags: map[string]model.Flag{}},
new: map[string]model.Flag{},
want: &State{Flags: map[string]model.Flag{}},
wantNotifs: map[string]interface{}{},
},
{
name: "empty new",
current: &State{Flags: map[string]model.Flag{}},
new: nil,
want: &State{Flags: map[string]model.Flag{}},
wantNotifs: map[string]interface{}{},
},
{
name: "merging with new source",
current: &State{
Flags: map[string]model.Flag{
"waka": {
DefaultVariant: "off",
Source: "1",
},
},
},
new: map[string]model.Flag{
"paka": {
DefaultVariant: "on",
},
},
newSource: "2",
want: &State{Flags: map[string]model.Flag{
"waka": {
DefaultVariant: "off",
Source: "1",
},
"paka": {
DefaultVariant: "on",
Source: "2",
},
}},
wantNotifs: map[string]interface{}{"paka": map[string]interface{}{"type": "write", "source": "2"}},
},
{
name: "override by new update",
current: &State{Flags: map[string]model.Flag{
"waka": {DefaultVariant: "off"},
"paka": {DefaultVariant: "off"},
}},
new: map[string]model.Flag{
"waka": {DefaultVariant: "on"},
"paka": {DefaultVariant: "on"},
},
want: &State{Flags: map[string]model.Flag{
"waka": {DefaultVariant: "on"},
"paka": {DefaultVariant: "on"},
}},
wantNotifs: map[string]interface{}{
"waka": map[string]interface{}{"type": "update", "source": ""},
"paka": map[string]interface{}{"type": "update", "source": ""},
},
},
{
name: "identical update so empty notifications",
current: &State{
Flags: map[string]model.Flag{"hello": {DefaultVariant: "off"}},
},
new: map[string]model.Flag{
"hello": {DefaultVariant: "off"},
},
want: &State{Flags: map[string]model.Flag{
"hello": {DefaultVariant: "off"},
}},
wantNotifs: map[string]interface{}{},
},
{
name: "deleted flag & trigger resync for same source",
current: &State{Flags: map[string]model.Flag{"hello": {DefaultVariant: "off", Source: "A"}}},
new: map[string]model.Flag{},
newSource: "A",
want: &State{Flags: map[string]model.Flag{}},
wantNotifs: map[string]interface{}{"hello": map[string]interface{}{"type": "delete", "source": "A"}},
wantResync: true,
},
{
name: "no deleted & no resync for same source but different selector",
current: &State{Flags: map[string]model.Flag{"hello": {DefaultVariant: "off", Source: "A", Selector: "X"}}},
new: map[string]model.Flag{},
newSource: "A",
newSelector: "Y",
want: &State{Flags: map[string]model.Flag{"hello": {DefaultVariant: "off", Source: "A", Selector: "X"}}},
wantResync: false,
wantNotifs: map[string]interface{}{},
},
{
name: "no merge due to low priority",
current: &State{
FlagSources: []string{
"B",
"A",
},
Flags: map[string]model.Flag{
"hello": {
DefaultVariant: "off",
Source: "A",
},
},
},
new: map[string]model.Flag{"hello": {DefaultVariant: "off"}},
newSource: "B",
want: &State{
FlagSources: []string{
"B",
"A",
},
Flags: map[string]model.Flag{
"hello": {
DefaultVariant: "off",
Source: "A",
},
},
},
wantNotifs: map[string]interface{}{},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotNotifs, resyncRequired := tt.current.Merge(logger.NewLogger(nil, false), tt.newSource, tt.newSelector, tt.new, model.Metadata{})
require.True(t, reflect.DeepEqual(tt.want.Flags, tt.current.Flags))
require.Equal(t, tt.wantNotifs, gotNotifs)
require.Equal(t, tt.wantResync, resyncRequired)
})
}
}
func TestFlags_Add(t *testing.T) {
mockLogger := logger.NewLogger(nil, false)
mockSource := "source"
mockOverrideSource := "source-2"
type request struct {
source string
selector string
flags map[string]model.Flag
}
tests := []struct {
name string
storedState *State
addRequest request
expectedState *State
expectedNotificationKeys []string
}{
{
name: "Add success",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
},
},
addRequest: request{
source: mockSource,
flags: map[string]model.Flag{
"B": {Source: mockSource},
},
},
expectedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
"B": {Source: mockSource},
},
},
expectedNotificationKeys: []string{"B"},
},
{
name: "Add multiple success",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
},
},
addRequest: request{
source: mockSource,
flags: map[string]model.Flag{
"B": {Source: mockSource},
"C": {Source: mockSource},
},
},
expectedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
"B": {Source: mockSource},
"C": {Source: mockSource},
},
},
expectedNotificationKeys: []string{"B", "C"},
},
{
name: "Add success - conflict and override",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
},
},
addRequest: request{
source: mockOverrideSource,
flags: map[string]model.Flag{
"A": {Source: mockOverrideSource},
},
},
expectedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockOverrideSource},
},
},
expectedNotificationKeys: []string{"A"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
messages := tt.storedState.Add(mockLogger, tt.addRequest.source, tt.addRequest.selector, tt.addRequest.flags)
require.Equal(t, tt.storedState, tt.expectedState)
for k := range messages {
require.Containsf(t, tt.expectedNotificationKeys, k,
"Message key %s not present in the expected key list", k)
}
})
}
}
func TestFlags_Update(t *testing.T) {
mockLogger := logger.NewLogger(nil, false)
mockSource := "source"
mockOverrideSource := "source-2"
type request struct {
source string
selector string
flags map[string]model.Flag
}
tests := []struct {
name string
storedState *State
UpdateRequest request
expectedState *State
expectedNotificationKeys []string
}{
{
name: "Update success",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource, DefaultVariant: "True"},
},
},
UpdateRequest: request{
source: mockSource,
flags: map[string]model.Flag{
"A": {Source: mockSource, DefaultVariant: "False"},
},
},
expectedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource, DefaultVariant: "False"},
},
},
expectedNotificationKeys: []string{"A"},
},
{
name: "Update multiple success",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource, DefaultVariant: "True"},
"B": {Source: mockSource, DefaultVariant: "True"},
},
},
UpdateRequest: request{
source: mockSource,
flags: map[string]model.Flag{
"A": {Source: mockSource, DefaultVariant: "False"},
"B": {Source: mockSource, DefaultVariant: "False"},
},
},
expectedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource, DefaultVariant: "False"},
"B": {Source: mockSource, DefaultVariant: "False"},
},
},
expectedNotificationKeys: []string{"A", "B"},
},
{
name: "Update success - conflict and override",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource, DefaultVariant: "True"},
},
},
UpdateRequest: request{
source: mockOverrideSource,
flags: map[string]model.Flag{
"A": {Source: mockOverrideSource, DefaultVariant: "True"},
},
},
expectedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockOverrideSource, DefaultVariant: "True"},
},
},
expectedNotificationKeys: []string{"A"},
},
{
name: "Update fail",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
},
},
UpdateRequest: request{
source: mockSource,
flags: map[string]model.Flag{
"B": {Source: mockSource},
},
},
expectedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
},
},
expectedNotificationKeys: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
messages := tt.storedState.Update(mockLogger, tt.UpdateRequest.source,
tt.UpdateRequest.selector, tt.UpdateRequest.flags)
require.Equal(t, tt.storedState, tt.expectedState)
for k := range messages {
require.Containsf(t, tt.expectedNotificationKeys, k,
"Message key %s not present in the expected key list", k)
}
})
}
}
func TestFlags_Delete(t *testing.T) {
mockLogger := logger.NewLogger(nil, false)
mockSource := "source"
mockSource2 := "source2"
tests := []struct {
name string
storedState *State
deleteRequest map[string]model.Flag
expectedState *State
expectedNotificationKeys []string
}{
{
name: "Remove success",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
"B": {Source: mockSource},
"C": {Source: mockSource2},
},
FlagSources: []string{
mockSource,
mockSource2,
},
},
deleteRequest: map[string]model.Flag{
"A": {Source: mockSource},
},
expectedState: &State{
Flags: map[string]model.Flag{
"B": {Source: mockSource},
"C": {Source: mockSource2},
},
FlagSources: []string{
mockSource,
mockSource2,
},
},
expectedNotificationKeys: []string{"A"},
},
{
name: "Nothing to remove",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
"B": {Source: mockSource},
"C": {Source: mockSource2},
},
FlagSources: []string{
mockSource,
mockSource2,
},
},
deleteRequest: map[string]model.Flag{
"C": {Source: mockSource},
},
expectedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
"B": {Source: mockSource},
"C": {Source: mockSource2},
},
FlagSources: []string{
mockSource,
mockSource2,
},
},
expectedNotificationKeys: []string{},
},
{
name: "Remove all",
storedState: &State{
Flags: map[string]model.Flag{
"A": {Source: mockSource},
"B": {Source: mockSource},
"C": {Source: mockSource2},
},
},
deleteRequest: map[string]model.Flag{},
expectedState: &State{
Flags: map[string]model.Flag{
"C": {Source: mockSource2},
},
},
expectedNotificationKeys: []string{"A", "B"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
messages := tt.storedState.DeleteFlags(mockLogger, mockSource, tt.deleteRequest)
require.Equal(t, tt.storedState, tt.expectedState)
for k := range messages {
require.Containsf(t, tt.expectedNotificationKeys, k,
"Message key %s not present in the expected key list", k)
}
})
}
}

133
core/pkg/store/query.go Normal file
View File

@ -0,0 +1,133 @@
package store
import (
"maps"
"sort"
"strings"
uuid "github.com/google/uuid"
"github.com/open-feature/flagd/core/pkg/model"
)
// flags table and index constants
const flagsTable = "flags"
const idIndex = "id"
const keyIndex = "key"
const sourceIndex = "source"
const priorityIndex = "priority"
const flagSetIdIndex = "flagSetId"
// compound indices; maintain sub-indexes alphabetically; order matters; these must match what's generated in the SelectorMapToQuery func.
const flagSetIdSourceCompoundIndex = flagSetIdIndex + "+" + sourceIndex
const keySourceCompoundIndex = keyIndex + "+" + sourceIndex
const flagSetIdKeySourceCompoundIndex = flagSetIdIndex + "+" + keyIndex + "+" + sourceIndex
// flagSetId defaults to a UUID generated at startup to make our queries consistent
// any flag without a "flagSetId" is assigned this one; it's never exposed externally
var nilFlagSetId = uuid.New().String()
// A selector represents a set of constraints used to query the store.
type Selector struct {
indexMap map[string]string
}
// NewSelector creates a new Selector from a selector expression string.
// For example, to select flags from source "./mySource" and flagSetId "1234", use the expression:
// "source=./mySource,flagSetId=1234"
func NewSelector(selectorExpression string) Selector {
return Selector{
indexMap: expressionToMap(selectorExpression),
}
}
func expressionToMap(sExp string) map[string]string {
selectorMap := make(map[string]string)
if sExp == "" {
return selectorMap
}
if strings.Index(sExp, "=") == -1 {
// if no '=' is found, treat the whole string as as source (backwards compatibility)
// we may may support interpreting this as a flagSetId in the future as an option
selectorMap[sourceIndex] = sExp
return selectorMap
}
// Split the selector by commas
pairs := strings.Split(sExp, ",")
for _, pair := range pairs {
// Split each pair by the first equal sign
parts := strings.Split(pair, "=")
if len(parts) == 2 {
key := parts[0]
value := parts[1]
selectorMap[key] = value
}
}
return selectorMap
}
func (s Selector) WithIndex(key string, value string) Selector {
m := maps.Clone(s.indexMap)
m[key] = value
return Selector{
indexMap: m,
}
}
func (s *Selector) IsEmpty() bool {
return s == nil || len(s.indexMap) == 0
}
// SelectorMapToQuery converts the selector map to an indexId and constraints for querying the store.
// For a given index, a specific order and number of constraints are required.
// Both the indexId and constraints are generated based on the keys present in the selector's internal map.
func (s Selector) ToQuery() (indexId string, constraints []interface{}) {
if len(s.indexMap) == 2 && s.indexMap[flagSetIdIndex] != "" && s.indexMap[keyIndex] != "" {
// special case for flagSetId and key (this is the "id" index)
return idIndex, []interface{}{s.indexMap[flagSetIdIndex], s.indexMap[keyIndex]}
}
qs := []string{}
keys := make([]string, 0, len(s.indexMap))
for key := range s.indexMap {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
indexId += key + "+"
qs = append(qs, s.indexMap[key])
}
indexId = strings.TrimSuffix(indexId, "+")
// Convert []string to []interface{}
c := make([]interface{}, 0, len(qs))
for _, v := range qs {
c = append(c, v)
}
constraints = c
return indexId, constraints
}
// SelectorToMetadata converts the selector's internal map to metadata for logging or tracing purposes.
// Only includes known indices to avoid leaking sensitive information, and is usually returned as the "top level" metadata
func (s *Selector) ToMetadata() model.Metadata {
meta := model.Metadata{}
if s == nil || s.indexMap == nil {
return meta
}
if s.indexMap[flagSetIdIndex] != "" {
meta[flagSetIdIndex] = s.indexMap[flagSetIdIndex]
}
if s.indexMap[sourceIndex] != "" {
meta[sourceIndex] = s.indexMap[sourceIndex]
}
return meta
}

View File

@ -0,0 +1,193 @@
package store
import (
"reflect"
"testing"
"github.com/open-feature/flagd/core/pkg/model"
)
func TestSelector_IsEmpty(t *testing.T) {
tests := []struct {
name string
selector *Selector
wantEmpty bool
}{
{
name: "nil selector",
selector: nil,
wantEmpty: true,
},
{
name: "nil indexMap",
selector: &Selector{indexMap: nil},
wantEmpty: true,
},
{
name: "empty indexMap",
selector: &Selector{indexMap: map[string]string{}},
wantEmpty: true,
},
{
name: "non-empty indexMap",
selector: &Selector{indexMap: map[string]string{"source": "abc"}},
wantEmpty: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.selector.IsEmpty()
if got != tt.wantEmpty {
t.Errorf("IsEmpty() = %v, want %v", got, tt.wantEmpty)
}
})
}
}
func TestSelector_WithIndex(t *testing.T) {
oldS := Selector{indexMap: map[string]string{"source": "abc"}}
newS := oldS.WithIndex("flagSetId", "1234")
if newS.indexMap["source"] != "abc" {
t.Errorf("WithIndex did not preserve existing keys")
}
if newS.indexMap["flagSetId"] != "1234" {
t.Errorf("WithIndex did not add new key")
}
// Ensure original is unchanged
if _, ok := oldS.indexMap["flagSetId"]; ok {
t.Errorf("WithIndex mutated original selector")
}
}
func TestSelector_ToQuery(t *testing.T) {
tests := []struct {
name string
selector Selector
wantIndex string
wantConstr []interface{}
}{
{
name: "flagSetId and key primary index special case",
selector: Selector{indexMap: map[string]string{"flagSetId": "fsid", "key": "myKey"}},
wantIndex: "id",
wantConstr: []interface{}{"fsid", "myKey"},
},
{
name: "multiple keys sorted",
selector: Selector{indexMap: map[string]string{"source": "src", "flagSetId": "fsid"}},
wantIndex: "flagSetId+source",
wantConstr: []interface{}{"fsid", "src"},
},
{
name: "single key",
selector: Selector{indexMap: map[string]string{"source": "src"}},
wantIndex: "source",
wantConstr: []interface{}{"src"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotIndex, gotConstr := tt.selector.ToQuery()
if gotIndex != tt.wantIndex {
t.Errorf("ToQuery() index = %v, want %v", gotIndex, tt.wantIndex)
}
if !reflect.DeepEqual(gotConstr, tt.wantConstr) {
t.Errorf("ToQuery() constraints = %v, want %v", gotConstr, tt.wantConstr)
}
})
}
}
func TestSelector_ToMetadata(t *testing.T) {
tests := []struct {
name string
selector *Selector
want model.Metadata
}{
{
name: "nil selector",
selector: nil,
want: model.Metadata{},
},
{
name: "nil indexMap",
selector: &Selector{indexMap: nil},
want: model.Metadata{},
},
{
name: "empty indexMap",
selector: &Selector{indexMap: map[string]string{}},
want: model.Metadata{},
},
{
name: "flagSetId only",
selector: &Selector{indexMap: map[string]string{"flagSetId": "fsid"}},
want: model.Metadata{"flagSetId": "fsid"},
},
{
name: "source only",
selector: &Selector{indexMap: map[string]string{"source": "src"}},
want: model.Metadata{"source": "src"},
},
{
name: "flagSetId and source",
selector: &Selector{indexMap: map[string]string{"flagSetId": "fsid", "source": "src"}},
want: model.Metadata{"flagSetId": "fsid", "source": "src"},
},
{
name: "flagSetId, source, and key (key should be ignored)",
selector: &Selector{indexMap: map[string]string{"flagSetId": "fsid", "source": "src", "key": "myKey"}},
want: model.Metadata{"flagSetId": "fsid", "source": "src"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.selector.ToMetadata()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ToMetadata() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewSelector(t *testing.T) {
tests := []struct {
name string
input string
wantMap map[string]string
}{
{
name: "source and flagSetId",
input: "source=abc,flagSetId=1234",
wantMap: map[string]string{"source": "abc", "flagSetId": "1234"},
},
{
name: "source",
input: "source=abc",
wantMap: map[string]string{"source": "abc"},
},
{
name: "no equals, treat as source",
input: "mysource",
wantMap: map[string]string{"source": "mysource"},
},
{
name: "empty string",
input: "",
wantMap: map[string]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewSelector(tt.input)
if !reflect.DeepEqual(s.indexMap, tt.wantMap) {
t.Errorf("NewSelector(%q) indexMap = %v, want %v", tt.input, s.indexMap, tt.wantMap)
}
})
}
}

396
core/pkg/store/store.go Normal file
View File

@ -0,0 +1,396 @@
package store
import (
"context"
"encoding/json"
"fmt"
"slices"
"sync"
"github.com/hashicorp/go-memdb"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/notifications"
)
var noValidatedSources = []string{}
type SelectorContextKey struct{}
type FlagQueryResult struct {
Flags map[string]model.Flag
}
type IStore interface {
Get(ctx context.Context, key string, selector *Selector) (model.Flag, model.Metadata, error)
GetAll(ctx context.Context, selector *Selector) (map[string]model.Flag, model.Metadata, error)
Watch(ctx context.Context, selector *Selector, watcher chan<- FlagQueryResult)
}
var _ IStore = (*Store)(nil)
type Store struct {
mx sync.RWMutex
db *memdb.MemDB
logger *logger.Logger
sources []string
// deprecated: has no effect and will be removed soon.
FlagSources []string
}
type SourceDetails struct {
Source string
Selector string
}
// NewStore creates a new in-memory store with the given sources.
// The order of sources in the slice determines their priority, when queries result in duplicate flags (queries without source or flagSetId), the higher priority source "wins".
func NewStore(logger *logger.Logger, sources []string) (*Store, error) {
// a unique index must exist for each set of constraints - for example, to look up by key and source, we need a compound index on key+source, etc
// we maybe want to generate these dynamically in the future to support more robust querying, but for now we will hardcode the ones we need
schema := &memdb.DBSchema{
Tables: map[string]*memdb.TableSchema{
flagsTable: {
Name: flagsTable,
Indexes: map[string]*memdb.IndexSchema{
// primary index; must be unique and named "id"
idIndex: {
Name: idIndex,
Unique: true,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{Field: model.FlagSetId, Lowercase: false},
&memdb.StringFieldIndex{Field: model.Key, Lowercase: false},
},
},
},
// for looking up by source
sourceIndex: {
Name: sourceIndex,
Unique: false,
Indexer: &memdb.StringFieldIndex{Field: model.Source, Lowercase: false},
},
// for looking up by priority, used to maintain highest priority flag when there are duplicates and no selector is provided
priorityIndex: {
Name: priorityIndex,
Unique: false,
Indexer: &memdb.IntFieldIndex{Field: model.Priority},
},
// for looking up by flagSetId
flagSetIdIndex: {
Name: flagSetIdIndex,
Unique: false,
Indexer: &memdb.StringFieldIndex{Field: model.FlagSetId, Lowercase: false},
},
keyIndex: {
Name: keyIndex,
Unique: false,
Indexer: &memdb.StringFieldIndex{Field: model.Key, Lowercase: false},
},
flagSetIdSourceCompoundIndex: {
Name: flagSetIdSourceCompoundIndex,
Unique: false,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{Field: model.FlagSetId, Lowercase: false},
&memdb.StringFieldIndex{Field: model.Source, Lowercase: false},
},
},
},
keySourceCompoundIndex: {
Name: keySourceCompoundIndex,
Unique: false, // duplicate from a single source ARE allowed (they just must have different flag sets)
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{Field: model.Key, Lowercase: false},
&memdb.StringFieldIndex{Field: model.Source, Lowercase: false},
},
},
},
// used to query all flags from a specific source so we know which flags to delete if a flag is missing from a source
flagSetIdKeySourceCompoundIndex: {
Name: flagSetIdKeySourceCompoundIndex,
Unique: true,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{Field: model.FlagSetId, Lowercase: false},
&memdb.StringFieldIndex{Field: model.Key, Lowercase: false},
&memdb.StringFieldIndex{Field: model.Source, Lowercase: false},
},
},
},
},
},
},
}
// Create a new data base
db, err := memdb.NewMemDB(schema)
if err != nil {
return nil, fmt.Errorf("unable to initialize flag database: %w", err)
}
// clone the sources to avoid modifying the original slice
s := slices.Clone(sources)
return &Store{
sources: s,
db: db,
logger: logger,
}, nil
}
// Deprecated: use NewStore instead - will be removed very soon.
func NewFlags() *Store {
state, err := NewStore(logger.NewLogger(nil, false), noValidatedSources)
if err != nil {
panic(fmt.Sprintf("unable to create flag store: %v", err))
}
return state
}
func (s *Store) Get(_ context.Context, key string, selector *Selector) (model.Flag, model.Metadata, error) {
s.logger.Debug(fmt.Sprintf("getting flag %s", key))
txn := s.db.Txn(false)
queryMeta := selector.ToMetadata()
// if present, use the selector to query the flags
if !selector.IsEmpty() {
selector := selector.WithIndex("key", key)
indexId, constraints := selector.ToQuery()
s.logger.Debug(fmt.Sprintf("getting flag with query: %s, %v", indexId, constraints))
raw, err := txn.First(flagsTable, indexId, constraints...)
flag, ok := raw.(model.Flag)
if err != nil {
return model.Flag{}, queryMeta, fmt.Errorf("flag %s not found: %w", key, err)
}
if !ok {
return model.Flag{}, queryMeta, fmt.Errorf("flag %s is not a valid flag", key)
}
return flag, queryMeta, nil
}
// otherwise, get all flags with the given key, and keep the last one with the highest priority
s.logger.Debug(fmt.Sprintf("getting highest priority flag with key: %s", key))
it, err := txn.Get(flagsTable, keyIndex, key)
if err != nil {
return model.Flag{}, queryMeta, fmt.Errorf("flag %s not found: %w", key, err)
}
flag := model.Flag{}
found := false
for raw := it.Next(); raw != nil; raw = it.Next() {
nextFlag, ok := raw.(model.Flag)
if !ok {
continue
}
found = true
if nextFlag.Priority >= flag.Priority {
flag = nextFlag
} else {
s.logger.Debug(fmt.Sprintf("discarding flag %s from lower priority source %s in favor of flag from source %s", nextFlag.Key, s.sources[nextFlag.Priority], s.sources[flag.Priority]))
}
}
if !found {
return flag, queryMeta, fmt.Errorf("flag %s not found", key)
}
return flag, queryMeta, nil
}
func (f *Store) String() (string, error) {
f.logger.Debug("dumping flags to string")
f.mx.RLock()
defer f.mx.RUnlock()
state, _, err := f.GetAll(context.Background(), nil)
if err != nil {
return "", fmt.Errorf("unable to get all flags: %w", err)
}
bytes, err := json.Marshal(state)
if err != nil {
return "", fmt.Errorf("unable to marshal flags: %w", err)
}
return string(bytes), nil
}
// GetAll returns a copy of the store's state (copy in order to be concurrency safe)
func (s *Store) GetAll(ctx context.Context, selector *Selector) (map[string]model.Flag, model.Metadata, error) {
flags := make(map[string]model.Flag)
queryMeta := selector.ToMetadata()
it, err := s.selectOrAll(selector)
if err != nil {
s.logger.Error(fmt.Sprintf("flag query error: %v", err))
return flags, queryMeta, err
}
flags = s.collect(it)
return flags, queryMeta, nil
}
// Update the flag state with the provided flags.
func (s *Store) Update(
source string,
flags map[string]model.Flag,
metadata model.Metadata,
) (map[string]interface{}, bool) {
resyncRequired := false
if source == "" {
panic("source cannot be empty")
}
priority := slices.Index(s.sources, source)
if priority == -1 {
// this is a hack to allow old constructors that didn't pass sources, remove when we remove "NewFlags" constructor
if !slices.Equal(s.sources, noValidatedSources) {
panic(fmt.Sprintf("source %s is not registered in the store", source))
}
// same as above - remove when we remove "NewFlags" constructor
priority = 0
}
txn := s.db.Txn(true)
defer txn.Abort()
// get all flags for the source we are updating
selector := NewSelector(sourceIndex + "=" + source)
oldFlags, _, _ := s.GetAll(context.Background(), &selector)
s.mx.Lock()
for key := range oldFlags {
if _, ok := flags[key]; !ok {
// flag has been deleted
s.logger.Debug(fmt.Sprintf("flag %s has been deleted from source %s", key, source))
count, err := txn.DeleteAll(flagsTable, keySourceCompoundIndex, key, source)
s.logger.Debug(fmt.Sprintf("deleted %d flags with key %s from source %s", count, key, source))
if err != nil {
s.logger.Error(fmt.Sprintf("error deleting flag: %s, %v", key, err))
}
continue
}
}
s.mx.Unlock()
for key, newFlag := range flags {
s.logger.Debug(fmt.Sprintf("got metadata %v", metadata))
newFlag.Key = key
newFlag.Source = source
newFlag.Priority = priority
newFlag.Metadata = patchMetadata(metadata, newFlag.Metadata)
// flagSetId defaults to a UUID generated at startup to make our queries isomorphic
flagSetId := nilFlagSetId
// flagSetId is inherited from the set, but can be overridden by the flag
setFlagSetId, ok := newFlag.Metadata["flagSetId"].(string)
if ok {
flagSetId = setFlagSetId
}
newFlag.FlagSetId = flagSetId
raw, err := txn.First(flagsTable, keySourceCompoundIndex, key, source)
if err != nil {
s.logger.Error(fmt.Sprintf("unable to get flag %s from source %s: %v", key, source, err))
continue
}
oldFlag, ok := raw.(model.Flag)
// If we already have a flag with the same key and source, we need to check if it has the same flagSetId
if ok {
if oldFlag.FlagSetId != newFlag.FlagSetId {
// If the flagSetId is different, we need to delete the entry, since flagSetId+key represents the primary index, and it's now been changed.
// This is important especially for clients listening to flagSetId changes, as they expect the flag to be removed from the set in this case.
_, err = txn.DeleteAll(flagsTable, idIndex, oldFlag.FlagSetId, key)
if err != nil {
s.logger.Error(fmt.Sprintf("unable to delete flags with key %s and flagSetId %s: %v", key, oldFlag.FlagSetId, err))
continue
}
}
}
// Store the new version of the flag
s.logger.Debug(fmt.Sprintf("storing flag: %v", newFlag))
err = txn.Insert(flagsTable, newFlag)
if err != nil {
s.logger.Error(fmt.Sprintf("unable to insert flag %s: %v", key, err))
continue
}
}
txn.Commit()
return notifications.NewFromFlags(oldFlags, flags), resyncRequired
}
// Watch the result-set of a selector for changes, sending updates to the watcher channel.
func (s *Store) Watch(ctx context.Context, selector *Selector, watcher chan<- FlagQueryResult) {
go func() {
for {
ws := memdb.NewWatchSet()
it, err := s.selectOrAll(selector)
if err != nil {
s.logger.Error(fmt.Sprintf("error watching flags: %v", err))
close(watcher)
return
}
ws.Add(it.WatchCh())
flags := s.collect(it)
watcher <- FlagQueryResult{
Flags: flags,
}
if err = ws.WatchCtx(ctx); err != nil {
s.logger.Error(fmt.Sprintf("error watching flags: %v", err))
close(watcher)
return
}
}
}()
}
// returns an iterator for the given selector, or all flags if the selector is nil or empty
func (s *Store) selectOrAll(selector *Selector) (it memdb.ResultIterator, err error) {
txn := s.db.Txn(false)
if !selector.IsEmpty() {
indexId, constraints := selector.ToQuery()
s.logger.Debug(fmt.Sprintf("getting all flags with query: %s, %v", indexId, constraints))
return txn.Get(flagsTable, indexId, constraints...)
} else {
// no selector, get all flags
return txn.Get(flagsTable, idIndex)
}
}
// collects flags from an iterator, ensuring that only the highest priority flag is kept when there are duplicates
func (s *Store) collect(it memdb.ResultIterator) map[string]model.Flag {
flags := make(map[string]model.Flag)
for raw := it.Next(); raw != nil; raw = it.Next() {
flag := raw.(model.Flag)
if existing, ok := flags[flag.Key]; ok {
if flag.Priority < existing.Priority {
s.logger.Debug(fmt.Sprintf("discarding duplicate flag %s from lower priority source %s in favor of flag from source %s", flag.Key, s.sources[flag.Priority], s.sources[existing.Priority]))
continue // we already have a higher priority flag
}
s.logger.Debug(fmt.Sprintf("overwriting duplicate flag %s from lower priority source %s in favor of flag from source %s", flag.Key, s.sources[existing.Priority], s.sources[flag.Priority]))
}
flags[flag.Key] = flag
}
return flags
}
func patchMetadata(original, patch model.Metadata) model.Metadata {
patched := make(model.Metadata)
if original == nil && patch == nil {
return nil
}
for key, value := range original {
patched[key] = value
}
for key, value := range patch { // patch values overwrite m1 values on key conflict
patched[key] = value
}
return patched
}

View File

@ -0,0 +1,487 @@
package store
import (
"context"
"testing"
"time"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUpdateFlags(t *testing.T) {
const source1 = "source1"
const source2 = "source2"
var sources = []string{source1, source2}
t.Parallel()
tests := []struct {
name string
setup func(t *testing.T) *Store
newFlags map[string]model.Flag
source string
wantFlags map[string]model.Flag
setMetadata model.Metadata
wantNotifs map[string]interface{}
wantResync bool
}{
{
name: "both nil",
setup: func(t *testing.T) *Store {
s, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
return s
},
source: source1,
newFlags: nil,
wantFlags: map[string]model.Flag{},
wantNotifs: map[string]interface{}{},
},
{
name: "both empty flags",
setup: func(t *testing.T) *Store {
s, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
return s
},
source: source1,
newFlags: map[string]model.Flag{},
wantFlags: map[string]model.Flag{},
wantNotifs: map[string]interface{}{},
},
{
name: "empty new",
setup: func(t *testing.T) *Store {
s, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
return s
},
source: source1,
newFlags: nil,
wantFlags: map[string]model.Flag{},
wantNotifs: map[string]interface{}{},
},
{
name: "update from source 1 (old flag removed)",
setup: func(t *testing.T) *Store {
s, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
s.Update(source1, map[string]model.Flag{
"waka": {DefaultVariant: "off"},
}, nil)
return s
},
newFlags: map[string]model.Flag{
"paka": {DefaultVariant: "on"},
},
source: source1,
wantFlags: map[string]model.Flag{
"paka": {Key: "paka", DefaultVariant: "on", Source: source1, FlagSetId: nilFlagSetId, Priority: 0},
},
wantNotifs: map[string]interface{}{
"paka": map[string]interface{}{"type": "write"},
"waka": map[string]interface{}{"type": "delete"},
},
},
{
name: "update from source 1 (new flag added)",
setup: func(t *testing.T) *Store {
s, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
s.Update(source1, map[string]model.Flag{
"waka": {DefaultVariant: "off"},
}, nil)
return s
},
newFlags: map[string]model.Flag{
"paka": {DefaultVariant: "on"},
},
source: source2,
wantFlags: map[string]model.Flag{
"waka": {Key: "waka", DefaultVariant: "off", Source: source1, FlagSetId: nilFlagSetId, Priority: 0},
"paka": {Key: "paka", DefaultVariant: "on", Source: source2, FlagSetId: nilFlagSetId, Priority: 1},
},
wantNotifs: map[string]interface{}{"paka": map[string]interface{}{"type": "write"}},
},
{
name: "flag set inheritance",
setup: func(t *testing.T) *Store {
s, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
s.Update(source1, map[string]model.Flag{}, model.Metadata{})
return s
},
setMetadata: model.Metadata{
"flagSetId": "topLevelSet", // top level set metadata, including flagSetId
},
newFlags: map[string]model.Flag{
"waka": {DefaultVariant: "on"},
"paka": {DefaultVariant: "on", Metadata: model.Metadata{"flagSetId": "flagLevelSet"}}, // overrides set level flagSetId
},
source: source1,
wantFlags: map[string]model.Flag{
"waka": {Key: "waka", DefaultVariant: "on", Source: source1, FlagSetId: "topLevelSet", Priority: 0, Metadata: model.Metadata{"flagSetId": "topLevelSet"}},
"paka": {Key: "paka", DefaultVariant: "on", Source: source1, FlagSetId: "flagLevelSet", Priority: 0, Metadata: model.Metadata{"flagSetId": "flagLevelSet"}},
},
wantNotifs: map[string]interface{}{
"paka": map[string]interface{}{"type": "write"},
"waka": map[string]interface{}{"type": "write"},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
store := tt.setup(t)
gotNotifs, resyncRequired := store.Update(tt.source, tt.newFlags, tt.setMetadata)
gotFlags, _, _ := store.GetAll(context.Background(), nil)
require.Equal(t, tt.wantFlags, gotFlags)
require.Equal(t, tt.wantNotifs, gotNotifs)
require.Equal(t, tt.wantResync, resyncRequired)
})
}
}
func TestGet(t *testing.T) {
sourceA := "sourceA"
sourceB := "sourceB"
sourceC := "sourceC"
flagSetIdB := "flagSetIdA"
flagSetIdC := "flagSetIdC"
var sources = []string{sourceA, sourceB, sourceC}
sourceASelector := NewSelector("source=" + sourceA)
flagSetIdCSelector := NewSelector("flagSetId=" + flagSetIdC)
t.Parallel()
tests := []struct {
name string
key string
selector *Selector
wantFlag model.Flag
wantErr bool
}{
{
name: "nil selector",
key: "flagA",
selector: nil,
wantFlag: model.Flag{Key: "flagA", DefaultVariant: "off", Source: sourceA, FlagSetId: nilFlagSetId, Priority: 0},
wantErr: false,
},
{
name: "flagSetId selector",
key: "dupe",
selector: &flagSetIdCSelector,
wantFlag: model.Flag{Key: "dupe", DefaultVariant: "off", Source: sourceC, FlagSetId: flagSetIdC, Priority: 2, Metadata: model.Metadata{"flagSetId": flagSetIdC}},
wantErr: false,
},
{
name: "source selector",
key: "dupe",
selector: &sourceASelector,
wantFlag: model.Flag{Key: "dupe", DefaultVariant: "on", Source: sourceA, FlagSetId: nilFlagSetId, Priority: 0},
wantErr: false,
},
{
name: "flag not found with source selector",
key: "flagB",
selector: &sourceASelector,
wantFlag: model.Flag{Key: "flagB", DefaultVariant: "off", Source: sourceB, FlagSetId: flagSetIdB, Priority: 1, Metadata: model.Metadata{"flagSetId": flagSetIdB}},
wantErr: true,
},
{
name: "flag not found with flagSetId selector",
key: "flagB",
selector: &flagSetIdCSelector,
wantFlag: model.Flag{Key: "flagB", DefaultVariant: "off", Source: sourceB, FlagSetId: flagSetIdB, Priority: 1, Metadata: model.Metadata{"flagSetId": flagSetIdB}},
wantErr: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
sourceAFlags := map[string]model.Flag{
"flagA": {Key: "flagA", DefaultVariant: "off"},
"dupe": {Key: "dupe", DefaultVariant: "on"},
}
sourceBFlags := map[string]model.Flag{
"flagB": {Key: "flagB", DefaultVariant: "off", Metadata: model.Metadata{"flagSetId": flagSetIdB}},
}
sourceCFlags := map[string]model.Flag{
"flagC": {Key: "flagC", DefaultVariant: "off", Metadata: model.Metadata{"flagSetId": flagSetIdC}},
"dupe": {Key: "dupe", DefaultVariant: "off", Metadata: model.Metadata{"flagSetId": flagSetIdC}},
}
store, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
store.Update(sourceA, sourceAFlags, nil)
store.Update(sourceB, sourceBFlags, nil)
store.Update(sourceC, sourceCFlags, nil)
gotFlag, _, err := store.Get(context.Background(), tt.key, tt.selector)
if !tt.wantErr {
require.Equal(t, tt.wantFlag, gotFlag)
} else {
require.Error(t, err, "expected an error for key %s with selector %v", tt.key, tt.selector)
}
})
}
}
func TestGetAllNoWatcher(t *testing.T) {
sourceA := "sourceA"
sourceB := "sourceB"
sourceC := "sourceC"
flagSetIdB := "flagSetIdA"
flagSetIdC := "flagSetIdC"
sources := []string{sourceA, sourceB, sourceC}
sourceASelector := NewSelector("source=" + sourceA)
flagSetIdCSelector := NewSelector("flagSetId=" + flagSetIdC)
t.Parallel()
tests := []struct {
name string
selector *Selector
wantFlags map[string]model.Flag
}{
{
name: "nil selector",
selector: nil,
wantFlags: map[string]model.Flag{
// "dupe" should be overwritten by higher priority flag
"flagA": {Key: "flagA", DefaultVariant: "off", Source: sourceA, FlagSetId: nilFlagSetId, Priority: 0},
"flagB": {Key: "flagB", DefaultVariant: "off", Source: sourceB, FlagSetId: flagSetIdB, Priority: 1, Metadata: model.Metadata{"flagSetId": flagSetIdB}},
"flagC": {Key: "flagC", DefaultVariant: "off", Source: sourceC, FlagSetId: flagSetIdC, Priority: 2, Metadata: model.Metadata{"flagSetId": flagSetIdC}},
"dupe": {Key: "dupe", DefaultVariant: "off", Source: sourceC, FlagSetId: flagSetIdC, Priority: 2, Metadata: model.Metadata{"flagSetId": flagSetIdC}},
},
},
{
name: "source selector",
selector: &sourceASelector,
wantFlags: map[string]model.Flag{
// we should get the "dupe" from sourceA
"flagA": {Key: "flagA", DefaultVariant: "off", Source: sourceA, FlagSetId: nilFlagSetId, Priority: 0},
"dupe": {Key: "dupe", DefaultVariant: "on", Source: sourceA, FlagSetId: nilFlagSetId, Priority: 0},
},
},
{
name: "flagSetId selector",
selector: &flagSetIdCSelector,
wantFlags: map[string]model.Flag{
// we should get the "dupe" from flagSetIdC
"flagC": {Key: "flagC", DefaultVariant: "off", Source: sourceC, FlagSetId: flagSetIdC, Priority: 2, Metadata: model.Metadata{"flagSetId": flagSetIdC}},
"dupe": {Key: "dupe", DefaultVariant: "off", Source: sourceC, FlagSetId: flagSetIdC, Priority: 2, Metadata: model.Metadata{"flagSetId": flagSetIdC}},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
sourceAFlags := map[string]model.Flag{
"flagA": {Key: "flagA", DefaultVariant: "off"},
"dupe": {Key: "dupe", DefaultVariant: "on"},
}
sourceBFlags := map[string]model.Flag{
"flagB": {Key: "flagB", DefaultVariant: "off", Metadata: model.Metadata{"flagSetId": flagSetIdB}},
}
sourceCFlags := map[string]model.Flag{
"flagC": {Key: "flagC", DefaultVariant: "off", Metadata: model.Metadata{"flagSetId": flagSetIdC}},
"dupe": {Key: "dupe", DefaultVariant: "off", Metadata: model.Metadata{"flagSetId": flagSetIdC}},
}
store, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
store.Update(sourceA, sourceAFlags, nil)
store.Update(sourceB, sourceBFlags, nil)
store.Update(sourceC, sourceCFlags, nil)
gotFlags, _, _ := store.GetAll(context.Background(), tt.selector)
require.Equal(t, len(tt.wantFlags), len(gotFlags))
require.Equal(t, tt.wantFlags, gotFlags)
})
}
}
func TestWatch(t *testing.T) {
sourceA := "sourceA"
sourceB := "sourceB"
sourceC := "sourceC"
myFlagSetId := "myFlagSet"
var sources = []string{sourceA, sourceB, sourceC}
pauseTime := 100 * time.Millisecond // time for updates to settle
timeout := 1000 * time.Millisecond // time to make sure we get enough updates, and no extras
sourceASelector := NewSelector("source=" + sourceA)
flagSetIdCSelector := NewSelector("flagSetId=" + myFlagSetId)
emptySelector := NewSelector("")
sourceCSelector := NewSelector("source=" + sourceC)
tests := []struct {
name string
selector *Selector
wantUpdates int
}{
{
name: "flag source selector (initial, plus 1 update)",
selector: &sourceASelector,
wantUpdates: 2,
},
{
name: "flag set selector (initial, plus 3 updates)",
selector: &flagSetIdCSelector,
wantUpdates: 4,
},
{
name: "no selector (all updates)",
selector: &emptySelector,
wantUpdates: 5,
},
{
name: "flag source selector for unchanged source (initial, plus no updates)",
selector: &sourceCSelector,
wantUpdates: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
sourceAFlags := map[string]model.Flag{
"flagA": {Key: "flagA", DefaultVariant: "off"},
}
sourceBFlags := map[string]model.Flag{
"flagB": {Key: "flagB", DefaultVariant: "off", Metadata: model.Metadata{"flagSetId": myFlagSetId}},
}
sourceCFlags := map[string]model.Flag{
"flagC": {Key: "flagC", DefaultVariant: "off"},
}
store, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
// setup initial flags
store.Update(sourceA, sourceAFlags, model.Metadata{})
store.Update(sourceB, sourceBFlags, model.Metadata{})
store.Update(sourceC, sourceCFlags, model.Metadata{})
watcher := make(chan FlagQueryResult, 1)
time.Sleep(pauseTime)
ctx, cancel := context.WithCancel(context.Background())
store.Watch(ctx, tt.selector, watcher)
// perform updates
go func() {
time.Sleep(pauseTime)
// changing a flag default variant should trigger an update
store.Update(sourceA, map[string]model.Flag{
"flagA": {Key: "flagA", DefaultVariant: "on"},
}, model.Metadata{})
time.Sleep(pauseTime)
// changing a flag default variant should trigger an update
store.Update(sourceB, map[string]model.Flag{
"flagB": {Key: "flagB", DefaultVariant: "on", Metadata: model.Metadata{"flagSetId": myFlagSetId}},
}, model.Metadata{})
time.Sleep(pauseTime)
// removing a flag set id should trigger an update (even for flag set id selectors; it should remove the flag from the set)
store.Update(sourceB, map[string]model.Flag{
"flagB": {Key: "flagB", DefaultVariant: "on"},
}, model.Metadata{})
time.Sleep(pauseTime)
// adding a flag set id should trigger an update
store.Update(sourceB, map[string]model.Flag{
"flagB": {Key: "flagB", DefaultVariant: "on", Metadata: model.Metadata{"flagSetId": myFlagSetId}},
}, model.Metadata{})
}()
updates := 0
for {
select {
case <-time.After(timeout):
assert.Equal(t, tt.wantUpdates, updates, "expected %d updates, got %d", tt.wantUpdates, updates)
cancel()
_, open := <-watcher
assert.False(t, open, "watcher channel should be closed after cancel")
return
case q := <-watcher:
if q.Flags != nil {
updates++
}
}
}
})
}
}
func TestQueryMetadata(t *testing.T) {
sourceA := "sourceA"
otherSource := "otherSource"
nonExistingFlagSetId := "nonExistingFlagSetId"
var sources = []string{sourceA}
sourceAFlags := map[string]model.Flag{
"flagA": {Key: "flagA", DefaultVariant: "off"},
"flagB": {Key: "flagB", DefaultVariant: "on"},
}
store, err := NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
// setup initial flags
store.Update(sourceA, sourceAFlags, model.Metadata{})
selector := NewSelector("source=" + otherSource + ",flagSetId=" + nonExistingFlagSetId)
_, metadata, _ := store.GetAll(context.Background(), &selector)
assert.Equal(t, metadata, model.Metadata{"source": otherSource, "flagSetId": nonExistingFlagSetId}, "metadata did not match expected")
selector = NewSelector("source=" + otherSource + ",flagSetId=" + nonExistingFlagSetId)
_, metadata, _ = store.Get(context.Background(), "key", &selector)
assert.Equal(t, metadata, model.Metadata{"source": otherSource, "flagSetId": nonExistingFlagSetId}, "metadata did not match expected")
}

View File

@ -88,7 +88,7 @@ func (hs *Sync) sync(ctx context.Context, dataSync chan<- sync.DataSync, skipChe
if err != nil {
return fmt.Errorf("couldn't get object attributes: %v", err)
}
if hs.lastUpdated == updated {
if hs.lastUpdated.Equal(updated) {
hs.Logger.Debug("configuration hasn't changed, skipping fetching full object")
return nil
}
@ -104,7 +104,7 @@ func (hs *Sync) sync(ctx context.Context, dataSync chan<- sync.DataSync, skipChe
if !skipCheckingModTime {
hs.lastUpdated = updated
}
dataSync <- sync.DataSync{FlagData: msg, Source: hs.Bucket + hs.Object, Type: sync.ALL}
dataSync <- sync.DataSync{FlagData: msg, Source: hs.Bucket + hs.Object}
return nil
}

View File

@ -52,7 +52,7 @@ func NewFileSync(uri string, watchType string, logger *logger.Logger) *Sync {
const defaultState = "{}"
func (fs *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error {
fs.sendDataSync(ctx, sync.ALL, dataSync)
fs.sendDataSync(ctx, dataSync)
return nil
}
@ -94,7 +94,7 @@ func (fs *Sync) setReady(val bool) {
//nolint:funlen
func (fs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
defer fs.watcher.Close()
fs.sendDataSync(ctx, sync.ALL, dataSync)
fs.sendDataSync(ctx, dataSync)
fs.setReady(true)
fs.Logger.Info(fmt.Sprintf("watching filepath: %s", fs.URI))
for {
@ -108,7 +108,7 @@ func (fs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
fs.Logger.Info(fmt.Sprintf("filepath event: %s %s", event.Name, event.Op.String()))
switch {
case event.Has(fsnotify.Create) || event.Has(fsnotify.Write):
fs.sendDataSync(ctx, sync.ALL, dataSync)
fs.sendDataSync(ctx, dataSync)
case event.Has(fsnotify.Remove):
// K8s exposes config maps as symlinks.
// Updates cause a remove event, we need to re-add the watcher in this case.
@ -116,20 +116,20 @@ func (fs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
if err != nil {
// the watcher could not be re-added, so the file must have been deleted
fs.Logger.Error(fmt.Sprintf("error restoring watcher, file may have been deleted: %s", err.Error()))
fs.sendDataSync(ctx, sync.DELETE, dataSync)
fs.sendDataSync(ctx, dataSync)
continue
}
// Counterintuitively, remove events are the only meaningful ones seen in K8s.
// K8s handles mounted ConfigMap updates by modifying symbolic links, which is an atomic operation.
// At the point the remove event is fired, we have our new data, so we can send it down the channel.
fs.sendDataSync(ctx, sync.ALL, dataSync)
fs.sendDataSync(ctx, dataSync)
case event.Has(fsnotify.Chmod):
// on linux the REMOVE event will not fire until all file descriptors are closed, this cannot happen
// while the file is being watched, os.Stat is used here to infer deletion
if _, err := os.Stat(fs.URI); errors.Is(err, os.ErrNotExist) {
fs.Logger.Error(fmt.Sprintf("file has been deleted: %s", err.Error()))
fs.sendDataSync(ctx, sync.DELETE, dataSync)
fs.sendDataSync(ctx, dataSync)
}
}
@ -147,14 +147,8 @@ func (fs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
}
}
func (fs *Sync) sendDataSync(ctx context.Context, syncType sync.Type, dataSync chan<- sync.DataSync) {
fs.Logger.Debug(fmt.Sprintf("Configuration %s: %s", fs.URI, syncType.String()))
if syncType == sync.DELETE {
// Skip fetching and emit default state to avoid EOF errors
dataSync <- sync.DataSync{FlagData: defaultState, Source: fs.URI, Type: syncType}
return
}
func (fs *Sync) sendDataSync(ctx context.Context, dataSync chan<- sync.DataSync) {
fs.Logger.Debug(fmt.Sprintf("Data sync received for %s", fs.URI))
msg := defaultState
m, err := fs.fetch(ctx)
@ -167,7 +161,7 @@ func (fs *Sync) sendDataSync(ctx context.Context, syncType sync.Type, dataSync c
msg = m
}
dataSync <- sync.DataSync{FlagData: msg, Source: fs.URI, Type: syncType}
dataSync <- sync.DataSync{FlagData: msg, Source: fs.URI}
}
func (fs *Sync) fetch(_ context.Context) (string, error) {

View File

@ -26,7 +26,6 @@ func TestSimpleReSync(t *testing.T) {
expectedDataSync := sync.DataSync{
FlagData: "hello",
Source: source,
Type: sync.ALL,
}
handler := Sync{
URI: source,
@ -76,7 +75,6 @@ func TestSimpleSync(t *testing.T) {
{
FlagData: fetchFileContents,
Source: fmt.Sprintf("%s/%s", readDirName, fetchFileName),
Type: sync.ALL,
},
},
},
@ -94,12 +92,10 @@ func TestSimpleSync(t *testing.T) {
{
FlagData: fetchFileContents,
Source: fmt.Sprintf("%s/%s", updateDirName, fetchFileName),
Type: sync.ALL,
},
{
FlagData: "new content",
Source: fmt.Sprintf("%s/%s", updateDirName, fetchFileName),
Type: sync.ALL,
},
},
},
@ -117,12 +113,10 @@ func TestSimpleSync(t *testing.T) {
{
FlagData: fetchFileContents,
Source: fmt.Sprintf("%s/%s", deleteDirName, fetchFileName),
Type: sync.ALL,
},
{
FlagData: defaultState,
Source: fmt.Sprintf("%s/%s", deleteDirName, fetchFileName),
Type: sync.DELETE,
},
},
},
@ -172,9 +166,6 @@ func TestSimpleSync(t *testing.T) {
if data.Source != syncEvent.Source {
t.Errorf("expected source: %s, but received source: %s", syncEvent.Source, data.Source)
}
if data.Type != syncEvent.Type {
t.Errorf("expected type: %b, but received type: %b", syncEvent.Type, data.Type)
}
case <-time.After(10 * time.Second):
t.Errorf("event not found, timeout out after 10 seconds")
}

View File

@ -106,7 +106,6 @@ func (g *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error
dataSync <- sync.DataSync{
FlagData: res.GetFlagConfiguration(),
Source: g.URI,
Type: sync.ALL,
}
return nil
}
@ -200,10 +199,10 @@ func (g *Sync) handleFlagSync(stream syncv1grpc.FlagSyncService_SyncFlagsClient,
}
dataSync <- sync.DataSync{
FlagData: data.FlagConfiguration,
Source: g.URI,
Selector: g.Selector,
Type: sync.ALL,
FlagData: data.FlagConfiguration,
SyncContext: data.SyncContext,
Source: g.URI,
Selector: g.Selector,
}
g.Logger.Debug("received full configuration payload")

View File

@ -16,7 +16,6 @@ import (
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/sync"
credendialsmock "github.com/open-feature/flagd/core/pkg/sync/grpc/credentials/mock"
grpcmock "github.com/open-feature/flagd/core/pkg/sync/grpc/mock"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"go.uber.org/zap"
@ -26,6 +25,8 @@ import (
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
)
func Test_InitWithMockCredentialBuilder(t *testing.T) {
@ -122,7 +123,6 @@ func Test_ReSyncTests(t *testing.T) {
notifications: []sync.DataSync{
{
FlagData: "success",
Type: sync.ALL,
},
},
shouldError: false,
@ -179,9 +179,6 @@ func Test_ReSyncTests(t *testing.T) {
for _, expected := range test.notifications {
out := <-syncChan
if expected.Type != out.Type {
t.Errorf("Returned sync type = %v, wanted %v", out.Type, expected.Type)
}
if expected.FlagData != out.FlagData {
t.Errorf("Returned sync data = %v, wanted %v", out.FlagData, expected.FlagData)
@ -195,98 +192,14 @@ func Test_ReSyncTests(t *testing.T) {
}
}
func TestSync_BasicFlagSyncStates(t *testing.T) {
grpcSyncImpl := Sync{
URI: "grpc://test",
ProviderID: "",
Logger: logger.NewLogger(nil, false),
}
mockError := errors.New("could not sync")
tests := []struct {
name string
stream syncv1grpc.FlagSyncService_SyncFlagsClient
setup func(t *testing.T, client *grpcmock.MockFlagSyncServiceClient, clientResponse *grpcmock.MockFlagSyncServiceClientResponse)
want sync.Type
wantError error
ready bool
}{
{
name: "State All maps to Sync All",
setup: func(t *testing.T, client *grpcmock.MockFlagSyncServiceClient, clientResponse *grpcmock.MockFlagSyncServiceClientResponse) {
client.EXPECT().SyncFlags(gomock.Any(), gomock.Any(), gomock.Any()).Return(clientResponse, nil)
gomock.InOrder(
clientResponse.EXPECT().Recv().Return(
&v1.SyncFlagsResponse{
FlagConfiguration: "{}",
},
nil,
),
clientResponse.EXPECT().Recv().Return(
nil, io.EOF,
),
)
},
want: sync.ALL,
ready: true,
},
{
name: "Error during flag sync",
setup: func(t *testing.T, client *grpcmock.MockFlagSyncServiceClient, clientResponse *grpcmock.MockFlagSyncServiceClientResponse) {
client.EXPECT().SyncFlags(gomock.Any(), gomock.Any(), gomock.Any()).Return(clientResponse, nil)
clientResponse.EXPECT().Recv().Return(
nil,
mockError,
)
},
ready: true,
want: -1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
syncChan := make(chan sync.DataSync, 1)
mockClient := grpcmock.NewMockFlagSyncServiceClient(ctrl)
mockClientResponse := grpcmock.NewMockFlagSyncServiceClientResponse(ctrl)
test.setup(t, mockClient, mockClientResponse)
waitChan := make(chan struct{})
go func() {
grpcSyncImpl.client = mockClient
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err := grpcSyncImpl.Sync(ctx, syncChan)
if err != nil {
t.Errorf("Error handling flag sync: %v", err)
}
close(waitChan)
}()
<-waitChan
if test.want < 0 {
require.Empty(t, syncChan)
return
}
data := <-syncChan
if grpcSyncImpl.IsReady() != test.ready {
t.Errorf("expected grpcSyncImpl.ready to be: '%v', got: '%v'", test.ready, grpcSyncImpl.ready)
}
if data.Type != test.want {
t.Errorf("Returned data sync state = %v, wanted %v", data.Type, test.want)
}
})
}
}
func Test_StreamListener(t *testing.T) {
const target = "localBufCon"
metadata, err := structpb.NewStruct(map[string]any{"sources": "A,B,C"})
if err != nil {
t.Fatalf("Failed to create sync context: %v", err)
}
tests := []struct {
name string
input []serverPayload
@ -301,8 +214,8 @@ func Test_StreamListener(t *testing.T) {
},
output: []sync.DataSync{
{
FlagData: "{\"flags\": {}}",
Type: sync.ALL,
FlagData: "{\"flags\": {}}",
SyncContext: metadata,
},
},
},
@ -318,12 +231,12 @@ func Test_StreamListener(t *testing.T) {
},
output: []sync.DataSync{
{
FlagData: "{}",
Type: sync.ALL,
FlagData: "{}",
SyncContext: metadata,
},
{
FlagData: "{\"flags\": {}}",
Type: sync.ALL,
FlagData: "{\"flags\": {}}",
SyncContext: metadata,
},
},
},
@ -376,13 +289,13 @@ func Test_StreamListener(t *testing.T) {
for _, expected := range test.output {
out := <-syncChan
if expected.Type != out.Type {
t.Errorf("Returned sync type = %v, wanted %v", out.Type, expected.Type)
}
if expected.FlagData != out.FlagData {
t.Errorf("Returned sync data = %v, wanted %v", out.FlagData, expected.FlagData)
}
if !proto.Equal(expected.SyncContext, out.SyncContext) {
t.Errorf("Returned sync context = %v, wanted = %v", out.SyncContext, expected.SyncContext)
}
}
// channel must be empty
@ -466,8 +379,7 @@ func Test_SyncRetry(t *testing.T) {
// Setup
target := "grpc://local"
bufListener := bufconn.Listen(1)
expectType := sync.ALL
emptyFlagData := "{}"
// buffer based server. response ignored purposefully
bServer := bufferedServer{listener: bufListener, mockResponses: []serverPayload{
@ -521,7 +433,7 @@ func Test_SyncRetry(t *testing.T) {
t.Errorf("timeout waiting for conditions to fulfil")
break
case data := <-syncChan:
if data.Type != expectType {
if data.FlagData != emptyFlagData {
t.Errorf("sync start error: %s", err.Error())
}
}
@ -541,9 +453,9 @@ func Test_SyncRetry(t *testing.T) {
case <-tCtx.Done():
cancelFunc()
t.Error("timeout waiting for conditions to fulfil")
case rsp := <-syncChan:
if rsp.Type != expectType {
t.Errorf("expected response: %s, but got: %s", expectType, rsp.Type)
case data := <-syncChan:
if data.FlagData != emptyFlagData {
t.Errorf("sync start error: %s", err.Error())
}
}
}
@ -575,8 +487,10 @@ type bufferedServer struct {
func (b *bufferedServer) SyncFlags(_ *v1.SyncFlagsRequest, stream syncv1grpc.FlagSyncService_SyncFlagsServer) error {
for _, response := range b.mockResponses {
metadata, _ := structpb.NewStruct(map[string]any{"sources": "A,B,C"})
err := stream.Send(&v1.SyncFlagsResponse{
FlagConfiguration: response.flags,
SyncContext: metadata,
})
if err != nil {
fmt.Printf("Error with stream: %s", err.Error())

View File

@ -27,6 +27,7 @@ type Sync struct {
AuthHeader string
Interval uint32
ready bool
eTag string
}
// Client defines the behaviour required of a http client
@ -42,11 +43,11 @@ type Cron interface {
}
func (hs *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error {
msg, err := hs.Fetch(ctx)
msg, _, err := hs.fetchBody(ctx, true)
if err != nil {
return err
}
dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI, Type: sync.ALL}
dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI}
return nil
}
@ -63,7 +64,7 @@ func (hs *Sync) IsReady() bool {
func (hs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
// Initial fetch
fetch, err := hs.Fetch(ctx)
fetch, _, err := hs.fetchBody(ctx, true)
if err != nil {
return err
}
@ -74,43 +75,30 @@ func (hs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
hs.Logger.Debug(fmt.Sprintf("polling %s every %d seconds", hs.URI, hs.Interval))
_ = hs.Cron.AddFunc(fmt.Sprintf("*/%d * * * *", hs.Interval), func() {
hs.Logger.Debug(fmt.Sprintf("fetching configuration from %s", hs.URI))
body, err := hs.fetchBodyFromURL(ctx, hs.URI)
previousBodySHA := hs.LastBodySHA
body, noChange, err := hs.fetchBody(ctx, false)
if err != nil {
hs.Logger.Error(err.Error())
hs.Logger.Error(fmt.Sprintf("error fetching: %s", err.Error()))
return
}
if body == "" {
if body == "" && !noChange {
hs.Logger.Debug("configuration deleted")
} else {
if hs.LastBodySHA == "" {
hs.Logger.Debug("new configuration created")
msg, err := hs.Fetch(ctx)
if err != nil {
hs.Logger.Error(fmt.Sprintf("error fetching: %s", err.Error()))
} else {
dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI, Type: sync.ALL}
}
} else {
currentSHA := hs.generateSha([]byte(body))
if hs.LastBodySHA != currentSHA {
hs.Logger.Debug("configuration modified")
msg, err := hs.Fetch(ctx)
if err != nil {
hs.Logger.Error(fmt.Sprintf("error fetching: %s", err.Error()))
} else {
dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI, Type: sync.ALL}
}
}
return
}
hs.LastBodySHA = currentSHA
}
if previousBodySHA == "" {
hs.Logger.Debug("configuration created")
dataSync <- sync.DataSync{FlagData: body, Source: hs.URI}
} else if previousBodySHA != hs.LastBodySHA {
hs.Logger.Debug("configuration updated")
dataSync <- sync.DataSync{FlagData: body, Source: hs.URI}
}
})
hs.Cron.Start()
dataSync <- sync.DataSync{FlagData: fetch, Source: hs.URI, Type: sync.ALL}
dataSync <- sync.DataSync{FlagData: fetch, Source: hs.URI}
<-ctx.Done()
hs.Cron.Stop()
@ -118,10 +106,14 @@ func (hs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
return nil
}
func (hs *Sync) fetchBodyFromURL(ctx context.Context, url string) (string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(nil))
func (hs *Sync) fetchBody(ctx context.Context, fetchAll bool) (string, bool, error) {
if hs.URI == "" {
return "", false, errors.New("no HTTP URL string set")
}
req, err := http.NewRequestWithContext(ctx, "GET", hs.URI, bytes.NewBuffer(nil))
if err != nil {
return "", fmt.Errorf("error creating request for url %s: %w", url, err)
return "", false, fmt.Errorf("error creating request for url %s: %w", hs.URI, err)
}
req.Header.Add("Accept", "application/json")
@ -134,32 +126,50 @@ func (hs *Sync) fetchBodyFromURL(ctx context.Context, url string) (string, error
req.Header.Set("Authorization", bearer)
}
if hs.eTag != "" && !fetchAll {
req.Header.Set("If-None-Match", hs.eTag)
}
resp, err := hs.Client.Do(req)
if err != nil {
return "", fmt.Errorf("error calling endpoint %s: %w", url, err)
return "", false, fmt.Errorf("error calling endpoint %s: %w", hs.URI, err)
}
defer func() {
err = resp.Body.Close()
if err != nil {
hs.Logger.Debug(fmt.Sprintf("error closing the response body: %s", err.Error()))
hs.Logger.Error(fmt.Sprintf("error closing the response body: %s", err.Error()))
}
}()
if resp.StatusCode == 304 {
hs.Logger.Debug("no changes detected")
return "", true, nil
}
statusOK := resp.StatusCode >= 200 && resp.StatusCode < 300
if !statusOK {
return "", fmt.Errorf("error fetching from url %s: %s", url, resp.Status)
return "", false, fmt.Errorf("error fetching from url %s: %s", hs.URI, resp.Status)
}
if resp.Header.Get("ETag") != "" {
hs.eTag = resp.Header.Get("ETag")
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("unable to read body to bytes: %w", err)
return "", false, fmt.Errorf("unable to read body to bytes: %w", err)
}
json, err := utils.ConvertToJSON(body, getFileExtensions(url), resp.Header.Get("Content-Type"))
json, err := utils.ConvertToJSON(body, getFileExtensions(hs.URI), resp.Header.Get("Content-Type"))
if err != nil {
return "", fmt.Errorf("error converting response body to json: %w", err)
return "", false, fmt.Errorf("error converting response body to json: %w", err)
}
return json, nil
if json != "" {
hs.LastBodySHA = hs.generateSha([]byte(body))
}
return json, false, nil
}
// getFileExtensions returns the file extension from the URL path
@ -179,17 +189,6 @@ func (hs *Sync) generateSha(body []byte) string {
}
func (hs *Sync) Fetch(ctx context.Context) (string, error) {
if hs.URI == "" {
return "", errors.New("no HTTP URL string set")
}
body, err := hs.fetchBodyFromURL(ctx, hs.URI)
if err != nil {
return "", err
}
if body != "" {
hs.LastBodySHA = hs.generateSha([]byte(body))
}
return body, nil
body, _, err := hs.fetchBody(ctx, false)
return body, err
}

View File

@ -5,7 +5,6 @@ import (
"io"
"log"
"net/http"
"reflect"
"strings"
"testing"
"time"
@ -111,6 +110,7 @@ func TestHTTPSync_Fetch(t *testing.T) {
uri string
bearerToken string
authHeader string
eTagHeader string
lastBodySHA string
handleResponse func(*testing.T, Sync, string, error)
}{
@ -241,6 +241,85 @@ func TestHTTPSync_Fetch(t *testing.T) {
}
},
},
"not modified response etag matched": {
setup: func(t *testing.T, client *syncmock.MockClient) {
expectedIfNoneMatch := `"1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef"`
client.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) {
actualIfNoneMatch := req.Header.Get("If-None-Match")
if actualIfNoneMatch != expectedIfNoneMatch {
t.Fatalf("expected If-None-Match header to be '%s', got %s", expectedIfNoneMatch, actualIfNoneMatch)
}
return &http.Response{
Header: map[string][]string{"ETag": {expectedIfNoneMatch}},
Body: io.NopCloser(strings.NewReader("")),
StatusCode: http.StatusNotModified,
}, nil
})
},
uri: "http://localhost",
eTagHeader: `"1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef"`,
handleResponse: func(t *testing.T, httpSync Sync, _ string, err error) {
if err != nil {
t.Fatalf("fetch: %v", err)
}
expectedLastBodySHA := ""
expectedETag := `"1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef"`
if httpSync.LastBodySHA != expectedLastBodySHA {
t.Errorf(
"expected last body sha to be: '%s', got: '%s'", expectedLastBodySHA, httpSync.LastBodySHA,
)
}
if httpSync.eTag != expectedETag {
t.Errorf(
"expected last etag to be: '%s', got: '%s'", expectedETag, httpSync.eTag,
)
}
},
},
"modified response etag mismatched": {
setup: func(t *testing.T, client *syncmock.MockClient) {
expectedIfNoneMatch := `"1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef"`
client.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) {
actualIfNoneMatch := req.Header.Get("If-None-Match")
if actualIfNoneMatch != expectedIfNoneMatch {
t.Fatalf("expected If-None-Match header to be '%s', got %s", expectedIfNoneMatch, actualIfNoneMatch)
}
newContent := "\"Hey there!\""
newETag := `"c2e01ce63d90109c4c7f4f6dcea97ed1bb2b51e3647f36caf5acbe27413a24bb"`
return &http.Response{
Header: map[string][]string{
"Content-Type": {"application/json"},
"Etag": {newETag},
},
Body: io.NopCloser(strings.NewReader(newContent)),
StatusCode: http.StatusOK,
}, nil
})
},
uri: "http://localhost",
eTagHeader: `"1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef"`,
handleResponse: func(t *testing.T, httpSync Sync, _ string, err error) {
if err != nil {
t.Fatalf("fetch: %v", err)
}
expectedLastBodySHA := "wuAc5j2QEJxMf09tzql-0bsrUeNkfzbK9ay-J0E6JLs="
expectedETag := `"c2e01ce63d90109c4c7f4f6dcea97ed1bb2b51e3647f36caf5acbe27413a24bb"`
if httpSync.LastBodySHA != expectedLastBodySHA {
t.Errorf(
"expected last body sha to be: '%s', got: '%s'", expectedLastBodySHA, httpSync.LastBodySHA,
)
}
if httpSync.eTag != expectedETag {
t.Errorf(
"expected last etag to be: '%s', got: '%s'", expectedETag, httpSync.eTag,
)
}
},
},
}
for name, tt := range tests {
@ -256,6 +335,7 @@ func TestHTTPSync_Fetch(t *testing.T) {
AuthHeader: tt.authHeader,
LastBodySHA: tt.lastBodySHA,
Logger: logger.NewLogger(nil, false),
eTag: tt.eTagHeader,
}
fetched, err := httpSync.Fetch(context.Background())
@ -289,6 +369,8 @@ func TestSync_Init(t *testing.T) {
func TestHTTPSync_Resync(t *testing.T) {
ctrl := gomock.NewController(t)
source := "http://localhost"
emptyFlagData := "{}"
tests := map[string]struct {
setup func(t *testing.T, client *syncmock.MockClient)
@ -303,11 +385,11 @@ func TestHTTPSync_Resync(t *testing.T) {
setup: func(_ *testing.T, client *syncmock.MockClient) {
client.EXPECT().Do(gomock.Any()).Return(&http.Response{
Header: map[string][]string{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader("")),
Body: io.NopCloser(strings.NewReader(emptyFlagData)),
StatusCode: http.StatusOK,
}, nil)
},
uri: "http://localhost",
uri: source,
handleResponse: func(t *testing.T, _ Sync, fetched string, err error) {
if err != nil {
t.Fatalf("fetch: %v", err)
@ -320,9 +402,8 @@ func TestHTTPSync_Resync(t *testing.T) {
wantErr: false,
wantNotifications: []sync.DataSync{
{
Type: sync.ALL,
FlagData: "",
Source: "",
FlagData: emptyFlagData,
Source: source,
},
},
},
@ -364,8 +445,8 @@ func TestHTTPSync_Resync(t *testing.T) {
for _, dataSync := range tt.wantNotifications {
select {
case x := <-d:
if !reflect.DeepEqual(x.String(), dataSync.String()) {
t.Error("unexpected datasync received", x, dataSync)
if x.FlagData != dataSync.FlagData || x.Source != dataSync.Source {
t.Errorf("unexpected datasync received %v vs %v", x, dataSync)
}
case <-time.After(2 * time.Second):
t.Error("expected datasync not received", dataSync)

View File

@ -2,37 +2,10 @@ package sync
import (
"context"
"google.golang.org/protobuf/types/known/structpb"
)
type Type int
// Type of the sync operation
const (
// ALL - All flags of sync provider. This is the default if unset due to primitive default
ALL Type = iota
// ADD - Additional flags from sync provider
ADD
// UPDATE - Update for flag(s) previously provided
UPDATE
// DELETE - Delete for flag(s) previously provided
DELETE
)
func (t Type) String() string {
switch t {
case ALL:
return "ALL"
case ADD:
return "ADD"
case UPDATE:
return "UPDATE"
case DELETE:
return "DELETE"
default:
return "UNKNOWN"
}
}
/*
ISync implementations watch for changes in the flag sources (HTTP backend, local file, K8s CRDs ...),fetch the latest
value and communicate to the Runtime with DataSync channel
@ -55,10 +28,10 @@ type ISync interface {
// DataSync is the data contract between Runtime and sync implementations
type DataSync struct {
FlagData string
Source string
Selector string
Type
FlagData string
SyncContext *structpb.Struct
Source string
Selector string
}
// SourceConfig is configuration option for flagd. This maps to startup parameter sources

View File

@ -57,7 +57,7 @@ func (k *Sync) ReSync(ctx context.Context, dataSync chan<- sync.DataSync) error
if err != nil {
return fmt.Errorf("unable to fetch flag configuration: %w", err)
}
dataSync <- sync.DataSync{FlagData: fetch, Source: k.URI, Type: sync.ALL}
dataSync <- sync.DataSync{FlagData: fetch, Source: k.URI}
return nil
}
@ -97,7 +97,7 @@ func (k *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
return err
}
dataSync <- sync.DataSync{FlagData: fetch, Source: k.URI, Type: sync.ALL}
dataSync <- sync.DataSync{FlagData: fetch, Source: k.URI}
notifies := make(chan INotify)
@ -136,7 +136,7 @@ func (k *Sync) watcher(ctx context.Context, notifies chan INotify, dataSync chan
continue
}
dataSync <- sync.DataSync{FlagData: msg, Source: k.URI, Type: sync.ALL}
dataSync <- sync.DataSync{FlagData: msg, Source: k.URI}
case DefaultEventTypeModify:
k.logger.Debug("Configuration modified")
msg, err := k.fetch(ctx)
@ -145,7 +145,7 @@ func (k *Sync) watcher(ctx context.Context, notifies chan INotify, dataSync chan
continue
}
dataSync <- sync.DataSync{FlagData: msg, Source: k.URI, Type: sync.ALL}
dataSync <- sync.DataSync{FlagData: msg, Source: k.URI}
case DefaultEventTypeDelete:
k.logger.Debug("configuration deleted")
case DefaultEventTypeReady:

View File

@ -607,6 +607,7 @@ func TestInit(t *testing.T) {
func TestSync_ReSync(t *testing.T) {
const name = "myFF"
const ns = "myNS"
const payload = "{\"flags\":null}"
s := runtime.NewScheme()
ff := &unstructured.Unstructured{}
ff.SetUnstructuredContent(getCFG(name, ns))
@ -668,8 +669,8 @@ func TestSync_ReSync(t *testing.T) {
i := tt.countMsg
for i > 0 {
d := <-dataChannel
if d.Type != sync.ALL {
t.Errorf("Expected %v, got %v", sync.ALL, d)
if d.FlagData != payload {
t.Errorf("Expected %v, got %v", payload, d.FlagData)
}
i--
}

View File

@ -21,7 +21,7 @@ import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@ -30,7 +30,6 @@ import (
const (
metricsExporterOtel = "otel"
exportInterval = 2 * time.Second
)
type CollectorConfig struct {
@ -196,7 +195,7 @@ func buildMetricReader(ctx context.Context, cfg Config) (metric.Reader, error) {
return nil, fmt.Errorf("error creating otel metric exporter: %w", err)
}
return metric.NewPeriodicReader(otelExporter, metric.WithInterval(exportInterval)), nil
return metric.NewPeriodicReader(otelExporter), nil
}
// buildOtlpExporter is a helper to build grpc backed otlp trace exporter

View File

@ -12,7 +12,7 @@ import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
)

View File

@ -10,7 +10,7 @@ import (
"go.opentelemetry.io/otel/sdk/instrumentation"
msdk "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
)
const (
@ -19,15 +19,15 @@ const (
FeatureFlagReasonKey = attribute.Key("feature_flag.reason")
ExceptionTypeKey = attribute.Key("ExceptionTypeKeyName")
httpRequestDurationMetric = "http.server.duration"
httpResponseSizeMetric = "http.server.response.size"
httpRequestDurationMetric = "http.server.request.duration"
httpResponseSizeMetric = "http.server.response.body.size"
httpActiveRequestsMetric = "http.server.active_requests"
impressionMetric = "feature_flag." + ProviderName + ".impression"
reasonMetric = "feature_flag." + ProviderName + ".evaluation.reason"
reasonMetric = "feature_flag." + ProviderName + ".result.reason"
)
type IMetricsRecorder interface {
HTTPAttributes(svcName, url, method, code string) []attribute.KeyValue
HTTPAttributes(svcName, url, method, code, scheme string) []attribute.KeyValue
HTTPRequestDuration(ctx context.Context, duration time.Duration, attrs []attribute.KeyValue)
HTTPResponseSize(ctx context.Context, sizeBytes int64, attrs []attribute.KeyValue)
InFlightRequestStart(ctx context.Context, attrs []attribute.KeyValue)
@ -38,7 +38,7 @@ type IMetricsRecorder interface {
type NoopMetricsRecorder struct{}
func (NoopMetricsRecorder) HTTPAttributes(_, _, _, _ string) []attribute.KeyValue {
func (NoopMetricsRecorder) HTTPAttributes(_, _, _, _, _ string) []attribute.KeyValue {
return []attribute.KeyValue{}
}
@ -68,12 +68,13 @@ type MetricsRecorder struct {
reasons metric.Int64Counter
}
func (r MetricsRecorder) HTTPAttributes(svcName, url, method, code string) []attribute.KeyValue {
func (r MetricsRecorder) HTTPAttributes(svcName, url, method, code, scheme string) []attribute.KeyValue {
return []attribute.KeyValue{
semconv.ServiceNameKey.String(svcName),
semconv.HTTPURLKey.String(url),
semconv.HTTPMethodKey.String(method),
semconv.HTTPStatusCodeKey.String(code),
semconv.HTTPRouteKey.String(url),
semconv.HTTPRequestMethodKey.String(method),
semconv.HTTPResponseStatusCodeKey.String(code),
semconv.URLSchemeKey.String(scheme),
}
}

View File

@ -10,7 +10,7 @@ import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.13.0"
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
)
const svcName = "mySvc"
@ -38,9 +38,10 @@ func TestHTTPAttributes(t *testing.T) {
},
want: []attribute.KeyValue{
semconv.ServiceNameKey.String(""),
semconv.HTTPURLKey.String(""),
semconv.HTTPMethodKey.String(""),
semconv.HTTPStatusCodeKey.String(""),
semconv.HTTPRouteKey.String(""),
semconv.HTTPRequestMethodKey.String(""),
semconv.HTTPResponseStatusCodeKey.String(""),
semconv.URLSchemeKey.String("http"),
},
},
{
@ -53,9 +54,10 @@ func TestHTTPAttributes(t *testing.T) {
},
want: []attribute.KeyValue{
semconv.ServiceNameKey.String("myService"),
semconv.HTTPURLKey.String("#123"),
semconv.HTTPMethodKey.String("POST"),
semconv.HTTPStatusCodeKey.String("300"),
semconv.HTTPRouteKey.String("#123"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPResponseStatusCodeKey.String("300"),
semconv.URLSchemeKey.String("http"),
},
},
{
@ -68,16 +70,17 @@ func TestHTTPAttributes(t *testing.T) {
},
want: []attribute.KeyValue{
semconv.ServiceNameKey.String("!@#$%^&*()_+|}{[];',./<>"),
semconv.HTTPURLKey.String(""),
semconv.HTTPMethodKey.String(""),
semconv.HTTPStatusCodeKey.String(""),
semconv.HTTPRouteKey.String(""),
semconv.HTTPRequestMethodKey.String(""),
semconv.HTTPResponseStatusCodeKey.String(""),
semconv.URLSchemeKey.String("http"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rec := MetricsRecorder{}
res := rec.HTTPAttributes(tt.req.Service, tt.req.ID, tt.req.Method, tt.req.Code)
res := rec.HTTPAttributes(tt.req.Service, tt.req.ID, tt.req.Method, tt.req.Code, "http")
require.Equal(t, tt.want, res)
})
}
@ -208,7 +211,7 @@ func TestMetrics(t *testing.T) {
// some really simple tests just to make sure all methods are actually implemented and nothing panics
func TestNoopMetricsRecorder_HTTPAttributes(t *testing.T) {
no := NoopMetricsRecorder{}
got := no.HTTPAttributes("", "", "", "")
got := no.HTTPAttributes("", "", "", "", "")
require.Empty(t, got)
}

View File

@ -2,7 +2,7 @@ package telemetry
import (
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
)
// utils contain common utilities to help with telemetry
@ -14,7 +14,7 @@ const provider = "flagd"
func SemConvFeatureFlagAttributes(ffKey string, ffVariant string) []attribute.KeyValue {
return []attribute.KeyValue{
semconv.FeatureFlagKey(ffKey),
semconv.FeatureFlagVariant(ffVariant),
semconv.FeatureFlagResultVariant(ffVariant),
semconv.FeatureFlagProviderName(provider),
}
}

View File

@ -4,7 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
semconv "go.opentelemetry.io/otel/semconv/v1.18.0"
semconv "go.opentelemetry.io/otel/semconv/v1.34.0"
)
func TestSemConvFeatureFlagAttributes(t *testing.T) {
@ -35,7 +35,7 @@ func TestSemConvFeatureFlagAttributes(t *testing.T) {
case semconv.FeatureFlagKeyKey:
require.Equal(t, test.key, attribute.Value.AsString(),
"expected flag key: %s, but received: %s", test.key, attribute.Value.AsString())
case semconv.FeatureFlagVariantKey:
case semconv.FeatureFlagResultVariantKey:
require.Equal(t, test.variant, attribute.Value.AsString(),
"expected flag variant: %s, but received %s", test.variant, attribute.Value.AsString())
case semconv.FeatureFlagProviderNameKey:

View File

@ -0,0 +1,98 @@
---
# Valid statuses: draft | proposed | rejected | accepted | superseded
status: draft
author: Your Name
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
# Title
<!--
This section should be one or two paragraphs that just explains what the goal of this decision is going to be, but without diving too deeply into the "why", "why now", "how", etc.
Ensure anyone opening the document will form a clear understanding of the intent from reading this paragraph(s).
-->
## Background
<!--
The next section is the "Background" section. This section should be at least two paragraphs and can take up to a whole page in some cases.
The guiding goal of the background section is: as a newcomer to this project (new employee, team transfer), can I read the background section and follow any links to get the full context of why this change is necessary?
If you can't show a random engineer the background section and have them acquire nearly full context on the necessity for the RFC, then the background section is not full enough. To help achieve this, link to prior RFCs, discussions, and more here as necessary to provide context so you don't have to simply repeat yourself.
-->
## Requirements
<!--
This section outlines the requirements that the proposal must meet.
These requirements should be derived from the background section and should be clear, concise, and actionable.
This is where you can specify the goals and constraints that the proposal must satisfy.
This could include performance metrics, security considerations, user experience goals, and any other relevant criteria.
-->
* {Requirement 1}
* {Requirement 2}
* {Requirement 3}
* … <!-- numbers of requirements can vary -->
## Considered Options
<!--
This section lists all the options that were considered for addressing the need outlined in the background section.
Each option should be clearly defined with a descriptive title.
This provides a comprehensive overview of the solution space that was explored before making a decision.
The options will be evaluated in the proposal section, where the chosen approach is justified.
-->
* {title of option 1}
* {title of option 2}
* {title of option 3}
* … <!-- numbers of options can vary -->
## Proposal
<!--
The next required section is "Proposal" or "Goal".
Given the background above, this section proposes a solution.
This should be an overview of the "how" for the solution.
Include content like diagrams, prototypes, and high-level requirements.
-->
<!-- This is an optional element. Feel free to remove. -->
### API changes
<!--
This section should describe any API changes that are part of the proposal.
This includes any new endpoints, changes to existing endpoints, or modifications to the data model.
It should provide enough detail for developers to understand how the API will evolve and what impact it will have on existing clients.
-->
<!-- This is an optional element. Feel free to remove. -->
### Consequences
* Good, because {positive consequence, e.g., improvement of one or more desired qualities, …}
* Bad, because {negative consequence, e.g., compromising one or more desired qualities, …}
* … <!-- numbers of consequences can vary -->
### Timeline
<!--
This section outlines a high level timeline for implementing the proposal.
It should include key milestones, deadlines, and any dependencies that need to be addressed.
This helps to set expectations for the size of the change and the expected timeline for completion.
-->
<!-- This is an optional element. Feel free to remove. -->
### Open questions
* {Question 1}
* … <!-- numbers of question can vary -->
<!-- This is an optional element. Feel free to remove. -->
## More Information
<!--
This section provides additional context, evidence, or documentation to support the decision.
Use this space to provide any supplementary information that would be helpful for future readers
to fully understand the decision and its implications.
-->

View File

@ -0,0 +1,81 @@
---
status: accepted
author: @toddbaert
created: 2025-05-16
updated: --
---
# Adoption of Cucumber/Gherkin for `flagd` Testing Suite
This decision document outlines the rationale behind adopting the Cucumber/Gherkin testing framework for the `flagd` projects testing suite. The goal is to establish a clear, maintainable, and language-agnostic approach for writing integration and behavior-driven tests.
By leveraging Gherkins natural language syntax and Cucumbers mature ecosystem, we aim to improve test clarity and accessibility across teams, enabling both developers and non-developers to contribute to test case development and validation.
## Background
`flagd` is an open-source feature flagging engine that forms a core part of the OpenFeature ecosystem. As such, it includes many clients (providers) written in multiple languages and it needs robust, readable, and accessible testing frameworks that allow for scalable behavior-driven testing.
Previously, test cases for `flagd` providers were written in language-specific test frameworks, which created fragmentation and limited contributions from engineers who werent familiar with the language in question. Furthermore, the ability to validate consistent feature flag behavior across multiple SDKs and environments became increasingly important as adoption grew, and in-process evaluation was implemented.
To address this, the engineering team investigated frameworks that would enable:
- Behavior-driven development (BDD) to validate consistent flag evaluation behavior, configuration, and provider life-cycle (connection, etc).
- High cross-language support to integrate with multiple SDKs and tools.
- Ease of use for writing, understanding, enhancing and maintaining tests.
After evaluating our options and experimenting with prototypes, we adopted Cucumber with Gherkin syntax for our testing strategy.
## Requirements
- Must be supported across a wide variety of programming languages.
- Must offer mature tooling and documentation.
- Must enable the writing of easily understandable, high-level test cases.
- Must be open source.
- Should support automated integration in CI pipelines.
- Should support parameterized and reusable test definitions.
## Considered Options
- Adoption of Cucumber/Gherkin e2e testing framework
- No cross-implementation e2e testing framework (rely on unit tests)
- Custom e2e testing framework, perhaps based on csv or other tabular input/output assertions
## Proposal
We adopted the Cucumber testing framework, using Gherkin syntax to define feature specifications and test behaviors. Gherkin offers a structured and readable DSL (domain-specific language) that enables concise expression of feature behaviors in plain English, making test scenarios accessible to both technical and non-technical contributors.
We use Cucumbers tooling in combination with language bindings (e.g., Go, JavaScript, Python) to execute these scenarios across different environments and SDKs. Step definitions are implemented using the idiomatic tools of each language, while test scenarios remain shared and version-controlled.
### API changes
N/A this decision does not introduce API-level changes but applies to test infrastructure and development workflow.
### Consequences
#### Pros
- Test scenarios are readable and accessible to a broad range of contributors.
- Cucumber and Gherkin are supported in most major programming languages.
- Tests are partially decoupled from the underlying implementation language.
- Parameterized and reuseable test definitions mean new validations and assertions can often be added in providers without writing any code.
#### Cons
- Adding a new framework introduces some complexity and a learning curve.
- In some cases/runtimes, debugging failed tests in Gherkin can be more difficult than traditional unit tests.
### Timeline
N/A - this is a retrospective document, timeline was not recorded.
### Open questions
- Should we enforce Gherkin for all providers?
## More Information
- [flagd Testbed Repository](https://github.com/open-feature/flagd-testbed)
- [Cucumber Documentation](https://cucumber.io/docs/)
- [Gherkin Syntax Guide](https://cucumber.io/docs/gherkin/)
- [flagd GitHub Repository](https://github.com/open-feature/flagd)
- [OpenFeature Project Overview](https://openfeature.dev/)

View File

@ -0,0 +1,77 @@
---
status: accepted
author: @tangenti
created: 2025-06-16
updated: 2025-06-16
---
# Decouple flag sync sources and flag sets
The goal is to support dynamic flag sets for flagd providers and decouple sources and flag sets.
## Background
Flagd daemon syncs flag configurations from multiple sources. A single source provides a single config, which has an optional flag set ID that may or may not change in the following syncs of the same source.
The in-process provider uses `selector` to specify the desired source. In order to get a desired flag set, a provider has to stick to a source that provides that flag set. In this case, the flagd daemon cannot remove a source without breaking the dependant flagd providers.
Assumptions of the current model
- `flagSetId`s must be unique across different sources or the configuration is considered invalid.
- In-process providers request at most one flag set.
## Requirements
- Flagd daemon can remove a source without breaking in-process providers that depend on the flag set the source provides.
- In-process providers can select based on flag sets.
- No breaking changes for the current usage of `selector`
## Proposal
### API change
#### Flag Configuration Schema
Add an optional field `flagsetID` under `flag` or `flag.metadata`. The flag set ID cannot be specified if a flag set ID is specified for the config.
### Flagd Sync Selector
Selector will be extended for generic flags selection, starting with checking the equivalence of `source` and `flagsetID` of flags.
Example
```yaml
# Flags from the source `override`
selector: override
# Flags from the source `override`
selector: source=override
# Flags from the flag set `project-42`
selector: flagsetID=project-42
```
The semantic can later be extended with a more complex design, such as AIP-160 filter or Kubernetes selections. This is out of the scope of this ADR.
### Flagd Daemon Storage
1. Flagd will have separate stores for `flags` and `sources`
2. `selector` will be removed from the store
3. `flagSetID` will be added as part of `model.Flag` or under `model.Flag.Metadata` for better consistency with the API.
### Flags Sync
Sync server would count the extended syntax of `selector` and filter the list of flags on-the-fly answering the requests from the providers.
The existing conflict resolving based on sources remains the same. Resyncs on removing flags remains unchanged as well.
## Consequences
### The good
- One source can have multiple flag sets.
- `selector` works on a more grandular level.
- No breaking change
- Sync servers and clients now hold the same understanding of the `selector` semantic.

View File

@ -0,0 +1,144 @@
---
status: accepted
author: @tangenti
created: 2025-06-27
updated: 2025-06-27
---
# Support for Duplicate Flag Keys
This ADR proposes allowing a single sync source to provide multiple flags that share the same key. This enables greater flexibility for modularizing flag configurations.
## Background
Currently, the `flagd` [flag configuration](https://flagd.dev/schema/v0/flags.json) stores flags in a JSON object (a map), where each key must be unique. While the JSON specification technically allows duplicate keys, it's not recommended and not well-supported in the implementations.
This limitation prevents use cases for flag modularization and multi-tenancy, such as:
- **Component-based Flags:** Two different services, each with its own in-process provider, cannot independently define a flag with the same key when communicating with the same `flagd` daemon.
- **Multi-Tenant Targeting:** A single flagd daemon cannot use the same flag key with different targeting rules for different tenants
## Requirements
- Allow a single sync source to define multiple flags that have the same key.
- Flags from a sync source with the same keys can have different types and targeting rules.
- No breaking changes for the current flagd flag configuration schema or flagd sync services.
## Proposal
We will update the `flagd` flag configuration schema to support receiving flags as an **array of flag objects**. The existing schema will remain fully supported.
### API Change
#### Flag Configuration Schema
We'll add a new schema as a [subschema](https://json-schema.org/learn/glossary#subschema) to the existing flagd flag configuration schema. It will be a composite of the original schema except `flags` (`#/definitions/base`), with a new schema for `flags` that allows flags array in addition to the currently supported flags object. The existing main schema will be the composite of `#/definitions/base` and the subschema for the flags object.
```json
...
"flagsArray": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/flag"
},
{
"type": "object",
"properties": {
"key": {
"description": "Key of the flag",
"type": "string",
"minLength": 1
}
},
"required": [
"key"
]
}
]
}
},
"flagsArraySchema": {
"$id": "https://flagd.dev/schema/v0/flags.json#flagsarray",
"type": "object",
"allOf": [
{
"$ref": "#/definitions/base"
},
{
"properties": {
"flags": {
"oneOf": [
{
"$ref": "#/definitions/flagsArray"
},
{
"$ref": "#/definitions/flagsMap"
}
]
}
},
"required": [
"flags"
]
}
]
}
...
```
If the config level flag set ID is not specified, `metadata.flagSetId` of each flag will be interpreted as its flag set ID.
A flag will be uniquely identified by the composite key `(flagKey, flagSetId)`. The following three flags will be considered as three different flags.
1. `{"flagKey": "enable-feature", "flagSetId": ""}`
2. `{"flagKey": "enable-feature", "flagSetId": "default"}`
3. `{"flagKey": "enable-feature", "flagSetId": "beta"}`
### Flagd daemon
Flagd daemon will perform the JSON schema checks with the reference to `https://flagd.dev/schema/v0/flags.json#flagsarray`, allowing both flags as an object and as an array.
If the flag array contains two or more flags with the same composite key, the config will be considered as invalid.
If the request from in-process flagd providers result in a config that has duplicate flag keys, the flagd daemon will only keep one of them in the response.
### Flagd Daemon Storage
1. Flagd will have separate stores for `flags` and `sources`.
1. The `flags` store will use the composite key for flags.
1. `selector` will be removed from the store
1. `flagSetId` will be moved from `source` metadata to `flag` metadata.
### Flags Lifecycle
Currently, the flags configurations from the latest update of a source will trigger a `sync.ALL` sync. If a flag was presented in the previous configuration but not in the current configuration, it will be removed. In another word, the latest source that provides the config for a flag will take the ownership of a flag, and any subsequent configs are considered as the full states of the flags that are owned by the source.
We'll keep the same behaviors with this proposal:
1. If two sources provide the flags with the same composite key, the latest one will be stored.
2. If a flag from a source no longer presents in the latest configuration of the same source, it will be removed.
This behavior is less ideal as the ownership management depends on the ordre of the sync. This should be addressed in a separate ADR.
### Consequences
#### The good
- One source can provide flags with the same keys.
- Flag set ID no longer bound to a source, so one source can have multiple flag sets.
- No breaking change of the API definition and the API behaviors.
- No significant change on the flagd stores and how selections work.
#### The bad
- The proposal still leverages the concept of flag set in the flagd storage.
- The schema does not guarantee that flags of the same flag set from the same source will not have the same keys. This is guaranteed in the proposal of #1634.
- Compared to #1634, this proposal does not allow to define flag set wide metadata.

View File

@ -0,0 +1,155 @@
---
status: accepted
author: Todd Baert
created: 2025-06-05
updated: 2025-06-05
---
# Flag and Targeting Configuration
## Background
Feature flag systems require a flexible, safe, and portable way to express targeting rules that can evaluate contextual data to determine which variant of a feature to serve.
flagd's targeting system was designed with several key requirements:
## Requirements
- **Language agnostic**: Rules must be portable across different programming languages, ideally relying on existing expression language(s)
- **Safe evaluation**: No arbitrary code execution or system access
- **Deterministic**: Same inputs must always produce same outputs
- **Extensible**: Support for the addition of domain-specific operations relevant to feature flags
- **Developer and machine friendly**: Human-readable, easily validated, and easily serialized
## Proposal
### JSON Logic as the Foundation
flagd chose **JSON Logic** as its core evaluation engine, implementing a modified version with custom extensions.
This provides a secure, portable foundation where rules are expressed as JSON objects with operators as keys and parameters as values.
#### Benefits realized
- Rules can be stored in databases, transmitted over networks, shared between frontend/backend, and embedded in Kubernetes custom resources
- No eval() or code injection risks - computations are deterministic and sand-boxed
- Implementations exist in most languages
#### Overview
The system provides two tiers of operators:
##### Primitive JSON Logic Operators (inherited from the JSONLogic)
- Logical: `and`, `or`, `!`, `!!`
- Comparison: `==`, `!=`, `>`, `<`, etc
- Arithmetic: `+`, `-`, `*`, `/`, `%`
- Array operations: `in`, `map`, `filter`, etc
- String operations: `cat`, `substr`, etc
- Control flow: `if`
- Assignment and extraction: `var`
##### Custom flagd Extensions
- `fractional`: Deterministic percentage-based distribution using murmur3 hashing
- `starts_with`/`ends_with`: String prefix/suffix matching for common patterns
- `sem_ver`: Semantic version comparisons with standard (npm-style) operators
- `$ref`: Reference to shared evaluators for DRY principle
##### Evaluation Context and Automatic Enrichment
flagd automatically injects critical context values:
##### System-provided context
- `$flagd.flagKey`: The flag being evaluated (available v0.6.4+)
- `$flagd.timestamp`: Unix timestamp of evaluation (available v0.6.7+)
This enables sophisticated targeting rules that can reference the flag itself or time-based conditions without requiring client-side context.
##### Reason Code System for Transparency
flagd returns specific reason codes with every evaluation to indicate how the decision was made:
1. **STATIC**: Flag has no targeting rules, and can be safely cached
2. **TARGETING_MATCH**: Targeting rules matched and returned a variant
3. **DEFAULT**: Targeting rules evaluated to null, fell back to default
4. **CACHED**: Value retrieved from provider cache (RPC mode only)
5. **ERROR**: Evaluation failed due to invalid configuration
This transparency enables:
- Appropriate caching strategies (only STATIC flags are cached)
- Improved debugging, telemetry, and monitoring of flag behavior
##### Shared Evaluators for Reusability
The `$evaluators` top-level property enables shared targeting logic:
```json
{
"$evaluators": {
"isEmployee": {
"ends_with": [{"var": "email"}, "@company.com"]
}
},
"flags": {
"feature-x": {
"state": "ENABLED",
"defaultVariant": "enabled",
"variants": {
"enabled": true,
"disabled": false
},
"targeting": {
"if": [{"$ref": "isEmployee"}, "enabled", "disabled"]
}
}
}
}
```
##### Intelligent Caching Strategy
Only flags with reason **STATIC** are cached, as they have deterministic outputs. This ensures:
- Maximum cache efficiency for simple toggles
- Fresh evaluation for complex targeting rules
- Cache invalidation on configuration changes
##### Schema-Driven Configuration
Two schemas validate flag configurations:
- `https://flagd.dev/schema/v0/flags.json`: Overall flag structure
- `https://flagd.dev/schema/v0/targeting.json`: Targeting rule validation
These enable:
- IDE support with autocomplete
- Run-time and build-time validation
- Separate validation of rules and overall configuration if desired
## Considered Options
- **Custom DSL**: Would require parsers in every language
- **JavaScript/Lua evaluation**: Security risks and language lock-in
- **CEL**: limited number of implementations at time of decision, can't be directly parsed/validated when embedded in Kubernetes resources
## Consequences
### Positive
- Good, because implementations exist across languages
- Good, because, no code injection or system access possible
- Good, because combined with JSON schemas, we have rich IDE support
- Good, because JSON is easily serialized and also can be represented/embedded in YAML
### Negative
- Bad, JSONLogic syntax can be cumbersome when rules are complex
- Bad, hard to debug
## Conclusion
flagd's targeting configuration system represents a thoughtful balance between safety, portability, and capability.
By building on JSON Logic and extending it with feature-flag-specific operators, flagd achieves remarkable flexibility while maintaining security and performance.

View File

@ -0,0 +1,121 @@
---
status: draft
author: @toddbaert
created: 2025-06-06
updated: 2025-06-06
---
# Fractional Operator
The fractional operator enables deterministic, fractional feature flag distribution.
## Background
Nearly all feature flag systems require pseudorandom assignment support to facilitate key use cases, including experimentation and fractional progressive rollouts.
Since flagd seeks to implement a full feature flag evaluation engine, such a feature is required.
## Requirements
- **Deterministic**: must be consistent given the same input (so users aren't re-assigned with each page view, for example)
- **Performant**: must be quick; we want "predictable randomness", but with a relatively low performance cost
- **Ease of use**: must be easy to use and understand for basic use-cases
- **Customization**: must support customization, such as specifying a particular context attribute to "bucket" on
- **Stability**: adding new variants should result in new assignments for as small a section of the audience as possible
- **Strong avalanche effect**: slight input changes should result in relatively high chance of differential bucket assignment
## Considered Options
- We considered various "more common" hash algos, such as `sha1` and `md5`, but they were frequently slower than `Murmur3`, and didn't offer better performance for our purposes
- Initially we required weights to sum to 100, but we've since revoked that requirement
## Proposal
### MurmurHash3 + numeric weights + optional targeting-key-based bucketing value
#### The fractional operator mechanism
The fractional operator facilitates **deterministic A/B testing and gradual rollouts** through a custom JSONLogic extension introduced in flagd version 0.6.4+.
This operator splits feature flag variants into "buckets", based the `targetingKey` (or another optionally specified key), ensuring users consistently receive the same variant across sessions through sticky evaluation.
The core algorithm involves four steps: extracting a bucketing property from the evaluation context, hashing this value using MurmurHash3, mapping the hash to a [0, 100] range, and selecting variants based on cumulative weight thresholds.
This approach guarantees that identical inputs always produce identical outputs (excepting the case of rules involving the `$flag.timestamp`), which is crucial for maintaining a consistent user experience.
#### MurmurHash3: The chosen algorithm
flagd specifically employs **MurmurHash3 (32-bit variant)** for its fractional operator, prioritizing performance and distribution quality over cryptographic security.
This non-cryptographic hash function provides excellent performance and good avalanche properties (small input changes produce dramatically different outputs) while maintaining deterministic behavior essential for sticky evaluations.
Its wide language implementation ensures identical results across different flagd providers, no matter the language in question.
#### Bucketing value
The bucking value is an optional first value to the operator (it may be a JSONLogic expression, other than an array).
This allows enables targeting based on arbitrary attributes (individual users, companies/tenants, etc).
If not specified, the bucketing value is a JSONLogic expression concatenating the `$flagd.flagKey` and the extracted [targeting key](https://openfeature.dev/specification/glossary/#targeting-key) (`targetingKey`) from the context (the inclusion of the flag key prevents users from landing in the same "bucket index" for all flags with the same number of buckets).
If the bucking value does not resolve to a string, or the `targeting key` is undefined, the evaluation is considered erroneous.
```json
// Default bucketing value
{
"cat": [
{"var": "$flagd.flagKey"},
{"var": "targetingKey"}
]
}
```
#### Bucketing strategy implementation
After retrieving the bucketing value, and hashing it to a [0, 99] range, the algorithm iterates through variants, accumulating their relative weights until finding the bucket containing the hash value.
```go
// Simplified implementation structure
hashValue := murmur3Hash(bucketingValue) % 100
currentWeight := 0
for _, distribution := range variants {
currentWeight += (distribution.weight * 100) / sumOfWeights
if hashValue < currentWeight {
return distribution.variant
}
}
```
This approach supports flexible weight ratios; weights of [25, 50, 25] translate to 25%, 50%, and 25% distribution respectively as do [1, 2, 1].
It's worth noting that the maximum bucket resolution is 1/100, meaning that the maximum ratio between variant distributions is 1:99 (ie: a weight distribution of [1, 100000] behaves the same as [1, 100]).
#### Format flexibility: Shorthand vs longhand
flagd provides two syntactic options for defining fractional distributions, balancing simplicity with precision. **Shorthand format** enables equal distribution by specifying variants as single-element arrays (in this case, an equal weight of 1 is automatically assumed):
```json
{
"fractional": [
["red"],
["blue"],
["green"]
]
}
```
**Longhand format** allows precise weight control through two-element arrays:
Note that in this example, we've also specified a custom bucketing value.
```json
{
"fractional": [
{ "var": "email" },
["red", 50],
["blue", 20],
["green", 30]
]
}
```
### Consequences
- Good, because Murmur3 is fast, has good avalanche properties, and we don't need "cryptographic" randomness
- Good, because we have flexibility but also simple shorthand
- Good, because our bucketing algorithm is relatively stable when new variants are added
- Bad, because we only support string bucketing values
- Bad, because we don't have bucket resolution finer than 1:99
- Bad, because we don't support JSONLogic expressions within bucket definitions

View File

@ -0,0 +1,157 @@
---
status: rejected
author: @alexandraoberaigner
created: 2025-05-28
updated: -
---
# Add support for dynamic usage of Flag Sets to `flagd`
⚠️ REJECTED IN FAVOR OF <https://github.com/open-feature/flagd/blob/main/docs/architecture-decisions/duplicate-flag-keys.md> ⚠️
The goal of this decision document is to establish flag sets as a first class concept in `flagd`, and support the dynamic addition/update/removal of flag sets at runtime.
## Background
`flagd` is a language-agnostic feature flagging engine that forms a core part of the OpenFeature ecosystem.
Flag configurations can be stored in different locations so called `sources`. These are specified at startup, e.g.:
````shell
flagd start \
--port 8013 \
--uri file:etc/flagd/my-flags-1.json \
--uri https://my-flags-2.com/flags
````
The primary object here is to remove the coupling between sources and "logical" groups of flags, so that provider's aren't required to know their set of flags are sources from a file/http resource, etc, but could instead just supply a logical identifier for their flag set.
## Requirements
* Should enable the dynamic usage of flag sets as logical identifiers.
* Should support configurations without flag sets.
* Should adhere to existing OpenFeature and flagd terminology and concepts
## Considered Options
1. Addition of flag set support in the [flags schema](https://flagd.dev/reference/schema/#targeting) and associated enhancements to `flagd` storage layer
2. Support for dynamically adding/removing flag sources through some kind of runtime configuration API
3. Support for dynamically adding/removing flag sources through some kind of "discovery" protocol or endpoint (ie: point flagd at a resource that would enumerate a mutable collection of secondary resources which represent flag sets)
## Proposal
To support the dynamic usage of flag sets we propose to adapt the flag schema & storage layer in `flagd`.
The changes will decouple flag sets from flag sources by supporting multiple flag sets within single flag sources.
Dynamic updates to flag sources is already a feature of `flagd`.
### New Schema Structure
The proposed changes to the current flagd schema would allow the following json structure for **sources**:
````json
{
"$schema": "https://flagd.dev/schema/v1/flagsets.json",
"flagSets": {
"my-project-1": {
"metadata": {
...
},
"flags": {
"my-flag-1": {
"metadata": {
...
},
...
},
...
},
"$evaluators": {
...
}
},
"my-project-2": {
...
}
}
}
````
We propose to introduce a 3rd json schema `flagSets.json`, which references to `flags.json`:
1. flagSets.json (new)
2. flags.json
3. targeting.json
We don't want to support merging of flag sets, due to implementation efforts & potential confusing behaviour of the
merge strategy.
Therefore, we propose for the initial implementation, `flagSetId`s must be unique across different sources or the configuration is considered invalid.
In the future, it might be useful to support and implement multiple "strategies" for merging flagSets from different sources, but that's beyond the scope of this proposal.
### New Data Structure
The storage layer in `flagd` requires refactoring to better support multiple flag sets within one source.
````go
package store
type State struct {
FlagSets map[string]FlagSet `json:"flagSets"` // key = flagSetId
}
type FlagSet struct {
Flags map[string]model.Flag `json:"flags"` // key = flagKey
Metadata Metadata `json:"metadata,omitempty"`
}
type Flag struct {
State string `json:"state"`
DefaultVariant string `json:"defaultVariant"`
Variants map[string]any `json:"variants"`
Targeting json.RawMessage `json:"targeting,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
}
type Metadata = map[string]interface{}
````
### OpenFeature Provider Implications
Currently, creating a new flagd provider can look like follows:
````java
final FlagdProvider flagdProvider =
new FlagdProvider(FlagdOptions.builder()
.resolverType(Config.Evaluator.IN_PROCESS)
.host("localhost")
.port(8015)
.selector("myFlags.json")
.build());
````
* With the proposed solution the `flagSetId` should be passed to the builder as selector argument instead of the source.
* `null` is now a valid selector value, referencing flags which do not belong to a flag set. The default/fallback `flagSetId` should be `null`.
### Consequences
* Good, because it decouples flag sets from the sources
* Good, because we will refactor the flagd storage layer (which is currently storing duplicate data & difficult to
understand)
* Good, because we can support backwards compatibility with the v0 schema
* Good, because the "null" flag set is logically treated as any other flag set, reducing overall implementation complexity.
* Bad, because there's additional complexity to support this new config schema as well as the current.
* Bad, because this is a breaking change in the behavior of the `selector` member.
### Other Options
We evaluated the [mentioned options](#considered-options) as follows: _options 2 + 3: support for dynamically adding/removing flag sources_ and decided against this option because it requires much more implementation effort than _option 1_. Required changes include:
* flagd/core/sync: dynamic mode, which allows specifying the sync type that should be added/removed at runtime
* flagd/flagd: startup dynamic sync configuration
* make sure to still support static syncs
## More Information
* Current flagd schema: [flags.json](https://flagd.dev/schema/v0/flags.json)
* flagd storage layer
implementation: [store/flags.go](https://github.com/open-feature/flagd/blob/main/core/pkg/store/flags.go)
* [flagd GitHub Repository](https://github.com/open-feature/flagd)
* [OpenFeature Project Overview](https://openfeature.dev/)

View File

@ -0,0 +1,77 @@
---
status: accepted
author: Dave Josephsen
created: 2025-05-21
updated: 2025-05-21
---
# ADR: Multiple Sync Sources
It is the Intent of this document to articulate our rationale for supporting multiple flag syncronization sources (grpc, http, blob, local file, etc..) as a core design property of flagd. This document also includes a short discussion of how flagd is engineered to enable the community to extend it to support new sources in the future, to "future proof" the runtime against sources that don't yet exist, or those we may have omitted is a requisite byproduct of this architectural decision.
The goal of first-class multi-sync support generally is to broaden flagd's potential to suit the needs of many different types of users or architecture. By decoupling flag persistence from the runtime, flagd can focus on evaluation and sync, while enabling its user-base to choose a persistence layer that best suits their individual requirements.
## Background
The flagd daemon is a feature flag evaluation engine that forms a core part of the OpenFeature ecosystem as a production-grade reference implementation. Unlike OpenFeature SDK components, which are, by design, agnostic to the specifics of flag structure, evaluation, and persistence, flagd must take an opinionated stance about how feature-flags look, feel, and act.
What schema best describes a flag? How should they be evaluated? And in what sort of persistence layer should they be stored?
This latter-most question -- _how should they be stored_ -- is the most opaque design detail of every commercial flagging product from the perspective its end-users.
As a front-line engineer using a commercial flagging product, I may, for example, be exposed to flag schema by the product's SDK, and become familiar with its evaluation intricacies over time as my needs grow to require advanced features, or as I encounter surprising behavior. Rarely, however, is an end-user exposed to the details of a commercial product's storage backend.
The SaaS vendor is expected to engineer a safe, fast, multi-tenant storage back-end, optimized for its flag schema and operational parameters, and insulate the customer from these details via its SDK.
This presents Flagd, an open-source evaluation engine, with an interesting conundrum: what sort of flag storage best suits the needs of its potential user-base (which is everyone)?
## Requirements
* Support the storage technology that's most likely to meet the needs of current Flagd user-base (Don't be weird. Don't be surprising.)
* Make it "easy" to extend the flagd runtime to support "new" storage systems
* Horizontally scalable persistence layer
* Minimize end-user exposure to persistence "quirks" (replication schemes, leader election, back-end scaling, consistency minutia, etc.. )
* Reliable, Fast, Transparent
* Full CRUD, read-optimized.
## Considered Options
* Be super-opinionated and prescribe a built-in raftesque key-value setup, analogous to the designs of k8s and kafka, which prescribe etcd and zookeeper respectively.
* Roll a single "standard interface" for flag sync (published grpc spec or similar) (??)
* Decouple storage from flagd entirely, by exposing a Golang interface type that "providers" can implement to provide support for any data store.
## Proposal
<!--
Unsure whether we want a diagram in this section or not. Happy to add one if we want one.
-->
The solution to the conundrum posited in the background section of this document is to decouple flag storage entirely from the rest of the runtime, including instead support for myriad commonly used data syncronization interfaces.
This allows Flagd to be agnostic to flag storage, while enabling users to use whichever storage back-end best suits their environment.
To extend Flagd to support a new storage back-end, _sync providers_ implement the _ISync_ interface, detailed below:
```go
type ISync interface {
// Init is used by the sync provider to initialize its data structures and external dependencies.
Init(ctx context.Context) error
// Sync is the contract between Runtime and sync implementation.
// Note that, it is expected to return the first data sync as soon as possible to fill the store.
Sync(ctx context.Context, dataSync chan<- DataSync) error
// ReSync is used to fetch the full flag configuration from the sync
// This method should trigger an ALL sync operation then exit
ReSync(ctx context.Context, dataSync chan<- DataSync) error
// IsReady shall return true if the provider is ready to communicate with the Runtime
IsReady() bool
}
```
syncronization events "fan-in" from all configured sync providers to flagd's in-memory state-store via a channel carrying [`sync.DataSync`](https://github.com/open-feature/flagd/blob/main/core/pkg/store/flags.go#L19) events.
These events detail the source and type of the change, along with the flag data in question and are merged into the currently held state by the [store](https://github.com/open-feature/flagd/blob/main/core/pkg/store/flags.go#L19).
### Consequences
Because syncronization providers may vary wildly with respect to their implementation details, supporting multiple sync providers means supporting custom configuration parameters for each provider.
As a consequence, Flagd's configuration is itself made more complex, and its bootstrap process, whose goal is to create a [`runtime.Runtime`](https://github.com/open-feature/flagd/blob/main/flagd/pkg/runtime/runtime.go#L21) object from user-provided configuration, spends the preponderance of its time and effort interpreting, configuring, and initializing sync providers.
There is, in fact, a custom bootstrap type, called the `syncbuilder` whose job is to bootstrap sync providers and arrange them into a map, for the runtime to use.
Further, Because sync providers may vary wildly with respect to implementation, the end-user's choice of sync sources can change Flagd's operational parameters. For example, end-users who choose the GRPC provider can expect flag-sync operations to be nearly immediate, because GRPC updates can be pushed to flagd as they occur, compared with end-users who chose the HTTP provider, who must wait for a timer to expire in order to notice updates, because HTTP is a polling-based implementation.
Finally, sync Providers also contribute a great deal of girth to flagd's documentation, because again, their setup, syntax, and runtime idiosyncrasies may differ wildly.

View File

@ -0,0 +1,211 @@
---
status: accepted
author: @beeme1mr
created: 2025-06-06
updated: 2025-06-20
---
# Support Explicit Code Default Values in flagd Configuration
This ADR proposes adding support for explicitly configuring flagd to use code-defined default values by allowing `null` as a valid default variant. This change addresses the current limitation where users cannot differentiate between "use the code's default" and "use this configured default" without resorting to workarounds like misconfigured rulesets.
## Background
Currently, flagd requires a default variant to be specified in flag configurations. This creates a fundamental mismatch with the OpenFeature specification and common feature flag usage patterns where code-defined defaults serve as the ultimate fallback.
The current behavior leads to confusion and operational challenges:
1. **Two Sources of Truth**: Applications have default values defined in code (as per OpenFeature best practices), while flagd configurations require their own default variants. This dual-default pattern violates the principle of single source of truth.
2. **State Transition Issues**: When transitioning a flag from DISABLED to ENABLED state, the behavior changes unexpectedly:
- DISABLED state: Flag evaluation falls through to code defaults
- ENABLED state: Flag evaluation uses the configured default variant
3. **Workarounds**: Users resort to misconfiguring rulesets (e.g., returning invalid variants) to force fallback to code defaults, which generates confusing error states and complicates debugging.
4. **OpenFeature Alignment**: The OpenFeature specification emphasizes that code defaults should be the ultimate fallback, but flagd's current design doesn't provide a clean way to express this intent.
Related discussions and context can be found in the [OpenFeature specification](https://openfeature.dev/specification/types) and [flagd flag definitions reference](https://flagd.dev/reference/flag-definitions/).
## Requirements
- **Explicit Code Default Support**: Users must be able to explicitly configure a flag to use the code-defined default value as its resolution
- **Backward Compatibility**: Existing flag configurations must continue to work without modification
- **Clear Semantics**: The configuration must clearly indicate when code defaults are being used versus configured defaults
- **Appropriate Reason Codes**: Resolution details must include appropriate reason codes when code defaults are used (e.g., `DEFAULT` or a new specific reason)
- **Schema Validation**: JSON schema must support and validate the new configuration options
- **Provider Compatibility**: All OpenFeature providers must handle the new behavior correctly
- **Testbed Coverage**: flagd testbed must include test cases for the new functionality
## Considered Options
- **Option 1: Allow `null` as Default Variant** - Modify the schema to accept `null` as a valid value for defaultVariant, signaling "use code default"
- **Option 2: Make Default Variant Optional** - Remove the requirement for defaultVariant entirely, with absence meaning "use code default"
- **Option 3: Special Variant Value** - Define a reserved variant name (e.g., `"__CODE_DEFAULT__"`) that signals code default usage
- **Option 4: New Configuration Property** - Add a new property like `useCodeDefault: true` alongside or instead of defaultVariant
- **Option 5: Status Quo with Documentation** - Keep current behavior but improve documentation about workarounds
## Proposal
We propose implementing **Option 1: Allow `null` as Default Variant**, potentially combined with **Option 2: Make Default Variant Optional** for maximum flexibility.
The implementation leverages field presence in evaluation responses across all protocols (in-process, RPC, and OFREP). When a flag configuration has `defaultVariant: null`, the evaluation response omits the value field entirely, which serves as a programmatic signal to the client to use its code-defined default value.
This approach offers several key advantages:
1. **No Protocol Changes**: RPC and OFREP protocols remain unchanged
2. **Clear Semantics**: Omitted value field = "use your code default"
3. **Backward Compatible**: Existing clients and servers continue to work
4. **Universal Pattern**: Works consistently across all evaluation modes
The absence of a value field provides an unambiguous signal that distinguishes between "the server evaluated to null/false/empty" (value field present) and "the server delegates to your code default" (value field absent).
### Implementation Details
1. **Schema Changes**:
```json
{
"defaultVariant": {
"oneOf": [
{ "type": "string" },
{ "type": "null" }
],
"description": "Default variant to use. Set to null to use code-defined default."
}
}
```
2. **Evaluation Behavior**:
- When flag has `defaultVariant: null` and targeting returns no match
- Server responds with reason set to reason "ERROR" and error code "FLAG_NOT_FOUND"
- Client detects this reason value field and uses its code-defined default
- This same pattern works across all evaluation modes
3. **Provider Implementation**:
- No changes to existing providers
### Design Rationale
**Using "ERROR" reason**: We intentionally reuse the existing "ERROR" reason code rather than introducing a new one (like "CODE_DEFAULT"). This retains the current behavior of an disabled flag and allows for progressive enablement of a flag without unexpected variations in flag evaluation behavior.
Advantages of this approach:
- The "ERROR" reason is already used for cases where the flag is not found or misconfigured, so it aligns with the intent of using code defaults.
- This approach avoids introducing new reason codes that would require additional handling in providers and clients.
### API changes
**Flag Configuration**:
```yaml
flags:
my-feature:
state: ENABLED
defaultVariant: null # Explicitly use code default
variants:
on: true
off: false
targeting:
if:
- "===":
- var: user-type
- "beta"
- on
```
**OFREP Response** when code default is indicated:
#### Single flag evaluation response
A single flag evaluation returns a `404` status code.
```json
{
"key": "my-feature",
"errorCode": "FLAG_NOT_FOUND",
// Optional error details
"errorDetails": "Targeting not matched, using code default",
"metadata": {}
}
```
#### Bulk flag evaluation response
```json
{
"flags": [
// Flag is omitted from bulk response
]
}
```
**flagd RPC Response** (ResolveBooleanResponse):
```protobuf
{
"reason": "ERROR",
"errorCode": "FLAG_NOT_FOUND",
"metadata": {}
}
```
### Consequences
- Good, because it eliminates the confusion between code and configuration defaults
- Good, because it provides explicit control over default behavior without workarounds
- Good, because it aligns flagd more closely with OpenFeature specification principles
- Good, because it supports gradual flag rollout patterns more naturally
- Good, because it provides the ability to delegate to whatever is defined in code
- Good, because it requires no changes to existing RPC or protocol signatures
- Good, because it uses established patterns (field presence) for clear semantics
- Good, because it maintains full backward compatibility
- Bad, because it requires updates across multiple components (flagd, providers, testbed)
- Bad, because it introduces a new concept that users need to understand
- Neutral, because existing configurations continue to work unchange
### Implementation Plan
1. Update flagd-schemas with new JSON schema supporting null default variants
2. Update flagd-testbed with comprehensive test cases for all evaluation modes
3. Implement core logic in flagd to handle null defaults and omit value/variant fields
4. Update OpenFeature providers with the latest schema and test harness to ensure they handle the new behavior correctly
5. Documentation updates, migration guides, and playground examples to demonstrate the new configuration options
### Testing Considerations
To ensure correct implementation across all components:
1. **Provider Tests**: Each component (flagd, providers) must have unit tests verifying the handling of `null` as a default variant
2. **Integration Tests**: End-to-end tests across different language combinations (e.g., Go flagd with Java provider)
3. **OFREP Tests**: Verify JSON responses correctly omits flags with a `null` default variant
4. **Backward Compatibility Tests**: Ensure old providers handle new responses gracefully
5. **Consistency Tests**: Verify identical behavior across in-process, RPC, and OFREP modes
### Open questions
- How should providers handle responses with missing value fields in strongly-typed languages?
- We'll handle the same way as with optional fields, using language-specific patterns (e.g., pointers in Go, `hasValue()` in Java).
- Should we support both `null` and absent `defaultVariant` fields, or choose one approach?
- Yes, we'll support both `null` and absent fields to maximize flexibility. An absent `defaultVariant` will be the equivalent of `null`.
- What migration path should we recommend for users currently using workarounds?
- Update the flag configurations to use `defaultVariant: null` and remove any misconfigured rulesets that force code defaults.
- Should this feature be gated behind a configuration flag during initial rollout?
- We'll avoid public facing documentation until the feature is fully implemented and tested.
- How do we ensure consistent behavior across all provider implementations?
- Gherkin tests will be added to the flagd testbed to ensure all providers handle the new behavior consistently.
- Should providers validate that the reason is "DEFAULT" when value is omitted, or accept any omitted value as delegation?
- Providers should accept any omitted value as delegation.
- How do we handle edge cases where network protocols might strip empty fields?
- It would behaving as expected, as the absence of fields is the intended signal.
- When the client uses its code default after receiving a delegation response, what variant should be reported in telemetry/analytics?
- The variant will be omitted, indicating that the code default was used.
- Should we add explicit proto comments documenting the field omission behavior?
- Leave this to the implementers, but it would be beneficial to add comments in the proto files to clarify this behavior for future maintainers.
## More Information
- [OpenFeature Specification - Flag Evaluation](https://openfeature.dev/specification/types#flag-evaluation)
- [flagd Flag Definitions Reference](https://flagd.dev/reference/flag-definitions/)
- [flagd JSON Schema Repository](https://github.com/open-feature/flagd-schemas)
- [flagd Testbed](https://github.com/open-feature/flagd-testbed)

View File

@ -34,6 +34,11 @@ invoked with **HTTP GET** request.
The polling interval, port, TLS settings, and authentication information can be configured.
See [sync source](../reference/sync-configuration.md#source-configuration) configuration for details.
To optimize network usage, it honors the HTTP ETag protocol: if the server includes an `ETag` header in its response,
flagd will store this value and send it in the `If-None-Match` header on subsequent requests. If the flag data has
not changed, the server responds with 304 Not Modified, and flagd will skip updating its state. If the data has
changed, the server returns the new content and a new ETag, prompting flagd to update its flags.
---
### gRPC sync

View File

@ -25,23 +25,58 @@ These types of feature flags are commonly used to gate access to a new feature u
The second flag has the key `background-color` and is a multi-variant string.
These are commonly used for A/B/(n) testing and experimentation.
### Start flagd
### Running Flagd
Run the following command to start flagd using docker. This will expose flagd on port `8013` and read from the `demo.flagd.json` file we downloaded in the previous step.
=== "Docker"
```shell
docker run \
--rm -it \
--name flagd \
-p 8013:8013 \
-v $(pwd):/etc/flagd \
ghcr.io/open-feature/flagd:latest start \
--uri file:./etc/flagd/demo.flagd.json
```
Run the following command to start flagd using docker. This will expose flagd on port `8013` and read from the `demo.flagd.json` file we downloaded in the previous step.
??? "Tips for Windows users"
In Windows, use WSL system for both the file location and Docker runtime.
Mixed file systems does not work and this is a [limitation of Docker](https://github.com/docker/for-win/issues/8479).
```shell
docker run \
--rm -it \
--name flagd \
-p 8013:8013 \
-v $(pwd):/etc/flagd \
ghcr.io/open-feature/flagd:latest start \
--uri file:./etc/flagd/demo.flagd.json
```
??? "Tips for Windows users"
In Windows, use WSL system for both the file location and Docker runtime.
Mixed file systems does not work and this is a [limitation of Docker](https://github.com/docker/for-win/issues/8479).
=== "Docker Compose"
Create a docker-compose.yaml file with the following contents:
```yaml
services:
flagd:
image: ghcr.io/open-feature/flagd:latest
volumes:
- ./flags:/flags
command: [
'start',
'--uri',
'file:./flags/demo.flagd.json',
]
ports:
- '8013:8013'
```
Create a folder called `flags` where the JSON flag files can reside. [Download the flag definition](#download-the-flag-definition) and move this JSON file to the flags folder.
```text
├── flags
│ ├── demo.flagd.json
├── docker-compose.yaml
```
Open up a terminal and run the following:
```shell
docker compose up
```
### Evaluating a feature flag

View File

@ -64,7 +64,9 @@ The `defaultVariant` is `red`, but it contains a [targeting rule](../flag-defini
In this case, `25%` of the evaluations will receive `red`, `25%` will receive `blue`, and so on.
Assignment is deterministic (sticky) based on the expression supplied as the first parameter (`{ "cat": [{ "var": "$flagd.flagKey" }, { "var": "email" }]}`, in this case).
The value retrieved by this expression is referred to as the "bucketing value".
The value retrieved by this expression is referred to as the "bucketing value" and must be a string.
Other primitive types can be used by casting the value using `"cat"` operator.
For example, a less deterministic distribution can be achieved using `{ "cat": [{ "var": "$flagd.timestamp" }]}`.
The bucketing value expression can be omitted, in which case a concatenation of the `targetingKey` and the `flagKey` will be used.
The `fractional` operation is a custom JsonLogic operation which deterministically selects a variant based on

View File

@ -11,26 +11,29 @@ flagd start [flags]
### Options
```
-X, --context-value stringToString add arbitrary key value pairs to the flag evaluation context (default [])
-C, --cors-origin strings CORS allowed origins, * will allow all origins
-h, --help help for start
-z, --log-format string Set the logging format, e.g. console or json (default "console")
-m, --management-port int32 Port for management operations (default 8014)
-t, --metrics-exporter string Set the metrics exporter. Default(if unset) is Prometheus. Can be override to otel - OpenTelemetry metric exporter. Overriding to otel require otelCollectorURI to be present
-r, --ofrep-port int32 ofrep service port (default 8016)
-A, --otel-ca-path string tls certificate authority path to use with OpenTelemetry collector
-D, --otel-cert-path string tls certificate path to use with OpenTelemetry collector
-o, --otel-collector-uri string Set the grpc URI of the OpenTelemetry collector for flagd runtime. If unset, the collector setup will be ignored and traces will not be exported.
-K, --otel-key-path string tls key path to use with OpenTelemetry collector
-I, --otel-reload-interval duration how long between reloading the otel tls certificate from disk (default 1h0m0s)
-p, --port int32 Port to listen on (default 8013)
-c, --server-cert-path string Server side tls certificate path
-k, --server-key-path string Server side tls key path
-d, --socket-path string Flagd unix socket path. With grpc the evaluations service will become available on this address. With http(s) the grpc-gateway proxy will use this address internally.
-s, --sources string JSON representation of an array of SourceConfig objects. This object contains 2 required fields, uri (string) and provider (string). Documentation for this object: https://flagd.dev/reference/sync-configuration/#source-configuration
-g, --sync-port int32 gRPC Sync port (default 8015)
-e, --sync-socket-path string Flagd sync service socket path. With grpc the sync service will be available on this address.
-f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath, URL (HTTP and gRPC), FeatureFlag custom resource, or GCS or Azure Blob. When flag keys are duplicated across multiple providers the merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. Please note that if you are using filepath, flagd only supports files with .yaml/.yml/.json extension.
-H, --context-from-header stringToString add key-value pairs to map header values to context values, where key is Header name, value is context key (default [])
-X, --context-value stringToString add arbitrary key value pairs to the flag evaluation context (default [])
-C, --cors-origin strings CORS allowed origins, * will allow all origins
--disable-sync-metadata Disables the getMetadata endpoint of the sync service. Defaults to false, but will default to true in later versions.
-h, --help help for start
-z, --log-format string Set the logging format, e.g. console or json (default "console")
-m, --management-port int32 Port for management operations (default 8014)
-t, --metrics-exporter string Set the metrics exporter. Default(if unset) is Prometheus. Can be override to otel - OpenTelemetry metric exporter. Overriding to otel require otelCollectorURI to be present
-r, --ofrep-port int32 ofrep service port (default 8016)
-A, --otel-ca-path string tls certificate authority path to use with OpenTelemetry collector
-D, --otel-cert-path string tls certificate path to use with OpenTelemetry collector
-o, --otel-collector-uri string Set the grpc URI of the OpenTelemetry collector for flagd runtime. If unset, the collector setup will be ignored and traces will not be exported.
-K, --otel-key-path string tls key path to use with OpenTelemetry collector
-I, --otel-reload-interval duration how long between reloading the otel tls certificate from disk (default 1h0m0s)
-p, --port int32 Port to listen on (default 8013)
-c, --server-cert-path string Server side tls certificate path
-k, --server-key-path string Server side tls key path
-d, --socket-path string Flagd unix socket path. With grpc the evaluations service will become available on this address. With http(s) the grpc-gateway proxy will use this address internally.
-s, --sources string JSON representation of an array of SourceConfig objects. This object contains 2 required fields, uri (string) and provider (string). Documentation for this object: https://flagd.dev/reference/sync-configuration/#source-configuration
--stream-deadline duration Set a server-side deadline for flagd sync and event streams (default 0, means no deadline).
-g, --sync-port int32 gRPC Sync port (default 8015)
-e, --sync-socket-path string Flagd sync service socket path. With grpc the sync service will be available on this address.
-f, --uri .yaml/.yml/.json Set a sync provider uri to read data from, this can be a filepath, URL (HTTP and gRPC), FeatureFlag custom resource, or GCS or Azure Blob. When flag keys are duplicated across multiple providers the merge priority follows the index of the flag arguments, as such flags from the uri at index 0 take the lowest precedence, with duplicated keys being overwritten by those from the uri at index 1. Please note that if you are using filepath, flagd only supports files with .yaml/.yml/.json extension.
```
### Options inherited from parent commands

View File

@ -47,15 +47,25 @@ Given below is the current implementation overview of flagd telemetry internals,
flagd exposes the following metrics:
- `http.server.duration`
- `http.server.response.size`
- `http.server.active_requests`
- `feature_flag.flagd.impression`
- `feature_flag.flagd.evaluation.reason`
- `http.server.request.duration` - Measures the duration of inbound HTTP requests
- `http.server.response.body.size` - Measures the size of HTTP response messages
- `http.server.active_requests` - Measures the number of concurrent HTTP requests that are currently in-flight
- `feature_flag.flagd.impression` - Measures the number of evaluations for a given flag
- `feature_flag.flagd.result.reason` - Measures the number of evaluations for a given reason
> Please note that metric names may vary based on the consuming monitoring tool naming requirements.
> For example, the transformation of OTLP metrics to Prometheus is described [here](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus).
### HTTP Metric Attributes
flagd uses the following OpenTelemetry Semantic Conventions for HTTP metrics:
- `service.name` - The name of the service
- `http.route` - The matched route (path template)
- `http.request.method` - The HTTP request method (GET, POST, etc.)
- `http.response.status_code` - The HTTP response status code
- `url.scheme` - The URI scheme (http or https)
## Traces
flagd creates the following spans as part of a trace:
@ -83,9 +93,8 @@ official [OTEL collector example](https://github.com/open-telemetry/opentelemetr
```yaml
services:
# Jaeger
jaeger-all-in-one:
image: jaegertracing/all-in-one:latest
jaeger:
image: cr.jaegertracing.io/jaegertracing/jaeger:2.8.0
restart: always
ports:
- "16686:16686"
@ -93,7 +102,7 @@ services:
- "14250"
# Collector
otel-collector:
image: otel/opentelemetry-collector:latest
image: otel/opentelemetry-collector:0.129.1
restart: always
command: [ "--config=/etc/otel-collector-config.yaml" ]
volumes:
@ -106,10 +115,10 @@ services:
- "4317:4317" # OTLP gRPC receiver
- "55679:55679" # zpages extension
depends_on:
- jaeger-all-in-one
- jaeger
prometheus:
container_name: prometheus
image: prom/prometheus:latest
image: prom/prometheus:v2.53.5
restart: always
volumes:
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
@ -128,10 +137,8 @@ receivers:
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
const_labels:
label1: value1
otlp/jaeger:
endpoint: jaeger-all-in-one:4317
endpoint: jaeger:4317
tls:
insecure: true
processors:
@ -148,7 +155,7 @@ service:
exporters: [ prometheus ]
```
#### prometheus.yml
#### prometheus.yaml
```yaml
scrape_configs:
@ -156,10 +163,9 @@ scrape_configs:
scrape_interval: 10s
static_configs:
- targets: [ 'otel-collector:8889' ]
- targets: [ 'otel-collector:8888' ]
```
Once, configuration files are ready, use `docker-compose up` to start the local setup. With successful startup, you can
Once, configuration files are ready, use `docker compose up` to start the local setup. With successful startup, you can
access metrics through [Prometheus](http://localhost:9090/graph) & traces through [Jaeger](http://localhost:16686/).
## Metadata

View File

@ -280,6 +280,7 @@ Below are the supported configuration parameters (note that not all apply to bot
| cache | FLAGD_CACHE | enable cache of static flags | String - `lru`, `disabled` | lru | rpc |
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | max size of static flag cache | int | 1000 | rpc |
| selector | FLAGD_SOURCE_SELECTOR | selects a single sync source to retrieve flags from only that source | string | null | in-process |
| providerId | FLAGD_PROVIDER_ID | A unique identifier for flagd(grpc client) initiating the request. | string | null | in-process |
| offlineFlagSourcePath | FLAGD_OFFLINE_FLAG_SOURCE_PATH | offline, file-based flag definitions, overrides host/port/targetUri | string | null | file |
| offlinePollIntervalMs | FLAGD_OFFLINE_POLL_MS | poll interval for reading offlineFlagSourcePath | int | 5000 | file |
| contextEnricher | - | sync-metadata to evaluation context mapping function | function | identity function | in-process |

View File

@ -476,7 +476,7 @@
"$comment": "there seems to be a bug here, where ajv gives a warning (not an error) because maxItems doesn't equal the number of entries in items, though this is valid in this case",
"items": [
{
"description": "Bucketing value used in pseudorandom assignment; should be unique and stable for each subject of flag evaluation. Defaults to a concatenation of the flagKey and targetingKey.",
"description": "Bucketing value used in pseudorandom assignment; should be a string that is unique and stable for each subject of flag evaluation. Defaults to a concatenation of the flagKey and targetingKey.",
"$ref": "#/definitions/anyRule"
},
{

View File

@ -1,5 +1,48 @@
# Changelog
## [0.8.0](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.6...flagd-proxy/v0.8.0) (2025-07-21)
### ⚠ BREAKING CHANGES
* remove sync.Type ([#1691](https://github.com/open-feature/flagd/issues/1691))
### ✨ New Features
* remove sync.Type ([#1691](https://github.com/open-feature/flagd/issues/1691)) ([ac647e0](https://github.com/open-feature/flagd/commit/ac647e065636071f5bc065a9a084461cea692166))
### 🧹 Chore
* **deps:** update module github.com/open-feature/flagd/core to v0.11.8 ([#1685](https://github.com/open-feature/flagd/issues/1685)) ([c07ffba](https://github.com/open-feature/flagd/commit/c07ffba55426d538224d8564be5f35339d2258d0))
## [0.7.6](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.5...flagd-proxy/v0.7.6) (2025-07-15)
### 🧹 Chore
* **deps:** update module github.com/open-feature/flagd/core to v0.11.6 ([#1683](https://github.com/open-feature/flagd/issues/1683)) ([b6da282](https://github.com/open-feature/flagd/commit/b6da282f8a98082ba3733593d501d14842cbd97f))
## [0.7.5](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.4...flagd-proxy/v0.7.5) (2025-07-10)
### 🐛 Bug Fixes
* **security:** update module github.com/go-viper/mapstructure/v2 to v2.3.0 [security] ([#1667](https://github.com/open-feature/flagd/issues/1667)) ([caa0ed0](https://github.com/open-feature/flagd/commit/caa0ed04eb9d5d01136deb71b8fcc4da72aa1910))
### ✨ New Features
* add sync_context to SyncFlags ([#1642](https://github.com/open-feature/flagd/issues/1642)) ([07a45d9](https://github.com/open-feature/flagd/commit/07a45d9b2275584fa92ff33cbe5e5c7d7864db38))
## [0.7.4](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.3...flagd-proxy/v0.7.4) (2025-05-28)
### 🧹 Chore
* **deps:** update dependency go to v1.24.1 ([#1559](https://github.com/open-feature/flagd/issues/1559)) ([cd46044](https://github.com/open-feature/flagd/commit/cd4604471bba0a1df67bf87653a38df3caf9d20f))
* **security:** upgrade dependency versions ([#1632](https://github.com/open-feature/flagd/issues/1632)) ([761d870](https://github.com/open-feature/flagd/commit/761d870a3c563b8eb1b83ee543b41316c98a1d48))
## [0.7.3](https://github.com/open-feature/flagd/compare/flagd-proxy/v0.7.2...flagd-proxy/v0.7.3) (2025-03-25)

View File

@ -1,159 +1,170 @@
module github.com/open-feature/flagd/flagd-proxy
go 1.22.7
go 1.24.0
toolchain go1.22.9
toolchain go1.24.4
require (
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1
github.com/dimiro1/banner v1.1.0
github.com/mattn/go-colorable v0.1.14
github.com/open-feature/flagd/core v0.11.2
github.com/prometheus/client_golang v1.21.1
github.com/open-feature/flagd/core v0.11.8
github.com/prometheus/client_golang v1.22.0
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.19.0
go.opentelemetry.io/otel/exporters/prometheus v0.57.0
go.opentelemetry.io/otel/metric v1.35.0
go.opentelemetry.io/otel/sdk/metric v1.35.0
github.com/spf13/viper v1.20.1
go.opentelemetry.io/otel/exporters/prometheus v0.59.0
go.opentelemetry.io/otel/metric v1.37.0
go.opentelemetry.io/otel/sdk/metric v1.37.0
go.uber.org/zap v1.27.0
golang.org/x/net v0.35.0
golang.org/x/sync v0.11.0
google.golang.org/grpc v1.71.0
golang.org/x/net v0.41.0
golang.org/x/sync v0.15.0
google.golang.org/grpc v1.73.0
)
require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/auth v0.8.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.1.13 // indirect
cloud.google.com/go/storage v1.43.0 // indirect
cel.dev/expr v0.23.0 // indirect
cloud.google.com/go v0.121.1 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
connectrpc.com/connect v1.18.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/aws/aws-sdk-go v1.55.6 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.12 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/google/wire v0.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/open-feature/flagd-schemas v0.2.9-0.20250127221449-bb763438abc5 // indirect
github.com/open-feature/open-feature-operator/apis v0.2.44 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86 // indirect
github.com/open-feature/open-feature-operator/apis v0.2.45 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gocloud.dev v0.40.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
gocloud.dev v0.42.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/api v0.191.0 // indirect
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/protobuf v1.36.5 // indirect
google.golang.org/api v0.235.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.31.4 // indirect
k8s.io/apiextensions-apiserver v0.31.0 // indirect
k8s.io/apimachinery v0.31.4 // indirect
k8s.io/client-go v0.31.4 // indirect
k8s.io/api v0.33.2 // indirect
k8s.io/apiextensions-apiserver v0.31.1 // indirect
k8s.io/apimachinery v0.33.2 // indirect
k8s.io/client-go v0.33.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/controller-runtime v0.19.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/controller-runtime v0.19.3 // indirect
sigs.k8s.io/gateway-api v1.2.1 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

View File

@ -1,85 +1,165 @@
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2 h1:D3HI5RQbqgffyf+Z77+hReDx5kigFVAKGvttULD9/ms=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2/go.mod h1:b9rfG6rbGXZAlLwQwedvZ0kI0nUcR+aLaYF70pj920E=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.4-20250127221518-be6d1143b690.1 h1:0vXmOkGv8nO5H1W8TkSz+GtZhvD2LNXiQaiucEio6vk=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.4-20250127221518-be6d1143b690.1/go.mod h1:wemFLfCpuNfhrBQ7NwzbtYxbg+IihAYqJcNeS+fLpLI=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1 h1:eZKupK8gUTuc6zifAFQon8Gnt44fR4cd0GnTWjELvEw=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1/go.mod h1:wJvVIADHM0IaBc5sYf8wgMMgSHi0nAtc6rgr5rfizhA=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2 h1:TZ+7u106u7C7lgNctxG03ABliF46eLhcIZG5Mdo67/E=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2/go.mod h1:4u0WLwfkLob3dC/F8qNctqhtiEv2Mlyi8YgCDDzgYDs=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1 h1:LdC4xAuUaNdduzQr5VvhjsgrCfpW9IYxYsjyCF0ANs0=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1/go.mod h1:cCQ49+ttXE2MZ/ciRNb0tCG+F3kj2ZVbP+0/psbhrLY=
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4=
cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus=
cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nTWy3TYE=
cloud.google.com/go/longrunning v0.5.12/go.mod h1:S5hMV8CDJ6r50t2ubVJSKQVv5u0rmik5//KgLO3k4lU=
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA=
cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU=
cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw=
cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI=
cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io=
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc=
github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
github.com/aws/aws-sdk-go-v2/config v1.29.12 h1:Y/2a+jLPrPbHpFkpAAYkVEtJmxORlXoo5k2g1fa2sUo=
github.com/aws/aws-sdk-go-v2/config v1.29.12/go.mod h1:xse1YTjmORlb/6fhkWi8qJh3cvZi4JoVNhc+NbJt4kI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 h1:q+nV2yYegofO/SUXruT+pn4KxkxmaQ++1B/QedcKBFM=
github.com/aws/aws-sdk-go-v2/credentials v1.17.65/go.mod h1:4zyjAuGOdikpNYiSGpsGz8hLGmUzlY8pc8r9QQ/RXYQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69 h1:6VFPH/Zi9xYFMJKPQOX5URYkQoXRWeJ7V/7Y6ZDYoms=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69/go.mod h1:GJj8mmO6YT6EqgduWocwhMoxTLFitkhIrK+owzrYL2I=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 h1:pdgODsAhGo4dvzC3JAG5Ce0PX8kWXrTZGx+jxADD+5E=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 h1:90uX0veLKcdHVfvxhkWUQSCi5VabtwMLFutYiRke4oo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -87,10 +167,11 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -103,7 +184,15 @@ github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
@ -114,11 +203,15 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
@ -131,14 +224,20 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -153,6 +252,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -160,8 +261,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo=
@ -177,18 +276,22 @@ github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -203,18 +306,14 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -224,8 +323,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -237,81 +334,68 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/open-feature/flagd-schemas v0.2.9-0.20250106184836-37baa2cdea48 h1:qtUAqQxMT252ZE2T/ESExiSOgx+enrOnhPBUMIerKNs=
github.com/open-feature/flagd-schemas v0.2.9-0.20250106184836-37baa2cdea48/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd-schemas v0.2.9-0.20250127221449-bb763438abc5 h1:0RKCLYeQpvSsKR95kc894tm8GAZmq7bcG48v0KJ0HCs=
github.com/open-feature/flagd-schemas v0.2.9-0.20250127221449-bb763438abc5/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd/core v0.10.8 h1:JDspNtQ/AspLgtoC4UVsk8j+iCxY4yo4Ro51qdbYzEE=
github.com/open-feature/flagd/core v0.10.8/go.mod h1:8o7NXJdknnbseuK2HmfKYlqpIqvtwK8SZ+0Tg9/H4ac=
github.com/open-feature/flagd/core v0.11.0 h1:uyT2keRVqVptqoNOaK4Pq3LueLkcVIZ/qfCnN2mdKiw=
github.com/open-feature/flagd/core v0.11.0/go.mod h1:jjMq0sMdCvvsoi4dWoNwUTP4jN0vC1e8OtY/giqZIIM=
github.com/open-feature/flagd/core v0.11.1 h1:0qBVXcRBZOFoZ5lNK/Yba2IyUDdxUHcLsv5OhUJtltA=
github.com/open-feature/flagd/core v0.11.1/go.mod h1:yzPjp7D9wNusvOyKt8wBND5PQGslcu+5e+xmaIBGgLE=
github.com/open-feature/flagd/core v0.11.2 h1:3LAuLR2vXpBF80RwwCAu9JX898JasfPH7ErJEf5C5YA=
github.com/open-feature/flagd/core v0.11.2/go.mod h1:rTdYFyLGP1tGwxo0X26ygIrC31nINCv8hcCq+vhFD0E=
github.com/open-feature/flagd-schemas v0.2.9-0.20250319190911-9b0ee43ecc47 h1:c6nodciz/xeU0xiAcDQ5MBW34DnPoi5/lEgjV5kZeZA=
github.com/open-feature/flagd-schemas v0.2.9-0.20250319190911-9b0ee43ecc47/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86 h1:r3e+qs3QUdf4+lUi2ZZnSHgYkjeLIb5yu5jo+ypA8iw=
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd/core v0.11.5 h1:2zUbXN3Uh/cKZZVOQo9Z+Evjb9s15HcCtdo8vaNiBPY=
github.com/open-feature/flagd/core v0.11.5/go.mod h1:VBCrZ6bs+Occ226391YoIOnNEXmNvpWsQEeYfsLJ23c=
github.com/open-feature/flagd/core v0.11.6 h1:qrGYGUBmFABlvFA46LkLoG/DTwchUm+OLKX+XUytgS4=
github.com/open-feature/flagd/core v0.11.6/go.mod h1:UUhSAf0NSbGmFbdmkOf93YP90DA8O6ahVYn+vJO4BMg=
github.com/open-feature/flagd/core v0.11.8 h1:84uDdSzhtVNBnsjuAnBqBXbFwXC2CQ6aO5cNNKKM7uc=
github.com/open-feature/flagd/core v0.11.8/go.mod h1:3dNe+BX8HUpx/mXrGLD554G6cQB67yvuASVTKVC4TU4=
github.com/open-feature/open-feature-operator/apis v0.2.44 h1:0r4Z+RnJltuHdRBv79NFgAckhna6/M3Wcec6gzNX5vI=
github.com/open-feature/open-feature-operator/apis v0.2.44/go.mod h1:xB2uLzvUkbydieX7q6/NqannBz3bt/e5BS2DeOyyw4Q=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/open-feature/open-feature-operator/apis v0.2.45 h1:URnUf22ZoAx7/W8ek8dXCBYgY8FmnFEuEOSDLROQafY=
github.com/open-feature/open-feature-operator/apis v0.2.45/go.mod h1:PYh/Hfyna1lZYZUeu/8LM0qh0ZgpH7kKEXRLYaaRhGs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/cobra v1.9.0 h1:Py5fIuq/lJsRYxcxfOtsJqpmwJWCMOUy2tMJYV8TNHE=
github.com/spf13/cobra v1.9.0/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@ -328,38 +412,38 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E=
go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk=
go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk=
go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo=
go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
@ -370,21 +454,19 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng=
gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ=
gocloud.dev v0.42.0 h1:qzG+9ItUL3RPB62/Amugws28n+4vGZXEoJEAMfjutzw=
gocloud.dev v0.42.0/go.mod h1:zkaYAapZfQisXOA4bzhsbA4ckiStGQ3Psvs9/OQ5dPM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -411,15 +493,13 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -429,10 +509,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -449,20 +527,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -470,12 +544,14 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -488,38 +564,46 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk=
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E=
google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0=
google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY=
google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo=
google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM=
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -529,10 +613,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@ -540,8 +622,6 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@ -552,23 +632,46 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.31.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM=
k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw=
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40=
k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ=
k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM=
k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ=
k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg=
k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99 h1:w6nThEmGo9zcL+xH1Tu6pjxJ3K1jXFW+V0u4peqN8ks=
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw=
sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM=
sigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM=
sigs.k8s.io/gateway-api v1.2.1/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -104,7 +104,6 @@ func (l *oldHandler) SyncFlags(
case d := <-dataSync:
if err := stream.Send(&syncv1.SyncFlagsResponse{
FlagConfiguration: d.FlagData,
State: dataSyncToGrpcState(d),
}); err != nil {
return fmt.Errorf("error sending configuration change event: %w", err)
}
@ -115,8 +114,3 @@ func (l *oldHandler) SyncFlags(
}
}
}
//nolint:staticcheck
func dataSyncToGrpcState(s sync.DataSync) syncv1.SyncState {
return syncv1.SyncState(s.Type + 1)
}

View File

@ -21,6 +21,8 @@ type syncMock struct {
initError error
ctxCloseError error
mu sync.Mutex
}
func newMockSync() *syncMock {
@ -38,6 +40,8 @@ func (s *syncMock) Sync(ctx context.Context, dataSync chan<- isync.DataSync) err
for {
select {
case <-ctx.Done():
s.mu.Lock()
defer s.mu.Unlock()
return s.ctxCloseError
case d := <-s.dataSyncChanIn:
dataSync <- d
@ -48,6 +52,8 @@ func (s *syncMock) Sync(ctx context.Context, dataSync chan<- isync.DataSync) err
}
func (s *syncMock) ReSync(_ context.Context, dataSync chan<- isync.DataSync) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.resyncData != nil {
dataSync <- *s.resyncData
}
@ -104,7 +110,6 @@ func Test_watchResource(t *testing.T) {
in := isync.DataSync{
FlagData: "im a flag",
Source: "im a flag source",
Type: isync.ALL,
}
syncMock.dataSyncChanIn <- in
@ -329,7 +334,6 @@ func Test_FetchAllFlags(t *testing.T) {
mockData: &isync.DataSync{
FlagData: "im a flag",
Source: "im a flag source",
Type: isync.ALL,
},
setHandler: true,
},
@ -396,7 +400,6 @@ func Test_registerSubscriptionResyncPath(t *testing.T) {
data: &isync.DataSync{
FlagData: "im a flag",
Source: "im a flag source",
Type: isync.ALL,
},
expectErr: false,
},

View File

@ -1,6 +1,8 @@
module github.com/open-feature/flagd/flagd-proxy/tests/loadtest
go 1.20
go 1.22
toolchain go1.24.1
require (
buf.build/gen/go/open-feature/flagd/grpc/go v1.3.0-20240215170432-1e611e2999cc.3
@ -13,5 +15,5 @@ require (
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240506185236-b8a5c65736ae // indirect
google.golang.org/protobuf v1.34.1 // indirect
google.golang.org/protobuf v1.36.4 // indirect
)

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,69 @@
# Changelog
## [0.12.9](https://github.com/open-feature/flagd/compare/flagd/v0.12.8...flagd/v0.12.9) (2025-07-28)
### ✨ New Features
* Add toggle for disabling getMetadata request ([#1693](https://github.com/open-feature/flagd/issues/1693)) ([e8fd680](https://github.com/open-feature/flagd/commit/e8fd6808608caa7ff7e54792fe97d88e7c294486))
## [0.12.8](https://github.com/open-feature/flagd/compare/flagd/v0.12.7...flagd/v0.12.8) (2025-07-21)
### 🐛 Bug Fixes
* update to latest otel semconv ([#1668](https://github.com/open-feature/flagd/issues/1668)) ([81855d7](https://github.com/open-feature/flagd/commit/81855d76f94a09251a19a05f830cc1d11ab6b566))
### 🧹 Chore
* **deps:** update module github.com/open-feature/flagd/core to v0.11.8 ([#1685](https://github.com/open-feature/flagd/issues/1685)) ([c07ffba](https://github.com/open-feature/flagd/commit/c07ffba55426d538224d8564be5f35339d2258d0))
## [0.12.7](https://github.com/open-feature/flagd/compare/flagd/v0.12.6...flagd/v0.12.7) (2025-07-15)
### 🧹 Chore
* **deps:** update module github.com/open-feature/flagd/core to v0.11.6 ([#1683](https://github.com/open-feature/flagd/issues/1683)) ([b6da282](https://github.com/open-feature/flagd/commit/b6da282f8a98082ba3733593d501d14842cbd97f))
## [0.12.6](https://github.com/open-feature/flagd/compare/flagd/v0.12.5...flagd/v0.12.6) (2025-07-10)
### 🐛 Bug Fixes
* **security:** update module github.com/go-viper/mapstructure/v2 to v2.3.0 [security] ([#1667](https://github.com/open-feature/flagd/issues/1667)) ([caa0ed0](https://github.com/open-feature/flagd/commit/caa0ed04eb9d5d01136deb71b8fcc4da72aa1910))
### ✨ New Features
* add sync_context to SyncFlags ([#1642](https://github.com/open-feature/flagd/issues/1642)) ([07a45d9](https://github.com/open-feature/flagd/commit/07a45d9b2275584fa92ff33cbe5e5c7d7864db38))
## [0.12.5](https://github.com/open-feature/flagd/compare/flagd/v0.12.4...flagd/v0.12.5) (2025-06-13)
### ✨ New Features
* add server-side deadline to sync service ([#1638](https://github.com/open-feature/flagd/issues/1638)) ([b70fa06](https://github.com/open-feature/flagd/commit/b70fa06b66e1fe8a28728441a7ccd28c6fe6a0c6))
* updating context using headers ([#1641](https://github.com/open-feature/flagd/issues/1641)) ([ba34815](https://github.com/open-feature/flagd/commit/ba348152b6e7b6bd7473bb11846aac7db316c88e))
## [0.12.4](https://github.com/open-feature/flagd/compare/flagd/v0.12.3...flagd/v0.12.4) (2025-05-28)
### 🐛 Bug Fixes
* Bump OpenTelemetry instrumentation dependencies ([#1616](https://github.com/open-feature/flagd/issues/1616)) ([11db29d](https://github.com/open-feature/flagd/commit/11db29dc3ab0639c3a9129c9bf449e0bd8bfc45c))
### ✨ New Features
* add traces to ofrep endpoint ([#1593](https://github.com/open-feature/flagd/issues/1593)) ([a5d43bc](https://github.com/open-feature/flagd/commit/a5d43bc1de9ca2757899678436a3e04d833da196))
### 🧹 Chore
* **deps:** update dependency go to v1.24.1 ([#1559](https://github.com/open-feature/flagd/issues/1559)) ([cd46044](https://github.com/open-feature/flagd/commit/cd4604471bba0a1df67bf87653a38df3caf9d20f))
* **security:** upgrade dependency versions ([#1632](https://github.com/open-feature/flagd/issues/1632)) ([761d870](https://github.com/open-feature/flagd/commit/761d870a3c563b8eb1b83ee543b41316c98a1d48))
## [0.12.3](https://github.com/open-feature/flagd/compare/flagd/v0.12.2...flagd/v0.12.3) (2025-03-25)

View File

@ -36,7 +36,10 @@ const (
syncPortFlagName = "sync-port"
syncSocketPathFlagName = "sync-socket-path"
uriFlagName = "uri"
disableSyncMetadata = "disable-sync-metadata"
contextValueFlagName = "context-value"
headerToContextKeyFlagName = "context-from-header"
streamDeadlineFlagName = "stream-deadline"
)
func init() {
@ -84,6 +87,10 @@ func init() {
"from disk")
flags.StringToStringP(contextValueFlagName, "X", map[string]string{}, "add arbitrary key value pairs "+
"to the flag evaluation context")
flags.StringToStringP(headerToContextKeyFlagName, "H", map[string]string{}, "add key-value pairs to map "+
"header values to context values, where key is Header name, value is context key")
flags.Duration(streamDeadlineFlagName, 0, "Set a server-side deadline for flagd sync and event streams (default 0, means no deadline).")
flags.Bool(disableSyncMetadata, false, "Disables the getMetadata endpoint of the sync service. Defaults to false, but will default to true in later versions.")
bindFlags(flags)
}
@ -107,6 +114,9 @@ func bindFlags(flags *pflag.FlagSet) {
_ = viper.BindPFlag(syncSocketPathFlagName, flags.Lookup(syncSocketPathFlagName))
_ = viper.BindPFlag(ofrepPortFlagName, flags.Lookup(ofrepPortFlagName))
_ = viper.BindPFlag(contextValueFlagName, flags.Lookup(contextValueFlagName))
_ = viper.BindPFlag(headerToContextKeyFlagName, flags.Lookup(headerToContextKeyFlagName))
_ = viper.BindPFlag(streamDeadlineFlagName, flags.Lookup(streamDeadlineFlagName))
_ = viper.BindPFlag(disableSyncMetadata, flags.Lookup(disableSyncMetadata))
}
// startCmd represents the start command
@ -156,25 +166,33 @@ var startCmd = &cobra.Command{
contextValuesToMap[k] = v
}
headerToContextKeyMappings := make(map[string]string)
for k, v := range viper.GetStringMapString(headerToContextKeyFlagName) {
headerToContextKeyMappings[k] = v
}
// Build Runtime -----------------------------------------------------------
rt, err := runtime.FromConfig(logger, Version, runtime.Config{
CORS: viper.GetStringSlice(corsFlagName),
MetricExporter: viper.GetString(metricsExporter),
ManagementPort: viper.GetUint16(managementPortFlagName),
OfrepServicePort: viper.GetUint16(ofrepPortFlagName),
OtelCollectorURI: viper.GetString(otelCollectorURI),
OtelCertPath: viper.GetString(otelCertPathFlagName),
OtelKeyPath: viper.GetString(otelKeyPathFlagName),
OtelReloadInterval: viper.GetDuration(otelReloadIntervalFlagName),
OtelCAPath: viper.GetString(otelCAPathFlagName),
ServiceCertPath: viper.GetString(serverCertPathFlagName),
ServiceKeyPath: viper.GetString(serverKeyPathFlagName),
ServicePort: viper.GetUint16(portFlagName),
ServiceSocketPath: viper.GetString(socketPathFlagName),
SyncServicePort: viper.GetUint16(syncPortFlagName),
SyncServiceSocketPath: viper.GetString(syncSocketPathFlagName),
SyncProviders: syncProviders,
ContextValues: contextValuesToMap,
CORS: viper.GetStringSlice(corsFlagName),
MetricExporter: viper.GetString(metricsExporter),
ManagementPort: viper.GetUint16(managementPortFlagName),
OfrepServicePort: viper.GetUint16(ofrepPortFlagName),
OtelCollectorURI: viper.GetString(otelCollectorURI),
OtelCertPath: viper.GetString(otelCertPathFlagName),
OtelKeyPath: viper.GetString(otelKeyPathFlagName),
OtelReloadInterval: viper.GetDuration(otelReloadIntervalFlagName),
OtelCAPath: viper.GetString(otelCAPathFlagName),
ServiceCertPath: viper.GetString(serverCertPathFlagName),
ServiceKeyPath: viper.GetString(serverKeyPathFlagName),
ServicePort: viper.GetUint16(portFlagName),
ServiceSocketPath: viper.GetString(socketPathFlagName),
SyncServicePort: viper.GetUint16(syncPortFlagName),
SyncServiceSocketPath: viper.GetString(syncSocketPathFlagName),
StreamDeadline: viper.GetDuration(streamDeadlineFlagName),
DisableSyncMetadata: viper.GetBool(disableSyncMetadata),
SyncProviders: syncProviders,
ContextValues: contextValuesToMap,
HeaderToContextKeyMappings: headerToContextKeyMappings,
})
if err != nil {
rtLogger.Fatal(err.Error())

View File

@ -1,181 +1,196 @@
module github.com/open-feature/flagd/flagd
go 1.22.7
go 1.24.0
toolchain go1.22.9
toolchain go1.24.4
require (
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250127221518-be6d1143b690.1
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250529171031-ebdc14163473.1
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1
connectrpc.com/connect v1.18.1
github.com/dimiro1/banner v1.1.0
github.com/gorilla/mux v1.8.1
github.com/mattn/go-colorable v0.1.14
github.com/open-feature/flagd/core v0.11.2
github.com/prometheus/client_golang v1.21.1
github.com/open-feature/flagd/core v0.11.8
github.com/prometheus/client_golang v1.22.0
github.com/rs/cors v1.11.1
github.com/rs/xid v1.6.0
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/spf13/viper v1.19.0
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/sdk v1.35.0
go.opentelemetry.io/otel/sdk/metric v1.35.0
go.opentelemetry.io/otel/trace v1.35.0
go.uber.org/mock v0.5.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0
go.opentelemetry.io/otel v1.37.0
go.opentelemetry.io/otel/sdk v1.37.0
go.opentelemetry.io/otel/sdk/metric v1.37.0
go.opentelemetry.io/otel/trace v1.37.0
go.uber.org/mock v0.5.2
go.uber.org/zap v1.27.0
golang.org/x/net v0.35.0
golang.org/x/sync v0.11.0
google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.5
golang.org/x/net v0.41.0
golang.org/x/sync v0.15.0
google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6
)
require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/auth v0.8.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.1.13 // indirect
cloud.google.com/go/storage v1.43.0 // indirect
connectrpc.com/otelconnect v0.7.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
cel.dev/expr v0.23.0 // indirect
cloud.google.com/go v0.121.1 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/storage v1.55.0 // indirect
connectrpc.com/otelconnect v0.7.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/aws/aws-sdk-go v1.55.6 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.12 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect
github.com/diegoholiveira/jsonlogic/v3 v3.8.4 // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/google/wire v0.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-memdb v1.3.5 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/open-feature/flagd-schemas v0.2.9-0.20250127221449-bb763438abc5 // indirect
github.com/open-feature/open-feature-operator/apis v0.2.44 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86 // indirect
github.com/open-feature/open-feature-operator/apis v0.2.45 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/zeebo/errs v1.4.0 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.56.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.59.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
gocloud.dev v0.40.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
gocloud.dev v0.42.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/api v0.191.0 // indirect
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/api v0.235.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.31.4 // indirect
k8s.io/apiextensions-apiserver v0.31.0 // indirect
k8s.io/apimachinery v0.31.4 // indirect
k8s.io/client-go v0.31.4 // indirect
k8s.io/api v0.33.2 // indirect
k8s.io/apiextensions-apiserver v0.31.1 // indirect
k8s.io/apimachinery v0.33.2 // indirect
k8s.io/client-go v0.33.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/controller-runtime v0.19.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/controller-runtime v0.19.3 // indirect
sigs.k8s.io/gateway-api v1.2.1 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

View File

@ -1,98 +1,184 @@
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250127221518-be6d1143b690.1 h1:BIA4nkcwwyl0zPZ4vutd5Mn+VeLOV756RmvbQbfnb9g=
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250127221518-be6d1143b690.1/go.mod h1:BSerK2QrH0wQdgiFP6fKevMB4QXotvyXUfmE6mExwHY=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2 h1:D3HI5RQbqgffyf+Z77+hReDx5kigFVAKGvttULD9/ms=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250127221518-be6d1143b690.2/go.mod h1:b9rfG6rbGXZAlLwQwedvZ0kI0nUcR+aLaYF70pj920E=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1 h1:eZKupK8gUTuc6zifAFQon8Gnt44fR4cd0GnTWjELvEw=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.5-20250127221518-be6d1143b690.1/go.mod h1:wJvVIADHM0IaBc5sYf8wgMMgSHi0nAtc6rgr5rfizhA=
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250529171031-ebdc14163473.1 h1:qnXaezo9s36RjysUq6omZlU3rjQNykMpqCXfCL/CH1Q=
buf.build/gen/go/open-feature/flagd/connectrpc/go v1.18.1-20250529171031-ebdc14163473.1/go.mod h1:/9fZNSPUzUwGRzDAjyasa5NC+YFgQthby355ZVuKS2Q=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2 h1:TZ+7u106u7C7lgNctxG03ABliF46eLhcIZG5Mdo67/E=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20250529171031-ebdc14163473.2/go.mod h1:4u0WLwfkLob3dC/F8qNctqhtiEv2Mlyi8YgCDDzgYDs=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1 h1:LdC4xAuUaNdduzQr5VvhjsgrCfpW9IYxYsjyCF0ANs0=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.6-20250529171031-ebdc14163473.1/go.mod h1:cCQ49+ttXE2MZ/ciRNb0tCG+F3kj2ZVbP+0/psbhrLY=
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4=
cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus=
cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nTWy3TYE=
cloud.google.com/go/longrunning v0.5.12/go.mod h1:S5hMV8CDJ6r50t2ubVJSKQVv5u0rmik5//KgLO3k4lU=
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA=
cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU=
cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw=
cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI=
cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io=
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY=
connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms=
connectrpc.com/otelconnect v0.7.2 h1:WlnwFzaW64dN06JXU+hREPUGeEzpz3Acz2ACOmN8cMI=
connectrpc.com/otelconnect v0.7.2/go.mod h1:JS7XUKfuJs2adhCnXhNHPHLz6oAaZniCJdSF00OZSew=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc=
github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
github.com/aws/aws-sdk-go-v2/config v1.29.12 h1:Y/2a+jLPrPbHpFkpAAYkVEtJmxORlXoo5k2g1fa2sUo=
github.com/aws/aws-sdk-go-v2/config v1.29.12/go.mod h1:xse1YTjmORlb/6fhkWi8qJh3cvZi4JoVNhc+NbJt4kI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
github.com/aws/aws-sdk-go-v2/credentials v1.17.65 h1:q+nV2yYegofO/SUXruT+pn4KxkxmaQ++1B/QedcKBFM=
github.com/aws/aws-sdk-go-v2/credentials v1.17.65/go.mod h1:4zyjAuGOdikpNYiSGpsGz8hLGmUzlY8pc8r9QQ/RXYQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69 h1:6VFPH/Zi9xYFMJKPQOX5URYkQoXRWeJ7V/7Y6ZDYoms=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69/go.mod h1:GJj8mmO6YT6EqgduWocwhMoxTLFitkhIrK+owzrYL2I=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2 h1:pdgODsAhGo4dvzC3JAG5Ce0PX8kWXrTZGx+jxADD+5E=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.2/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0 h1:90uX0veLKcdHVfvxhkWUQSCi5VabtwMLFutYiRke4oo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.0/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLoBaFpOOds6QyY1L8AX7uoY+Ln3BHc22W40X0=
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
@ -102,10 +188,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diegoholiveira/jsonlogic/v3 v3.7.3 h1:orvGaTW5Qdcowpr7vgfMNuqYNjQYcqtw9W1Bk4QyI38=
github.com/diegoholiveira/jsonlogic/v3 v3.7.3/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
github.com/diegoholiveira/jsonlogic/v3 v3.7.4 h1:92HSmB9bwM/o0ZvrCpcvTP2EsPXSkKtAniIr2W/dcIM=
github.com/diegoholiveira/jsonlogic/v3 v3.7.4/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
github.com/diegoholiveira/jsonlogic/v3 v3.8.4 h1:IVVU/VLz2hn10ImbmibjiUkdVsSFIB1vfDaOVsaipH4=
github.com/diegoholiveira/jsonlogic/v3 v3.8.4/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
github.com/dimiro1/banner v1.1.0 h1:TSfy+FsPIIGLzaMPOt52KrEed/omwFO1P15VA8PMUh0=
github.com/dimiro1/banner v1.1.0/go.mod h1:tbL318TJiUaHxOUNN+jnlvFSgsh/RX7iJaQrGgOiTco=
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
@ -113,7 +199,15 @@ github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
@ -124,11 +218,15 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
@ -141,14 +239,20 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -163,6 +267,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -170,8 +276,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo=
@ -187,22 +291,36 @@ github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+Nyo=
github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -217,10 +335,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -229,8 +345,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -240,8 +354,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -253,38 +365,46 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/open-feature/flagd-schemas v0.2.9-0.20250127221449-bb763438abc5 h1:0RKCLYeQpvSsKR95kc894tm8GAZmq7bcG48v0KJ0HCs=
github.com/open-feature/flagd-schemas v0.2.9-0.20250127221449-bb763438abc5/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd/core v0.11.1 h1:0qBVXcRBZOFoZ5lNK/Yba2IyUDdxUHcLsv5OhUJtltA=
github.com/open-feature/flagd/core v0.11.1/go.mod h1:yzPjp7D9wNusvOyKt8wBND5PQGslcu+5e+xmaIBGgLE=
github.com/open-feature/flagd/core v0.11.2 h1:3LAuLR2vXpBF80RwwCAu9JX898JasfPH7ErJEf5C5YA=
github.com/open-feature/flagd/core v0.11.2/go.mod h1:rTdYFyLGP1tGwxo0X26ygIrC31nINCv8hcCq+vhFD0E=
github.com/open-feature/flagd-schemas v0.2.9-0.20250319190911-9b0ee43ecc47 h1:c6nodciz/xeU0xiAcDQ5MBW34DnPoi5/lEgjV5kZeZA=
github.com/open-feature/flagd-schemas v0.2.9-0.20250319190911-9b0ee43ecc47/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86 h1:r3e+qs3QUdf4+lUi2ZZnSHgYkjeLIb5yu5jo+ypA8iw=
github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd/core v0.11.5 h1:2zUbXN3Uh/cKZZVOQo9Z+Evjb9s15HcCtdo8vaNiBPY=
github.com/open-feature/flagd/core v0.11.5/go.mod h1:VBCrZ6bs+Occ226391YoIOnNEXmNvpWsQEeYfsLJ23c=
github.com/open-feature/flagd/core v0.11.6 h1:qrGYGUBmFABlvFA46LkLoG/DTwchUm+OLKX+XUytgS4=
github.com/open-feature/flagd/core v0.11.6/go.mod h1:UUhSAf0NSbGmFbdmkOf93YP90DA8O6ahVYn+vJO4BMg=
github.com/open-feature/flagd/core v0.11.8 h1:84uDdSzhtVNBnsjuAnBqBXbFwXC2CQ6aO5cNNKKM7uc=
github.com/open-feature/flagd/core v0.11.8/go.mod h1:3dNe+BX8HUpx/mXrGLD554G6cQB67yvuASVTKVC4TU4=
github.com/open-feature/open-feature-operator/apis v0.2.44 h1:0r4Z+RnJltuHdRBv79NFgAckhna6/M3Wcec6gzNX5vI=
github.com/open-feature/open-feature-operator/apis v0.2.44/go.mod h1:xB2uLzvUkbydieX7q6/NqannBz3bt/e5BS2DeOyyw4Q=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/open-feature/open-feature-operator/apis v0.2.45 h1:URnUf22ZoAx7/W8ek8dXCBYgY8FmnFEuEOSDLROQafY=
github.com/open-feature/open-feature-operator/apis v0.2.45/go.mod h1:PYh/Hfyna1lZYZUeu/8LM0qh0ZgpH7kKEXRLYaaRhGs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@ -295,32 +415,29 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
@ -341,67 +458,77 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E=
go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk=
go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c=
go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo=
go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng=
gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ=
gocloud.dev v0.42.0 h1:qzG+9ItUL3RPB62/Amugws28n+4vGZXEoJEAMfjutzw=
gocloud.dev v0.42.0/go.mod h1:zkaYAapZfQisXOA4bzhsbA4ckiStGQ3Psvs9/OQ5dPM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -413,10 +540,10 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -432,13 +559,15 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -448,8 +577,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -466,16 +595,18 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -483,10 +614,14 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -499,38 +634,46 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk=
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E=
google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0=
google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY=
google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo=
google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM=
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -540,8 +683,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@ -549,8 +692,6 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@ -561,23 +702,46 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.31.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM=
k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw=
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40=
k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ=
k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM=
k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ=
k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg=
k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99 h1:w6nThEmGo9zcL+xH1Tu6pjxJ3K1jXFW+V0u4peqN8ks=
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw=
sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM=
sigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM=
sigs.k8s.io/gateway-api v1.2.1/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -38,11 +38,14 @@ type Config struct {
ServiceSocketPath string
SyncServicePort uint16
SyncServiceSocketPath string
StreamDeadline time.Duration
DisableSyncMetadata bool
SyncProviders []sync.SourceConfig
CORS []string
ContextValues map[string]any
ContextValues map[string]any
HeaderToContextKeyMappings map[string]string
}
// FromConfig builds a runtime from startup configurations
@ -76,21 +79,20 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime,
logger.Error(fmt.Sprintf("error building metrics recorder: %v", err))
}
// build flag store, collect flag sources & fill sources details
s := store.NewFlags()
sources := []string{}
for _, provider := range config.SyncProviders {
s.FlagSources = append(s.FlagSources, provider.URI)
s.SourceDetails[provider.URI] = store.SourceDetails{
Source: provider.URI,
Selector: provider.Selector,
}
sources = append(sources, provider.URI)
}
// build flag store, collect flag sources & fill sources details
store, err := store.NewStore(logger, sources)
if err != nil {
return nil, fmt.Errorf("error creating flag store: %w", err)
}
// derive evaluator
jsonEvaluator := evaluator.NewJSON(logger, s)
jsonEvaluator := evaluator.NewJSON(logger, store)
// derive services
@ -98,6 +100,7 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime,
connectService := flageval.NewConnectService(
logger.WithFields(zap.String("component", "service")),
jsonEvaluator,
store,
recorder)
// ofrep service
@ -106,21 +109,24 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime,
Port: config.OfrepServicePort,
},
config.ContextValues,
config.HeaderToContextKeyMappings,
)
if err != nil {
return nil, fmt.Errorf("error creating ofrep service")
return nil, fmt.Errorf("error creating OFREP service: %w", err)
}
// flag sync service
flagSyncService, err := flagsync.NewSyncService(flagsync.SvcConfigurations{
Logger: logger.WithFields(zap.String("component", "FlagSyncService")),
Port: config.SyncServicePort,
Sources: sources,
Store: s,
ContextValues: config.ContextValues,
KeyPath: config.ServiceKeyPath,
CertPath: config.ServiceCertPath,
SocketPath: config.SyncServiceSocketPath,
Logger: logger.WithFields(zap.String("component", "FlagSyncService")),
Port: config.SyncServicePort,
Sources: sources,
Store: store,
ContextValues: config.ContextValues,
KeyPath: config.ServiceKeyPath,
CertPath: config.ServiceCertPath,
SocketPath: config.SyncServiceSocketPath,
StreamDeadline: config.StreamDeadline,
DisableSyncMetadata: config.DisableSyncMetadata,
})
if err != nil {
return nil, fmt.Errorf("error creating sync service: %w", err)
@ -140,23 +146,25 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime,
}
return &Runtime{
Logger: logger.WithFields(zap.String("component", "runtime")),
Evaluator: jsonEvaluator,
FlagSync: flagSyncService,
OfrepService: ofrepService,
Service: connectService,
Logger: logger.WithFields(zap.String("component", "runtime")),
Evaluator: jsonEvaluator,
SyncService: flagSyncService,
OfrepService: ofrepService,
EvaluationService: connectService,
ServiceConfig: service.Configuration{
Port: config.ServicePort,
ManagementPort: config.ManagementPort,
ServiceName: svcName,
KeyPath: config.ServiceKeyPath,
CertPath: config.ServiceCertPath,
SocketPath: config.ServiceSocketPath,
CORS: config.CORS,
Options: options,
ContextValues: config.ContextValues,
Port: config.ServicePort,
ManagementPort: config.ManagementPort,
ServiceName: svcName,
KeyPath: config.ServiceKeyPath,
CertPath: config.ServiceCertPath,
SocketPath: config.ServiceSocketPath,
CORS: config.CORS,
Options: options,
ContextValues: config.ContextValues,
HeaderToContextKeyMappings: config.HeaderToContextKeyMappings,
StreamDeadline: config.StreamDeadline,
},
SyncImpl: iSyncs,
Syncs: iSyncs,
}, nil
}

View File

@ -19,23 +19,23 @@ import (
)
type Runtime struct {
Evaluator evaluator.IEvaluator
Logger *logger.Logger
FlagSync flagsync.ISyncService
OfrepService ofrep.IOfrepService
Service service.IFlagEvaluationService
ServiceConfig service.Configuration
SyncImpl []sync.ISync
Evaluator evaluator.IEvaluator
Logger *logger.Logger
SyncService flagsync.ISyncService
OfrepService ofrep.IOfrepService
EvaluationService service.IFlagEvaluationService
ServiceConfig service.Configuration
Syncs []sync.ISync
mu msync.Mutex
}
//nolint:funlen
func (r *Runtime) Start() error {
if r.Service == nil {
if r.EvaluationService == nil {
return errors.New("no service set")
}
if len(r.SyncImpl) == 0 {
if len(r.Syncs) == 0 {
return errors.New("no sync implementation set")
}
if r.Evaluator == nil {
@ -44,40 +44,26 @@ func (r *Runtime) Start() error {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
g, gCtx := errgroup.WithContext(ctx)
dataSync := make(chan sync.DataSync, len(r.SyncImpl))
dataSync := make(chan sync.DataSync, len(r.Syncs))
// Initialize DataSync channel watcher
g.Go(func() error {
for {
select {
case data := <-dataSync:
// resync events are triggered when a delete occurs during flag merges in the store
// resync events may trigger further resync events, however for a flag to be deleted from the store
// its source must match, preventing the opportunity for resync events to snowball
if resyncRequired := r.updateAndEmit(data); resyncRequired {
for _, s := range r.SyncImpl {
p := s
g.Go(func() error {
err := p.ReSync(gCtx, dataSync)
if err != nil {
return fmt.Errorf("error resyncing sources: %w", err)
}
return nil
})
}
}
r.updateAndEmit(data)
case <-gCtx.Done():
return nil
}
}
})
// Init sync providers
for _, s := range r.SyncImpl {
for _, s := range r.Syncs {
if err := s.Init(gCtx); err != nil {
return fmt.Errorf("sync provider Init returned error: %w", err)
}
}
// Start sync provider
for _, s := range r.SyncImpl {
for _, s := range r.Syncs {
p := s
g.Go(func() error {
if err := p.Sync(gCtx, dataSync); err != nil {
@ -89,14 +75,14 @@ func (r *Runtime) Start() error {
defer func() {
r.Logger.Info("Shutting down server...")
r.Service.Shutdown()
r.EvaluationService.Shutdown()
r.Logger.Info("Server successfully shutdown.")
}()
g.Go(func() error {
// Readiness probe rely on the runtime
r.ServiceConfig.ReadinessProbe = r.isReady
if err := r.Service.Serve(gCtx, r.ServiceConfig); err != nil {
if err := r.EvaluationService.Serve(gCtx, r.ServiceConfig); err != nil {
return fmt.Errorf("error returned from serving flag evaluation service: %w", err)
}
return nil
@ -112,7 +98,7 @@ func (r *Runtime) Start() error {
})
g.Go(func() error {
err := r.FlagSync.Start(gCtx)
err := r.SyncService.Start(gCtx)
if err != nil {
return fmt.Errorf("error from sync server: %w", err)
}
@ -128,7 +114,7 @@ func (r *Runtime) Start() error {
func (r *Runtime) isReady() bool {
// if all providers can watch for flag changes, we are ready.
for _, p := range r.SyncImpl {
for _, p := range r.Syncs {
if !p.IsReady() {
return false
}
@ -137,24 +123,14 @@ func (r *Runtime) isReady() bool {
}
// updateAndEmit helps to update state, notify changes and trigger sync updates
func (r *Runtime) updateAndEmit(payload sync.DataSync) bool {
func (r *Runtime) updateAndEmit(payload sync.DataSync) {
r.mu.Lock()
defer r.mu.Unlock()
notifications, resyncRequired, err := r.Evaluator.SetState(payload)
_, _, err := r.Evaluator.SetState(payload)
if err != nil {
r.Logger.Error(err.Error())
return false
r.Logger.Error(fmt.Sprintf("error setting state: %v", err))
return
}
r.Service.Notify(service.Notification{
Type: service.ConfigurationChange,
Data: map[string]interface{}{
"flags": notifications,
},
})
r.FlagSync.Emit(resyncRequired, payload.Source)
return resyncRequired
r.SyncService.Emit(payload.Source)
}

View File

@ -0,0 +1,3 @@
package service
const FLAGD_SELECTOR_HEADER = "Flagd-Selector"

View File

@ -16,6 +16,7 @@ import (
"github.com/open-feature/flagd/core/pkg/evaluator"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/service"
"github.com/open-feature/flagd/core/pkg/store"
"github.com/open-feature/flagd/core/pkg/telemetry"
"github.com/open-feature/flagd/flagd/pkg/service/middleware"
corsmw "github.com/open-feature/flagd/flagd/pkg/service/middleware/cors"
@ -71,15 +72,17 @@ type ConnectService struct {
// NewConnectService creates a ConnectService with provided parameters
func NewConnectService(
logger *logger.Logger, evaluator evaluator.IEvaluator, mRecorder telemetry.IMetricsRecorder,
logger *logger.Logger, evaluator evaluator.IEvaluator, store store.IStore, mRecorder telemetry.IMetricsRecorder,
) *ConnectService {
cs := &ConnectService{
logger: logger,
eval: evaluator,
metrics: &telemetry.NoopMetricsRecorder{},
eventingConfiguration: &eventingConfiguration{
subs: make(map[interface{}]chan service.Notification),
mu: &sync.RWMutex{},
subs: make(map[interface{}]chan service.Notification),
mu: &sync.RWMutex{},
store: store,
logger: logger,
},
}
if mRecorder != nil {
@ -172,6 +175,8 @@ func (s *ConnectService) setupServer(svcConf service.Configuration) (net.Listene
s.eventingConfiguration,
s.metrics,
svcConf.ContextValues,
svcConf.HeaderToContextKeyMappings,
svcConf.StreamDeadline,
)
_, newHandler := evaluationV1.NewServiceHandler(newFes, append(svcConf.Options, marshalOpts)...)

View File

@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"os"
"sync"
"testing"
"time"
@ -14,9 +15,11 @@ import (
mock "github.com/open-feature/flagd/core/pkg/evaluator/mock"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/notifications"
iservice "github.com/open-feature/flagd/core/pkg/service"
"github.com/open-feature/flagd/core/pkg/store"
"github.com/open-feature/flagd/core/pkg/telemetry"
"github.com/open-feature/flagd/flagd/pkg/service/middleware/mock"
middlewaremock "github.com/open-feature/flagd/flagd/pkg/service/middleware/mock"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
@ -81,7 +84,7 @@ func TestConnectService_UnixConnection(t *testing.T) {
exp := metric.NewManualReader()
rs := resource.NewWithAttributes("testSchema")
metricRecorder := telemetry.NewOTelRecorder(exp, rs, tt.name)
svc := NewConnectService(logger.NewLogger(nil, false), eval, metricRecorder)
svc := NewConnectService(logger.NewLogger(nil, false), eval, &store.Store{}, metricRecorder)
serveConf := iservice.Configuration{
ReadinessProbe: func() bool {
return true
@ -136,7 +139,7 @@ func TestAddMiddleware(t *testing.T) {
rs := resource.NewWithAttributes("testSchema")
metricRecorder := telemetry.NewOTelRecorder(exp, rs, "my-exporter")
svc := NewConnectService(logger.NewLogger(nil, false), nil, metricRecorder)
svc := NewConnectService(logger.NewLogger(nil, false), nil, &store.Store{}, metricRecorder)
serveConf := iservice.Configuration{
ReadinessProbe: func() bool {
@ -173,16 +176,22 @@ func TestConnectServiceNotify(t *testing.T) {
// given
ctrl := gomock.NewController(t)
eval := mock.NewMockIEvaluator(ctrl)
sources := []string{"source1", "source2"}
log := logger.NewLogger(nil, false)
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
exp := metric.NewManualReader()
rs := resource.NewWithAttributes("testSchema")
metricRecorder := telemetry.NewOTelRecorder(exp, rs, "my-exporter")
service := NewConnectService(logger.NewLogger(nil, false), eval, metricRecorder)
service := NewConnectService(logger.NewLogger(nil, false), eval, s, metricRecorder)
sChan := make(chan iservice.Notification, 1)
eventing := service.eventingConfiguration
eventing.Subscribe("key", sChan)
eventing.Subscribe(context.Background(), "key", nil, sChan)
// notification type
ofType := iservice.ConfigurationChange
@ -207,20 +216,73 @@ func TestConnectServiceNotify(t *testing.T) {
}
}
func TestConnectServiceWatcher(t *testing.T) {
sources := []string{"source1", "source2"}
log := logger.NewLogger(nil, false)
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
sChan := make(chan iservice.Notification, 1)
eventing := eventingConfiguration{
store: s,
logger: log,
mu: &sync.RWMutex{},
subs: make(map[any]chan iservice.Notification),
}
// subscribe and wait for for the sub to be active
eventing.Subscribe(context.Background(), "anything", nil, sChan)
time.Sleep(100 * time.Millisecond)
// make a change
s.Update(sources[0], map[string]model.Flag{
"flag1": {
Key: "flag1",
DefaultVariant: "off",
},
}, model.Metadata{})
// notification type
ofType := iservice.ConfigurationChange
timeout, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case n := <-sChan:
require.Equal(t, ofType, n.Type, "expected notification type: %s, but received %s", ofType, n.Type)
notifications := n.Data["flags"].(notifications.Notifications)
flag1, ok := notifications["flag1"].(map[string]interface{})
require.True(t, ok, "flag1 notification should be a map[string]interface{}")
require.Equal(t, flag1["type"], string(model.NotificationCreate), "expected notification type: %s, but received %s", model.NotificationCreate, flag1["type"])
case <-timeout.Done():
t.Error("timeout while waiting for notifications")
}
}
func TestConnectServiceShutdown(t *testing.T) {
// given
ctrl := gomock.NewController(t)
eval := mock.NewMockIEvaluator(ctrl)
sources := []string{"source1", "source2"}
log := logger.NewLogger(nil, false)
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
exp := metric.NewManualReader()
rs := resource.NewWithAttributes("testSchema")
metricRecorder := telemetry.NewOTelRecorder(exp, rs, "my-exporter")
service := NewConnectService(logger.NewLogger(nil, false), eval, metricRecorder)
service := NewConnectService(logger.NewLogger(nil, false), eval, s, metricRecorder)
sChan := make(chan iservice.Notification, 1)
eventing := service.eventingConfiguration
eventing.Subscribe("key", sChan)
eventing.Subscribe(context.Background(), "key", nil, sChan)
// notification type
ofType := iservice.Shutdown

View File

@ -1,29 +1,65 @@
package service
import (
"context"
"fmt"
"sync"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/notifications"
iservice "github.com/open-feature/flagd/core/pkg/service"
"github.com/open-feature/flagd/core/pkg/store"
)
// IEvents is an interface for event subscriptions
type IEvents interface {
Subscribe(id any, notifyChan chan iservice.Notification)
Subscribe(ctx context.Context, id any, selector *store.Selector, notifyChan chan iservice.Notification)
Unsubscribe(id any)
EmitToAll(n iservice.Notification)
}
var _ IEvents = &eventingConfiguration{}
// eventingConfiguration is a wrapper for notification subscriptions
type eventingConfiguration struct {
mu *sync.RWMutex
subs map[any]chan iservice.Notification
mu *sync.RWMutex
subs map[any]chan iservice.Notification
store store.IStore
logger *logger.Logger
}
func (eventing *eventingConfiguration) Subscribe(id any, notifyChan chan iservice.Notification) {
func (eventing *eventingConfiguration) Subscribe(ctx context.Context, id any, selector *store.Selector, notifier chan iservice.Notification) {
eventing.mu.Lock()
defer eventing.mu.Unlock()
eventing.subs[id] = notifyChan
// proxy events from our store watcher to the notify channel, so that RPC mode event streams
watcher := make(chan store.FlagQueryResult, 1)
go func() {
// store the previous flags to compare against new notifications, to compute proper diffs for RPC mode
var oldFlags map[string]model.Flag
for result := range watcher {
newFlags := result.Flags
// ignore the first notification (nil old flags), the watcher emits on initialization, but for RPC we don't care until there's a change
if oldFlags != nil {
notifications := notifications.NewFromFlags(oldFlags, newFlags)
notifier <- iservice.Notification{
Type: iservice.ConfigurationChange,
Data: map[string]interface{}{
"flags": notifications,
},
}
}
oldFlags = result.Flags
}
eventing.logger.Debug(fmt.Sprintf("closing notify channel for id %v", id))
close(notifier)
}()
eventing.store.Watch(ctx, selector, watcher)
eventing.subs[id] = notifier
}
func (eventing *eventingConfiguration) EmitToAll(n iservice.Notification) {

View File

@ -1,18 +1,29 @@
package service
import (
"context"
"sync"
"testing"
"github.com/open-feature/flagd/core/pkg/logger"
iservice "github.com/open-feature/flagd/core/pkg/service"
"github.com/open-feature/flagd/core/pkg/store"
"github.com/stretchr/testify/require"
)
func TestSubscribe(t *testing.T) {
// given
sources := []string{"source1", "source2"}
log := logger.NewLogger(nil, false)
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
eventing := &eventingConfiguration{
subs: make(map[interface{}]chan iservice.Notification),
mu: &sync.RWMutex{},
subs: make(map[interface{}]chan iservice.Notification),
mu: &sync.RWMutex{},
store: s,
}
idA := "a"
@ -22,8 +33,8 @@ func TestSubscribe(t *testing.T) {
chanB := make(chan iservice.Notification, 1)
// when
eventing.Subscribe(idA, chanA)
eventing.Subscribe(idB, chanB)
eventing.Subscribe(context.Background(), idA, nil, chanA)
eventing.Subscribe(context.Background(), idB, nil, chanB)
// then
require.Equal(t, chanA, eventing.subs[idA], "incorrect subscription association")
@ -32,9 +43,16 @@ func TestSubscribe(t *testing.T) {
func TestUnsubscribe(t *testing.T) {
// given
sources := []string{"source1", "source2"}
log := logger.NewLogger(nil, false)
s, err := store.NewStore(log, sources)
if err != nil {
t.Fatalf("NewStore failed: %v", err)
}
eventing := &eventingConfiguration{
subs: make(map[interface{}]chan iservice.Notification),
mu: &sync.RWMutex{},
subs: make(map[interface{}]chan iservice.Notification),
mu: &sync.RWMutex{},
store: s,
}
idA := "a"
@ -43,8 +61,8 @@ func TestUnsubscribe(t *testing.T) {
chanB := make(chan iservice.Notification, 1)
// when
eventing.Subscribe(idA, chanA)
eventing.Subscribe(idB, chanB)
eventing.Subscribe(context.Background(), idA, nil, chanA)
eventing.Subscribe(context.Background(), idB, nil, chanB)
eventing.Unsubscribe(idA)

View File

@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
"net/http"
"time"
schemaV1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/schema/v1"
@ -11,7 +12,9 @@ import (
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/service"
"github.com/open-feature/flagd/core/pkg/store"
"github.com/open-feature/flagd/core/pkg/telemetry"
flagdService "github.com/open-feature/flagd/flagd/pkg/service"
"github.com/rs/xid"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
@ -66,13 +69,17 @@ func (s *OldFlagEvaluationService) ResolveAll(
) (*connect.Response[schemaV1.ResolveAllResponse], error) {
reqID := xid.New().String()
defer s.logger.ClearFields(reqID)
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveAll", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveAll", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
res := &schemaV1.ResolveAllResponse{
Flags: make(map[string]*schemaV1.AnyFlag),
}
values, _, err := s.eval.ResolveAllValues(sCtx, reqID, mergeContexts(req.Msg.GetContext().AsMap(), s.contextValues))
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
values, _, err := s.eval.ResolveAllValues(ctx, reqID, mergeContexts(req.Msg.GetContext().AsMap(), s.contextValues, req.Header(), make(map[string]string)))
if err != nil {
s.logger.WarnWithID(reqID, fmt.Sprintf("error resolving all flags: %v", err))
return nil, fmt.Errorf("error resolving flags. Tracking ID: %s", reqID)
@ -81,7 +88,7 @@ func (s *OldFlagEvaluationService) ResolveAll(
span.SetAttributes(attribute.Int("feature_flag.count", len(values)))
for _, value := range values {
// register the impression and reason for each flag evaluated
s.metrics.RecordEvaluation(sCtx, value.Error, value.Reason, value.Variant, value.FlagKey)
s.metrics.RecordEvaluation(ctx, value.Error, value.Reason, value.Variant, value.FlagKey)
switch v := value.Value.(type) {
case bool:
@ -132,8 +139,12 @@ func (s *OldFlagEvaluationService) EventStream(
req *connect.Request[schemaV1.EventStreamRequest],
stream *connect.ServerStream[schemaV1.EventStreamResponse],
) error {
s.logger.Debug(fmt.Sprintf("starting event stream for request"))
requestNotificationChan := make(chan service.Notification, 1)
s.eventingConfiguration.Subscribe(req, requestNotificationChan)
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
s.eventingConfiguration.Subscribe(ctx, req, &selector, requestNotificationChan)
defer s.eventingConfiguration.Unsubscribe(req)
requestNotificationChan <- service.Notification{
@ -171,19 +182,24 @@ func (s *OldFlagEvaluationService) ResolveBoolean(
ctx context.Context,
req *connect.Request[schemaV1.ResolveBooleanRequest],
) (*connect.Response[schemaV1.ResolveBooleanResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveBoolean", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveBoolean", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
res := connect.NewResponse(&schemaV1.ResolveBooleanResponse{})
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
err := resolve[bool](
sCtx,
ctx,
s.logger,
s.eval.ResolveBooleanValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&booleanResponse{schemaV1Resp: res},
s.metrics,
s.contextValues,
make(map[string]string),
)
if err != nil {
span.RecordError(err)
@ -198,19 +214,25 @@ func (s *OldFlagEvaluationService) ResolveString(
ctx context.Context,
req *connect.Request[schemaV1.ResolveStringRequest],
) (*connect.Response[schemaV1.ResolveStringResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveString", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveString", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&schemaV1.ResolveStringResponse{})
err := resolve[string](
sCtx,
ctx,
s.logger,
s.eval.ResolveStringValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&stringResponse{schemaV1Resp: res},
s.metrics,
s.contextValues,
make(map[string]string),
)
if err != nil {
span.RecordError(err)
@ -225,19 +247,25 @@ func (s *OldFlagEvaluationService) ResolveInt(
ctx context.Context,
req *connect.Request[schemaV1.ResolveIntRequest],
) (*connect.Response[schemaV1.ResolveIntResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveInt", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveInt", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&schemaV1.ResolveIntResponse{})
err := resolve[int64](
sCtx,
ctx,
s.logger,
s.eval.ResolveIntValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&intResponse{schemaV1Resp: res},
s.metrics,
s.contextValues,
make(map[string]string),
)
if err != nil {
span.RecordError(err)
@ -252,19 +280,25 @@ func (s *OldFlagEvaluationService) ResolveFloat(
ctx context.Context,
req *connect.Request[schemaV1.ResolveFloatRequest],
) (*connect.Response[schemaV1.ResolveFloatResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveFloat", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveFloat", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&schemaV1.ResolveFloatResponse{})
err := resolve[float64](
sCtx,
ctx,
s.logger,
s.eval.ResolveFloatValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&floatResponse{schemaV1Resp: res},
s.metrics,
s.contextValues,
make(map[string]string),
)
if err != nil {
span.RecordError(err)
@ -279,19 +313,25 @@ func (s *OldFlagEvaluationService) ResolveObject(
ctx context.Context,
req *connect.Request[schemaV1.ResolveObjectRequest],
) (*connect.Response[schemaV1.ResolveObjectResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveObject", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveObject", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&schemaV1.ResolveObjectResponse{})
err := resolve[map[string]any](
sCtx,
ctx,
s.logger,
s.eval.ResolveObjectValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&objectResponse{schemaV1Resp: res},
s.metrics,
s.contextValues,
make(map[string]string),
)
if err != nil {
span.RecordError(err)
@ -301,9 +341,9 @@ func (s *OldFlagEvaluationService) ResolveObject(
return res, err
}
// mergeContexts combines values from the request context with the values from the config --context-values flag.
// Request context values have a higher priority.
func mergeContexts(reqCtx, configFlagsCtx map[string]any) map[string]any {
// mergeContexts combines context values from headers, static context (from cli) and request context.
// highest priority > header-context-from-cli > static-context-from-cli > request-context > lowest priority
func mergeContexts(reqCtx, configFlagsCtx map[string]any, headers http.Header, headerToContextKeyMappings map[string]string) map[string]any {
merged := make(map[string]any)
for k, v := range reqCtx {
merged[k] = v
@ -311,18 +351,24 @@ func mergeContexts(reqCtx, configFlagsCtx map[string]any) map[string]any {
for k, v := range configFlagsCtx {
merged[k] = v
}
for header, contextKey := range headerToContextKeyMappings {
if values, ok := headers[header]; ok {
merged[contextKey] = values[0]
}
}
return merged
}
// resolve is a generic flag resolver
func resolve[T constraints](ctx context.Context, logger *logger.Logger, resolver resolverSignature[T], flagKey string,
func resolve[T constraints](ctx context.Context, logger *logger.Logger, resolver resolverSignature[T], header http.Header, flagKey string,
evaluationContext *structpb.Struct, resp response[T], metrics telemetry.IMetricsRecorder,
configContextValues map[string]any,
configContextValues map[string]any, configHeaderToContextKeyMappings map[string]string,
) error {
reqID := xid.New().String()
defer logger.ClearFields(reqID)
mergedContext := mergeContexts(evaluationContext.AsMap(), configContextValues)
mergedContext := mergeContexts(evaluationContext.AsMap(), configContextValues, header, configHeaderToContextKeyMappings)
logger.WriteFields(
reqID,
zap.String("flag-key", flagKey),

View File

@ -2,6 +2,7 @@ package service
import (
"context"
"errors"
"fmt"
"time"
@ -10,7 +11,9 @@ import (
"github.com/open-feature/flagd/core/pkg/evaluator"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/service"
"github.com/open-feature/flagd/core/pkg/store"
"github.com/open-feature/flagd/core/pkg/telemetry"
flagdService "github.com/open-feature/flagd/flagd/pkg/service"
"github.com/rs/xid"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
@ -20,12 +23,14 @@ import (
)
type FlagEvaluationService struct {
logger *logger.Logger
eval evaluator.IEvaluator
metrics telemetry.IMetricsRecorder
eventingConfiguration IEvents
flagEvalTracer trace.Tracer
contextValues map[string]any
logger *logger.Logger
eval evaluator.IEvaluator
metrics telemetry.IMetricsRecorder
eventingConfiguration IEvents
flagEvalTracer trace.Tracer
contextValues map[string]any
headerToContextKeyMappings map[string]string
deadline time.Duration
}
// NewFlagEvaluationService creates a FlagEvaluationService with provided parameters
@ -34,14 +39,18 @@ func NewFlagEvaluationService(log *logger.Logger,
eventingCfg IEvents,
metricsRecorder telemetry.IMetricsRecorder,
contextValues map[string]any,
headerToContextKeyMappings map[string]string,
streamDeadline time.Duration,
) *FlagEvaluationService {
svc := &FlagEvaluationService{
logger: log,
eval: eval,
metrics: &telemetry.NoopMetricsRecorder{},
eventingConfiguration: eventingCfg,
flagEvalTracer: otel.Tracer("flagd.evaluation.v1"),
contextValues: contextValues,
logger: log,
eval: eval,
metrics: &telemetry.NoopMetricsRecorder{},
eventingConfiguration: eventingCfg,
flagEvalTracer: otel.Tracer("flagd.evaluation.v1"),
contextValues: contextValues,
headerToContextKeyMappings: headerToContextKeyMappings,
deadline: streamDeadline,
}
if metricsRecorder != nil {
@ -59,15 +68,19 @@ func (s *FlagEvaluationService) ResolveAll(
reqID := xid.New().String()
defer s.logger.ClearFields(reqID)
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveAll", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveAll", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
res := &evalV1.ResolveAllResponse{
Flags: make(map[string]*evalV1.AnyFlag),
}
resolutions, flagSetMetadata, err := s.eval.ResolveAllValues(sCtx, reqID, mergeContexts(req.Msg.GetContext().AsMap(),
s.contextValues))
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
evaluationContext := mergeContexts(req.Msg.GetContext().AsMap(), s.contextValues, req.Header(), s.headerToContextKeyMappings)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
resolutions, flagSetMetadata, err := s.eval.ResolveAllValues(ctx, reqID, evaluationContext)
if err != nil {
s.logger.WarnWithID(reqID, fmt.Sprintf("error resolving all flags: %v", err))
return nil, fmt.Errorf("error resolving flags. Tracking ID: %s", reqID)
@ -76,7 +89,7 @@ func (s *FlagEvaluationService) ResolveAll(
span.SetAttributes(attribute.Int("feature_flag.count", len(resolutions)))
for _, resolved := range resolutions {
// register the impression and reason for each flag evaluated
s.metrics.RecordEvaluation(sCtx, resolved.Error, resolved.Reason, resolved.Variant, resolved.FlagKey)
s.metrics.RecordEvaluation(ctx, resolved.Error, resolved.Reason, resolved.Variant, resolved.FlagKey)
switch v := resolved.Value.(type) {
case bool:
res.Flags[resolved.FlagKey] = &evalV1.AnyFlag{
@ -139,8 +152,21 @@ func (s *FlagEvaluationService) EventStream(
req *connect.Request[evalV1.EventStreamRequest],
stream *connect.ServerStream[evalV1.EventStreamResponse],
) error {
// attach server-side stream deadline to context
s.logger.Debug("starting event stream for request")
if s.deadline != 0 {
streamDeadline := time.Now().Add(s.deadline)
deadlineCtx, cancel := context.WithDeadline(ctx, streamDeadline)
ctx = deadlineCtx
defer cancel()
}
s.logger.Debug("starting event stream for request")
requestNotificationChan := make(chan service.Notification, 1)
s.eventingConfiguration.Subscribe(req, requestNotificationChan)
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
s.eventingConfiguration.Subscribe(ctx, req, &selector, requestNotificationChan)
defer s.eventingConfiguration.Unsubscribe(req)
requestNotificationChan <- service.Notification{
@ -168,6 +194,10 @@ func (s *FlagEvaluationService) EventStream(
s.logger.Error(err.Error())
}
case <-ctx.Done():
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
s.logger.Debug(fmt.Sprintf("server-side deadline of %s exceeded, exiting stream request with grpc error code 4", s.deadline.String()))
return connect.NewError(connect.CodeDeadlineExceeded, fmt.Errorf("%s", "stream closed due to server-side timeout"))
}
return nil
}
}
@ -177,19 +207,25 @@ func (s *FlagEvaluationService) ResolveBoolean(
ctx context.Context,
req *connect.Request[evalV1.ResolveBooleanRequest],
) (*connect.Response[evalV1.ResolveBooleanResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveBoolean", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveBoolean", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&evalV1.ResolveBooleanResponse{})
err := resolve(
sCtx,
ctx,
s.logger,
s.eval.ResolveBooleanValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&booleanResponse{evalV1Resp: res},
s.metrics,
s.contextValues,
s.headerToContextKeyMappings,
)
if err != nil {
span.RecordError(err)
@ -203,19 +239,25 @@ func (s *FlagEvaluationService) ResolveString(
ctx context.Context,
req *connect.Request[evalV1.ResolveStringRequest],
) (*connect.Response[evalV1.ResolveStringResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveString", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveString", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&evalV1.ResolveStringResponse{})
err := resolve(
sCtx,
ctx,
s.logger,
s.eval.ResolveStringValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&stringResponse{evalV1Resp: res},
s.metrics,
s.contextValues,
s.headerToContextKeyMappings,
)
if err != nil {
span.RecordError(err)
@ -229,19 +271,25 @@ func (s *FlagEvaluationService) ResolveInt(
ctx context.Context,
req *connect.Request[evalV1.ResolveIntRequest],
) (*connect.Response[evalV1.ResolveIntResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveInt", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveInt", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&evalV1.ResolveIntResponse{})
err := resolve(
sCtx,
ctx,
s.logger,
s.eval.ResolveIntValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&intResponse{evalV1Resp: res},
s.metrics,
s.contextValues,
s.headerToContextKeyMappings,
)
if err != nil {
span.RecordError(err)
@ -255,19 +303,25 @@ func (s *FlagEvaluationService) ResolveFloat(
ctx context.Context,
req *connect.Request[evalV1.ResolveFloatRequest],
) (*connect.Response[evalV1.ResolveFloatResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveFloat", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveFloat", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&evalV1.ResolveFloatResponse{})
err := resolve(
sCtx,
ctx,
s.logger,
s.eval.ResolveFloatValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&floatResponse{evalV1Resp: res},
s.metrics,
s.contextValues,
s.headerToContextKeyMappings,
)
if err != nil {
span.RecordError(err)
@ -281,19 +335,25 @@ func (s *FlagEvaluationService) ResolveObject(
ctx context.Context,
req *connect.Request[evalV1.ResolveObjectRequest],
) (*connect.Response[evalV1.ResolveObjectResponse], error) {
sCtx, span := s.flagEvalTracer.Start(ctx, "resolveObject", trace.WithSpanKind(trace.SpanKindServer))
ctx, span := s.flagEvalTracer.Start(ctx, "resolveObject", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector)
res := connect.NewResponse(&evalV1.ResolveObjectResponse{})
err := resolve(
sCtx,
ctx,
s.logger,
s.eval.ResolveObjectValue,
req.Header(),
req.Msg.GetFlagKey(),
req.Msg.GetContext(),
&objectResponse{evalV1Resp: res},
s.metrics,
s.contextValues,
s.headerToContextKeyMappings,
)
if err != nil {
span.RecordError(err)

View File

@ -3,6 +3,7 @@ package service
import (
"context"
"errors"
"net/http"
"reflect"
"testing"
@ -103,7 +104,7 @@ func TestConnectServiceV2_ResolveAll(t *testing.T) {
).AnyTimes()
metrics, exp := getMetricReader()
s := NewFlagEvaluationService(logger.NewLogger(nil, false), eval, &eventingConfiguration{}, metrics, nil)
s := NewFlagEvaluationService(logger.NewLogger(nil, false), eval, &eventingConfiguration{}, metrics, nil, nil, 0)
// when
got, err := s.ResolveAll(context.Background(), connect.NewRequest(tt.req))
@ -220,6 +221,8 @@ func TestFlag_EvaluationV2_ResolveBoolean(t *testing.T) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
got, err := s.ResolveBoolean(tt.functionArgs.ctx, connect.NewRequest(tt.functionArgs.req))
if (err != nil) && !errors.Is(err, tt.wantErr) {
@ -276,6 +279,8 @@ func BenchmarkFlag_EvaluationV2_ResolveBoolean(b *testing.B) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -375,6 +380,8 @@ func TestFlag_EvaluationV2_ResolveString(t *testing.T) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
got, err := s.ResolveString(tt.functionArgs.ctx, connect.NewRequest(tt.functionArgs.req))
if (err != nil) && !errors.Is(err, tt.wantErr) {
@ -431,6 +438,8 @@ func BenchmarkFlag_EvaluationV2_ResolveString(b *testing.B) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -529,6 +538,8 @@ func TestFlag_EvaluationV2_ResolveFloat(t *testing.T) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
got, err := s.ResolveFloat(tt.functionArgs.ctx, connect.NewRequest(tt.functionArgs.req))
if (err != nil) && !errors.Is(err, tt.wantErr) {
@ -585,6 +596,8 @@ func BenchmarkFlag_EvaluationV2_ResolveFloat(b *testing.B) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -683,6 +696,8 @@ func TestFlag_EvaluationV2_ResolveInt(t *testing.T) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
got, err := s.ResolveInt(tt.functionArgs.ctx, connect.NewRequest(tt.functionArgs.req))
if (err != nil) && !errors.Is(err, tt.wantErr) {
@ -739,6 +754,8 @@ func BenchmarkFlag_EvaluationV2_ResolveInt(b *testing.B) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -840,6 +857,8 @@ func TestFlag_EvaluationV2_ResolveObject(t *testing.T) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
outParsed, err := structpb.NewStruct(tt.evalFields.result)
@ -904,6 +923,8 @@ func BenchmarkFlag_EvaluationV2_ResolveObject(b *testing.B) {
&eventingConfiguration{},
metrics,
nil,
nil,
0,
)
if name != "eval returns error" {
outParsed, err := structpb.NewStruct(tt.evalFields.result)
@ -979,7 +1000,10 @@ func TestFlag_EvaluationV2_ErrorCodes(t *testing.T) {
func Test_mergeContexts(t *testing.T) {
type args struct {
clientContext, configContext map[string]any
headers http.Header
headerToContextKeyMappings map[string]string
clientContext map[string]any
configContext map[string]any
}
tests := []struct {
@ -988,19 +1012,54 @@ func Test_mergeContexts(t *testing.T) {
want map[string]any
}{
{
name: "merge contexts",
name: "merge contexts with no headers, with no header-context mappings",
args: args{
clientContext: map[string]any{"k1": "v1", "k2": "v2"},
configContext: map[string]any{"k2": "v22", "k3": "v3"},
clientContext: map[string]any{"k1": "v1", "k2": "v2"},
configContext: map[string]any{"k2": "v22", "k3": "v3"},
headers: http.Header{},
headerToContextKeyMappings: map[string]string{},
},
// static context should "win"
want: map[string]any{"k1": "v1", "k2": "v22", "k3": "v3"},
},
{
name: "merge contexts with headers, with no header-context mappings",
args: args{
clientContext: map[string]any{"k1": "v1", "k2": "v2"},
configContext: map[string]any{"k2": "v22", "k3": "v3"},
headers: http.Header{"X-key": []string{"value"}, "X-token": []string{"token"}},
headerToContextKeyMappings: map[string]string{},
},
// static context should "win"
want: map[string]any{"k1": "v1", "k2": "v22", "k3": "v3"},
},
{
name: "merge contexts with no headers, with header-context mappings",
args: args{
clientContext: map[string]any{"k1": "v1", "k2": "v2"},
configContext: map[string]any{"k2": "v22", "k3": "v3"},
headers: http.Header{},
headerToContextKeyMappings: map[string]string{"X-key": "k2"},
},
// static context should "win"
want: map[string]any{"k1": "v1", "k2": "v22", "k3": "v3"},
},
{
name: "merge contexts with headers, with header-context mappings",
args: args{
clientContext: map[string]any{"k1": "v1", "k2": "v2"},
configContext: map[string]any{"k2": "v22", "k3": "v3"},
headers: http.Header{"X-key": []string{"value"}, "X-token": []string{"token"}},
headerToContextKeyMappings: map[string]string{"X-key": "k2"},
},
// header context should "win"
want: map[string]any{"k1": "v1", "k2": "value", "k3": "v3"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := mergeContexts(tt.args.clientContext, tt.args.configContext)
got := mergeContexts(tt.args.clientContext, tt.args.configContext, tt.args.headers, tt.args.headerToContextKeyMappings)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("\ngot: %+v\nwant: %+v", got, tt.want)

View File

@ -1,6 +1,7 @@
package ofrep
import (
"context"
"encoding/json"
"fmt"
"net/http"
@ -10,7 +11,12 @@ import (
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/service/ofrep"
"github.com/open-feature/flagd/core/pkg/store"
"github.com/open-feature/flagd/flagd/pkg/service"
"github.com/rs/xid"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
const (
@ -20,22 +26,26 @@ const (
)
type handler struct {
Logger *logger.Logger
evaluator evaluator.IEvaluator
contextValues map[string]any
Logger *logger.Logger
evaluator evaluator.IEvaluator
contextValues map[string]any
headerToContextKeyMappings map[string]string
tracer trace.Tracer
}
func NewOfrepHandler(logger *logger.Logger, evaluator evaluator.IEvaluator, contextValues map[string]any) http.Handler {
func NewOfrepHandler(logger *logger.Logger, evaluator evaluator.IEvaluator, contextValues map[string]any, headerToContextKeyMappings map[string]string) http.Handler {
h := handler{
Logger: logger,
evaluator: evaluator,
contextValues: contextValues,
Logger: logger,
evaluator: evaluator,
contextValues: contextValues,
headerToContextKeyMappings: headerToContextKeyMappings,
tracer: otel.Tracer("flagd.ofrep.v1"),
}
router := mux.NewRouter()
router.HandleFunc(singleEvaluation, h.HandleFlagEvaluation).Methods("POST")
router.HandleFunc(bulkEvaluation, h.HandleBulkEvaluation).Methods("POST")
return router
return otelhttp.NewHandler(router, "flagd.ofrep")
}
func (h *handler) HandleFlagEvaluation(w http.ResponseWriter, r *http.Request) {
@ -57,9 +67,12 @@ func (h *handler) HandleFlagEvaluation(w http.ResponseWriter, r *http.Request) {
h.writeJSONToResponse(http.StatusBadRequest, ofrep.ContextErrorResponseFrom(flagKey), w)
return
}
evaluationContext := flagdContext(h.Logger, requestID, request, h.contextValues, r.Header, h.headerToContextKeyMappings)
selectorExpression := r.Header.Get(service.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx := context.WithValue(r.Context(), store.SelectorContextKey{}, selector)
context := flagdContext(h.Logger, requestID, request, h.contextValues)
evaluation := h.evaluator.ResolveAsAnyValue(r.Context(), requestID, flagKey, context)
evaluation := h.evaluator.ResolveAsAnyValue(ctx, requestID, flagKey, evaluationContext)
if evaluation.Error != nil {
status, evaluationError := ofrep.EvaluationErrorResponseFrom(evaluation)
h.writeJSONToResponse(status, evaluationError, w)
@ -78,8 +91,12 @@ func (h *handler) HandleBulkEvaluation(w http.ResponseWriter, r *http.Request) {
return
}
context := flagdContext(h.Logger, requestID, request, h.contextValues)
evaluations, metadata, err := h.evaluator.ResolveAllValues(r.Context(), requestID, context)
evaluationContext := flagdContext(h.Logger, requestID, request, h.contextValues, r.Header, h.headerToContextKeyMappings)
selectorExpression := r.Header.Get(service.FLAGD_SELECTOR_HEADER)
selector := store.NewSelector(selectorExpression)
ctx := context.WithValue(r.Context(), store.SelectorContextKey{}, selector)
evaluations, metadata, err := h.evaluator.ResolveAllValues(ctx, requestID, evaluationContext)
if err != nil {
h.Logger.WarnWithID(requestID, fmt.Sprintf("error from resolver: %v", err))
@ -119,8 +136,10 @@ func extractOfrepRequest(req *http.Request) (ofrep.Request, error) {
return request, nil
}
// flagdContext returns combined context values from headers, static context (from cli) and request context.
// highest priority > header-context-from-cli > static-context-from-cli > request-context > lowest priority
func flagdContext(
log *logger.Logger, requestID string, request ofrep.Request, staticContextValues map[string]any,
log *logger.Logger, requestID string, request ofrep.Request, staticContextValues map[string]any, headers http.Header, headerToContextKeyMappings map[string]string,
) map[string]any {
context := make(map[string]any)
if res, ok := request.Context.(map[string]any); ok {
@ -135,5 +154,11 @@ func flagdContext(
context[k] = v
}
for header, contextKey := range headerToContextKeyMappings {
if values, ok := headers[header]; ok {
context[contextKey] = values[0]
}
}
return context
}

View File

@ -30,13 +30,13 @@ type Service struct {
}
func NewOfrepService(
evaluator evaluator.IEvaluator, origins []string, cfg SvcConfiguration, contextValues map[string]any,
evaluator evaluator.IEvaluator, origins []string, cfg SvcConfiguration, contextValues map[string]any, headerToContextKeyMappings map[string]string,
) (*Service, error) {
corsMW := cors.New(cors.Options{
AllowedOrigins: origins,
AllowedMethods: []string{http.MethodPost},
})
h := corsMW.Handler(NewOfrepHandler(cfg.Logger, evaluator, contextValues))
h := corsMW.Handler(NewOfrepHandler(cfg.Logger, evaluator, contextValues, headerToContextKeyMappings))
server := http.Server{
Addr: fmt.Sprintf(":%d", cfg.Port),

View File

@ -28,7 +28,7 @@ func Test_OfrepServiceStartStop(t *testing.T) {
Port: uint16(port),
}
service, err := NewOfrepService(eval, []string{"*"}, cfg, nil)
service, err := NewOfrepService(eval, []string{"*"}, cfg, nil, nil)
if err != nil {
t.Fatalf("error creating the ofrep service: %v", err)
}

View File

@ -2,71 +2,118 @@ package sync
import (
"context"
"encoding/json"
"errors"
"fmt"
"maps"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"buf.build/gen/go/open-feature/flagd/grpc/go/flagd/sync/v1/syncv1grpc"
syncv1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/flagd/sync/v1"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/store"
"google.golang.org/protobuf/types/known/structpb"
)
// syncHandler implements the sync contract
type syncHandler struct {
mux *Multiplexer
log *logger.Logger
contextValues map[string]any
//mux *Multiplexer
store *store.Store
log *logger.Logger
contextValues map[string]any
deadline time.Duration
disableSyncMetadata bool
}
func (s syncHandler) SyncFlags(req *syncv1.SyncFlagsRequest, server syncv1grpc.FlagSyncService_SyncFlagsServer) error {
muxPayload := make(chan payload, 1)
selector := req.GetSelector()
watcher := make(chan store.FlagQueryResult, 1)
selectorExpression := req.GetSelector()
selector := store.NewSelector(selectorExpression)
ctx := server.Context()
err := s.mux.Register(ctx, selector, muxPayload)
syncContextMap := make(map[string]any)
maps.Copy(syncContextMap, s.contextValues)
syncContext, err := structpb.NewStruct(syncContextMap)
if err != nil {
return err
return status.Error(codes.DataLoss, "error constructing sync context")
}
// attach server-side stream deadline to context
if s.deadline != 0 {
streamDeadline := time.Now().Add(s.deadline)
deadlineCtx, cancel := context.WithDeadline(ctx, streamDeadline)
ctx = deadlineCtx
defer cancel()
}
s.store.Watch(ctx, &selector, watcher)
for {
select {
case payload := <-muxPayload:
err := server.Send(&syncv1.SyncFlagsResponse{FlagConfiguration: payload.flags})
case payload := <-watcher:
if err != nil {
s.log.Error(fmt.Sprintf("error from struct creation: %v", err))
return fmt.Errorf("error constructing metadata response")
}
flags, err := json.Marshal(payload.Flags)
if err != nil {
s.log.Error(fmt.Sprintf("error retrieving flags from store: %v", err))
return status.Error(codes.DataLoss, "error marshalling flags")
}
err = server.Send(&syncv1.SyncFlagsResponse{FlagConfiguration: string(flags), SyncContext: syncContext})
if err != nil {
s.log.Debug(fmt.Sprintf("error sending stream response: %v", err))
return fmt.Errorf("error sending stream response: %w", err)
}
case <-ctx.Done():
s.mux.Unregister(ctx, selector)
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
s.log.Debug(fmt.Sprintf("server-side deadline of %s exceeded, exiting stream request with grpc error code 4", s.deadline.String()))
return status.Error(codes.DeadlineExceeded, "stream closed due to server-side timeout")
}
s.log.Debug("context complete and exiting stream request")
return nil
}
}
}
func (s syncHandler) FetchAllFlags(_ context.Context, req *syncv1.FetchAllFlagsRequest) (
func (s syncHandler) FetchAllFlags(ctx context.Context, req *syncv1.FetchAllFlagsRequest) (
*syncv1.FetchAllFlagsResponse, error,
) {
flags, err := s.mux.GetAllFlags(req.GetSelector())
selectorExpression := req.GetSelector()
selector := store.NewSelector(selectorExpression)
flags, _, err := s.store.GetAll(ctx, &selector)
if err != nil {
s.log.Error(fmt.Sprintf("error retrieving flags from store: %v", err))
return nil, status.Error(codes.Internal, "error retrieving flags from store")
}
flagsString, err := json.Marshal(flags)
if err != nil {
return nil, err
}
return &syncv1.FetchAllFlagsResponse{
FlagConfiguration: flags,
FlagConfiguration: string(flagsString),
}, nil
}
// Deprecated - GetMetadata is deprecated and will be removed in a future release.
// Use the sync_context field in syncv1.SyncFlagsResponse, providing same info.
func (s syncHandler) GetMetadata(_ context.Context, _ *syncv1.GetMetadataRequest) (
*syncv1.GetMetadataResponse, error,
) {
if s.disableSyncMetadata {
return nil, status.Error(codes.Unimplemented, "metadata endpoint disabled")
}
metadataSrc := make(map[string]any)
for k, v := range s.contextValues {
metadataSrc[k] = v
}
if sources := s.mux.SourcesAsMetadata(); sources != "" {
metadataSrc["sources"] = sources
}
metadata, err := structpb.NewStruct(metadataSrc)
if err != nil {

View File

@ -0,0 +1,130 @@
package sync
import (
"context"
"sync"
"testing"
"time"
"buf.build/gen/go/open-feature/flagd/grpc/go/flagd/sync/v1/syncv1grpc"
syncv1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/flagd/sync/v1"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSyncHandler_SyncFlags(t *testing.T) {
tests := []struct {
name string
sources []string
contextValues map[string]any
wantMetadata map[string]any
}{
{
name: "with sources and context",
contextValues: map[string]any{
"env": "prod",
"region": "us-west",
},
wantMetadata: map[string]any{
"env": "prod",
"region": "us-west",
},
},
{
name: "with empty sources",
contextValues: map[string]any{
"env": "dev",
},
wantMetadata: map[string]any{
"env": "dev",
},
},
{
name: "with empty context",
contextValues: map[string]any{},
wantMetadata: map[string]any{},
},
}
for _, disableSyncMetadata := range []bool{true, false} {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Shared handler for testing both GetMetadata & SyncFlags methods
flagStore, err := store.NewStore(logger.NewLogger(nil, false), tt.sources)
require.NoError(t, err)
handler := syncHandler{
store: flagStore,
contextValues: tt.contextValues,
log: logger.NewLogger(nil, false),
disableSyncMetadata: disableSyncMetadata,
}
// Test getting metadata from `GetMetadata` (deprecated)
// remove when `GetMetadata` is full removed and deprecated
metaResp, err := handler.GetMetadata(context.Background(), &syncv1.GetMetadataRequest{})
if !disableSyncMetadata {
require.NoError(t, err)
respMetadata := metaResp.GetMetadata().AsMap()
assert.Equal(t, tt.wantMetadata, respMetadata)
} else {
assert.NotNil(t, err)
}
// Test metadata from sync_context
stream := &mockSyncFlagsServer{
ctx: context.Background(),
mu: sync.Mutex{},
respReady: make(chan struct{}, 1),
}
go func() {
err := handler.SyncFlags(&syncv1.SyncFlagsRequest{}, stream)
assert.NoError(t, err)
}()
select {
case <-stream.respReady:
syncResp := stream.GetLastResponse()
assert.NotNil(t, syncResp)
syncMetadata := syncResp.GetSyncContext().AsMap()
assert.Equal(t, tt.wantMetadata, syncMetadata)
case <-time.After(time.Second):
t.Fatal("timeout waiting for response")
}
})
}
}
}
// Mock server for testing
type mockSyncFlagsServer struct {
syncv1grpc.FlagSyncService_SyncFlagsServer
ctx context.Context
mu sync.Mutex
lastResp *syncv1.SyncFlagsResponse
respReady chan struct{}
}
func (m *mockSyncFlagsServer) Context() context.Context {
return m.ctx
}
func (m *mockSyncFlagsServer) Send(resp *syncv1.SyncFlagsResponse) error {
m.mu.Lock()
defer m.mu.Unlock()
m.lastResp = resp
select {
case m.respReady <- struct{}{}:
default:
}
return nil
}
func (m *mockSyncFlagsServer) GetLastResponse() *syncv1.SyncFlagsResponse {
m.mu.Lock()
defer m.mu.Unlock()
return m.lastResp
}

View File

@ -1,213 +0,0 @@
package sync
import (
"context"
"encoding/json"
"fmt"
"slices"
"strings"
"sync"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/store"
)
//nolint:errchkjson
var emptyConfigBytes, _ = json.Marshal(map[string]map[string]string{
"flags": {},
})
// Multiplexer abstract subscription handling and storage processing.
// Flag configurations will be lazy loaded using reFill logic upon the calls to publish.
type Multiplexer struct {
store *store.State
sources []string
subs map[interface{}]subscription // subscriptions on all sources
selectorSubs map[string]map[interface{}]subscription // source specific subscriptions
allFlags string // pre-calculated all flags in store as a string
selectorFlags map[string]string // pre-calculated selector scoped flags in store as strings
mu sync.RWMutex
}
type subscription struct {
id interface{}
channel chan payload
}
type payload struct {
flags string
}
// NewMux creates a new sync multiplexer
func NewMux(store *store.State, sources []string) (*Multiplexer, error) {
m := &Multiplexer{
store: store,
sources: sources,
subs: map[interface{}]subscription{},
selectorSubs: map[string]map[interface{}]subscription{},
selectorFlags: map[string]string{},
}
return m, m.reFill()
}
// Register a subscription
func (r *Multiplexer) Register(id interface{}, source string, con chan payload) error {
r.mu.Lock()
defer r.mu.Unlock()
if source != "" && !slices.Contains(r.sources, source) {
return fmt.Errorf("no flag watcher setup for source %s", source)
}
var initSync string
if source == "" {
// subscribe for flags from all source
r.subs[id] = subscription{
id: id,
channel: con,
}
initSync = r.allFlags
} else {
// subscribe for specific source
s, ok := r.selectorSubs[source]
if ok {
s[id] = subscription{
id: id,
channel: con,
}
} else {
r.selectorSubs[source] = map[interface{}]subscription{
id: {
id: id,
channel: con,
},
}
}
initSync = r.selectorFlags[source]
}
// Initial sync
con <- payload{flags: initSync}
return nil
}
// Publish sync updates to subscriptions
func (r *Multiplexer) Publish() error {
r.mu.Lock()
defer r.mu.Unlock()
// perform a refill prior to publishing
err := r.reFill()
if err != nil {
return err
}
// push to all source subs
for _, sub := range r.subs {
sub.channel <- payload{r.allFlags}
}
// push to selector subs
for source, flags := range r.selectorFlags {
for _, s := range r.selectorSubs[source] {
s.channel <- payload{flags}
}
}
return nil
}
// Unregister a subscription
func (r *Multiplexer) Unregister(id interface{}, selector string) {
r.mu.Lock()
defer r.mu.Unlock()
var from map[interface{}]subscription
if selector == "" {
from = r.subs
} else {
from = r.selectorSubs[selector]
}
delete(from, id)
}
// GetAllFlags per specific source
func (r *Multiplexer) GetAllFlags(source string) (string, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if source == "" {
return r.allFlags, nil
}
if !slices.Contains(r.sources, source) {
return "", fmt.Errorf("no flag watcher setup for source %s", source)
}
return r.selectorFlags[source], nil
}
// SourcesAsMetadata returns all known sources, comma separated to be used as service metadata
func (r *Multiplexer) SourcesAsMetadata() string {
r.mu.RLock()
defer r.mu.RUnlock()
return strings.Join(r.sources, ",")
}
// reFill local configuration values
func (r *Multiplexer) reFill() error {
clear(r.selectorFlags)
// start all sources with empty config
for _, source := range r.sources {
r.selectorFlags[source] = string(emptyConfigBytes)
}
all, metadata, err := r.store.GetAll(context.Background())
if err != nil {
return fmt.Errorf("error retrieving flags from the store: %w", err)
}
bytes, err := json.Marshal(map[string]interface{}{"flags": all, "metadata": metadata})
if err != nil {
return fmt.Errorf("error marshalling: %w", err)
}
r.allFlags = string(bytes)
collector := map[string]map[string]model.Flag{}
for key, flag := range all {
c, ok := collector[flag.Source]
if ok {
c[key] = flag
} else {
collector[flag.Source] = map[string]model.Flag{
key: flag,
}
}
}
// for all flags, sort them into their correct selector
for source, flags := range collector {
// store the corresponding metadata
metadata := r.store.GetMetadataForSource(source)
bytes, err := json.Marshal(map[string]interface{}{"flags": flags, "metadata": metadata})
if err != nil {
return fmt.Errorf("unable to marshal flags: %w", err)
}
r.selectorFlags[source] = string(bytes)
}
return nil
}

View File

@ -1,261 +0,0 @@
package sync
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const emptyConfigString = "{\"flags\":{}}"
func TestRegistration(t *testing.T) {
// given
mux, err := NewMux(getSimpleFlagStore())
if err != nil {
t.Fatal("error during flag extraction")
return
}
tests := []struct {
testName string
id interface{}
source string
flagStringValidator func(flagString string, testSource string, testName string)
connection chan payload
expectError bool
}{
{
testName: "subscribe to all flags",
id: context.Background(),
connection: make(chan payload, 1),
},
{
testName: "subscribe to source A",
id: context.Background(),
source: "A",
flagStringValidator: func(flagString string, testSource string, testName string) {
assert.Contains(t, flagString, fmt.Sprintf("\"source\":\"%s\"", testSource))
},
connection: make(chan payload, 1),
},
{
testName: "subscribe to source B",
id: context.Background(),
source: "B",
flagStringValidator: func(flagString string, testSource string, testName string) {
assert.Contains(t, flagString, fmt.Sprintf("\"source\":\"%s\"", testSource))
},
connection: make(chan payload, 1),
},
{
testName: "subscribe to empty",
id: context.Background(),
source: "C",
connection: make(chan payload, 1),
flagStringValidator: func(flagString string, testSource string, testName string) {
assert.Equal(t, flagString, emptyConfigString)
},
expectError: false,
},
{
testName: "subscribe to non-existing",
id: context.Background(),
source: "D",
connection: make(chan payload, 1),
expectError: true,
},
}
// validate registration
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
// when
err := mux.Register(test.id, test.source, test.connection)
// then
if !test.expectError && err != nil {
t.Fatal("expected no errors, but got error")
}
if test.expectError && err != nil {
// pass
return
}
// validate subscription
var initSync payload
select {
case <-time.After(2 * time.Second):
t.Fatal("data sync did not complete for initial sync within an acceptable timeframe")
case initSync = <-test.connection:
break
}
if test.flagStringValidator != nil {
test.flagStringValidator(initSync.flags, test.source, test.testName)
}
})
}
}
func TestUpdateAndRemoval(t *testing.T) {
// given
mux, err := NewMux(getSimpleFlagStore())
if err != nil {
t.Fatal("error during flag extraction")
return
}
identifier := context.Background()
channel := make(chan payload, 1)
err = mux.Register(identifier, "", channel)
if err != nil {
t.Fatal("error during subscription registration")
return
}
select {
case <-time.After(2 * time.Second):
t.Fatal("data sync did not complete for initial sync within an acceptable timeframe")
case <-channel:
break
}
// when - updates are triggered
err = mux.Publish()
if err != nil {
t.Fatal("failure to trigger update request on multiplexer")
return
}
// then
select {
case <-time.After(2 * time.Second):
t.Fatal("data sync did not complete for initial sync within an acceptable timeframe")
case <-channel:
break
}
// when - subscription removed & update triggered
mux.Unregister(identifier, "")
err = mux.Publish()
if err != nil {
t.Fatal("failure to trigger update request on multiplexer")
return
}
// then
select {
case <-time.After(2 * time.Second):
break
case <-channel:
t.Fatal("expected no sync but got an update as removal was not performed")
}
}
func TestGetAllFlags(t *testing.T) {
// given
mux, err := NewMux(getSimpleFlagStore())
if err != nil {
t.Fatal("error during flag extraction")
return
}
// when - get all with open scope
flagConfig, err := mux.GetAllFlags("")
if err != nil {
t.Fatal("error when retrieving all flags")
return
}
if len(flagConfig) == 0 {
t.Fatal("expected no empty flags")
return
}
// when - get all with a scope
flagConfig, err = mux.GetAllFlags("A")
if err != nil {
t.Fatal("error when retrieving all flags")
return
}
if len(flagConfig) == 0 || !strings.Contains(flagConfig, fmt.Sprintf("\"source\":\"%s\"", "A")) {
t.Fatal("expected flags to be scoped")
return
}
// when - get all for a flagless-scope
flagConfig, err = mux.GetAllFlags("C")
if err != nil {
t.Fatal("error when retrieving all flags")
return
}
assert.Equal(t, flagConfig, emptyConfigString)
}
func TestGetAllFlagsMetadata(t *testing.T) {
// given
mux, err := NewMux(getSimpleFlagStore())
if err != nil {
t.Fatal("error during flag extraction")
return
}
// when - get all with open scope
flagConfig, err := mux.GetAllFlags("")
if err != nil {
t.Fatal("error when retrieving all flags")
return
}
if len(flagConfig) == 0 {
t.Fatal("expected no empty flags")
return
}
if !strings.Contains(flagConfig, "\"keyA\":\"valueA\"") {
t.Fatal("expected unique metadata key for A to be present")
return
}
if !strings.Contains(flagConfig, "\"keyB\":\"valueB\"") {
t.Fatal("expected unique metadata key for B to be present")
return
}
// duplicated keys are removed
if strings.Contains(flagConfig, "\"keyDuped\":\"value\"") {
t.Fatal("expected duplicated metadata key NOT to be present")
return
}
// when - get all with a scope
flagConfig, err = mux.GetAllFlags("A")
if err != nil {
t.Fatal("error when retrieving all flags")
return
}
if len(flagConfig) == 0 {
t.Fatal("expected no empty flags")
return
}
if !strings.Contains(flagConfig, "\"keyA\":\"valueA\"") {
t.Fatal("expected unique metadata key to be present")
return
}
if !strings.Contains(flagConfig, "\"keyDuped\":\"value\"") {
t.Fatal("expected duplicated metadata key to be present")
return
}
}

View File

@ -21,24 +21,25 @@ type ISyncService interface {
Start(context.Context) error
// Emit updates for sync listeners
Emit(isResync bool, source string)
Emit(source string)
}
type SvcConfigurations struct {
Logger *logger.Logger
Port uint16
Sources []string
Store *store.State
ContextValues map[string]any
CertPath string
KeyPath string
SocketPath string
Logger *logger.Logger
Port uint16
Sources []string
Store *store.Store
ContextValues map[string]any
CertPath string
KeyPath string
SocketPath string
StreamDeadline time.Duration
DisableSyncMetadata bool
}
type Service struct {
listener net.Listener
logger *logger.Logger
mux *Multiplexer
server *grpc.Server
startupTracker syncTracker
@ -64,7 +65,6 @@ func loadTLSCredentials(certPath string, keyPath string) (credentials.TransportC
func NewSyncService(cfg SvcConfigurations) (*Service, error) {
var err error
l := cfg.Logger
mux, err := NewMux(cfg.Store, cfg.Sources)
if err != nil {
return nil, fmt.Errorf("error initializing multiplexer: %w", err)
}
@ -81,9 +81,11 @@ func NewSyncService(cfg SvcConfigurations) (*Service, error) {
}
syncv1grpc.RegisterFlagSyncServiceServer(server, &syncHandler{
mux: mux,
log: l,
contextValues: cfg.ContextValues,
store: cfg.Store,
log: l,
contextValues: cfg.ContextValues,
deadline: cfg.StreamDeadline,
disableSyncMetadata: cfg.DisableSyncMetadata,
})
var lis net.Listener
@ -101,7 +103,6 @@ func NewSyncService(cfg SvcConfigurations) (*Service, error) {
return &Service{
listener: lis,
logger: l,
mux: mux,
server: server,
startupTracker: syncTracker{
sources: slices.Clone(cfg.Sources),
@ -147,16 +148,8 @@ func (s *Service) Start(ctx context.Context) error {
return nil
}
func (s *Service) Emit(isResync bool, source string) {
func (s *Service) Emit(source string) {
s.startupTracker.trackAndRemove(source)
if !isResync {
err := s.mux.Publish()
if err != nil {
s.logger.Warn(fmt.Sprintf("error while publishing sync streams: %v", err))
return
}
}
}
func (s *Service) shutdown() {

View File

@ -7,6 +7,12 @@ import (
"testing"
"time"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/store"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"buf.build/gen/go/open-feature/flagd/grpc/go/flagd/sync/v1/syncv1grpc"
v1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/flagd/sync/v1"
"github.com/open-feature/flagd/core/pkg/logger"
@ -22,80 +28,169 @@ func TestSyncServiceEndToEnd(t *testing.T) {
clientCertPath string
socketPath string
tls bool
wantErr bool
wantStartErr bool
}{
{title: "with TLS Connection", certPath: "./test-cert/server-cert.pem", keyPath: "./test-cert/server-key.pem", clientCertPath: "./test-cert/ca-cert.pem", socketPath: "", tls: true, wantErr: false},
{title: "witout TLS Connection", certPath: "", keyPath: "", clientCertPath: "", socketPath: "", tls: false, wantErr: false},
{title: "with invalid TLS certificate path", certPath: "./lol/not/a/cert", keyPath: "./test-cert/server-key.pem", clientCertPath: "./test-cert/ca-cert.pem", socketPath: "", tls: true, wantErr: true},
{title: "with unix socket connection", certPath: "", keyPath: "", clientCertPath: "", socketPath: "/tmp/flagd", tls: false, wantErr: false},
{title: "with TLS Connection", certPath: "./test-cert/server-cert.pem", keyPath: "./test-cert/server-key.pem", clientCertPath: "./test-cert/ca-cert.pem", socketPath: "", tls: true, wantStartErr: false},
{title: "without TLS Connection", certPath: "", keyPath: "", clientCertPath: "", socketPath: "", tls: false, wantStartErr: false},
{title: "with invalid TLS certificate path", certPath: "./lol/not/a/cert", keyPath: "./test-cert/server-key.pem", clientCertPath: "./test-cert/ca-cert.pem", socketPath: "", tls: true, wantStartErr: true},
{title: "with unix socket connection", certPath: "", keyPath: "", clientCertPath: "", socketPath: "/tmp/flagd", tls: false, wantStartErr: false},
}
for _, disableSyncMetadata := range []bool{true, false} {
for _, tc := range testCases {
t.Run(fmt.Sprintf("Testing Sync Service %s", tc.title), func(t *testing.T) {
// given
port := 18016
flagStore, sources := getSimpleFlagStore(t)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
_, doneChan, err := createAndStartSyncService(
port,
sources,
flagStore,
tc.certPath,
tc.keyPath,
tc.socketPath,
ctx,
0,
disableSyncMetadata,
)
if tc.wantStartErr {
if err == nil {
t.Fatal("expected error creating the service!")
}
return
} else if err != nil {
t.Fatal("unexpected error creating the service: %w", err)
return
}
// when - derive a client for sync service
serviceClient := getSyncClient(t, tc.clientCertPath, tc.socketPath, tc.tls, port, ctx)
// then
// sync flags request
flags, err := serviceClient.SyncFlags(ctx, &v1.SyncFlagsRequest{})
if err != nil {
t.Fatal(fmt.Printf("error from sync request: %v", err))
return
}
syncRsp, err := flags.Recv()
if err != nil {
t.Fatal(fmt.Printf("stream error: %v", err))
return
}
if len(syncRsp.GetFlagConfiguration()) == 0 {
t.Error("expected non empty sync response, but got empty")
}
// checks sync context actually set
syncContext := syncRsp.GetSyncContext()
if syncContext == nil {
t.Fatal("expected sync_context in SyncFlagsResponse, but got nil")
}
// validate emits
dataReceived := make(chan interface{})
go func() {
_, err := flags.Recv()
if err != nil {
return
}
dataReceived <- nil
}()
// make a change
flagStore.Update(testSource1, testSource1Flags, model.Metadata{
"keyDuped": "value",
"keyA": "valueA",
})
select {
case <-dataReceived:
break
case <-time.After(1 * time.Second):
t.Fatal("expected data but timeout waiting for sync")
}
// fetch all flags
allRsp, err := serviceClient.FetchAllFlags(ctx, &v1.FetchAllFlagsRequest{})
if err != nil {
t.Fatal(fmt.Printf("fetch all error: %v", err))
return
}
if allRsp.GetFlagConfiguration() != syncRsp.GetFlagConfiguration() {
t.Errorf("expected both sync and fetch all responses to be same, but got %s from sync & %s from fetch all",
syncRsp.GetFlagConfiguration(), allRsp.GetFlagConfiguration())
}
// metadata request
metadataRsp, err := serviceClient.GetMetadata(ctx, &v1.GetMetadataRequest{})
if disableSyncMetadata {
if err == nil {
t.Fatal(fmt.Printf("getMetadata disabled, error should not be nil"))
return
}
} else {
asMap := metadataRsp.GetMetadata().AsMap()
assert.NotNil(t, asMap, "expected metadata to be non-nil")
}
// validate shutdown from context cancellation
go func() {
cancelFunc()
}()
select {
case <-doneChan:
// exit successful
return
case <-time.After(2 * time.Second):
t.Fatal("service did not exist within sufficient timeframe")
}
})
}
}
}
func TestSyncServiceDeadlineEndToEnd(t *testing.T) {
testCases := []struct {
title string
deadline time.Duration
}{
{title: "without deadline", deadline: 0},
{title: "with deadline", deadline: 2 * time.Second},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("Testing Sync Service %s", tc.title), func(t *testing.T) {
// given
port := 18016
store, sources := getSimpleFlagStore()
service, err := NewSyncService(SvcConfigurations{
Logger: logger.NewLogger(nil, false),
Port: uint16(port),
Sources: sources,
Store: store,
CertPath: tc.certPath,
KeyPath: tc.keyPath,
SocketPath: tc.socketPath,
})
if tc.wantErr {
if err == nil {
t.Fatal("expected error creating the service!")
}
return
} else if err != nil {
t.Fatal("unexpected error creating the service: %w", err)
return
}
flagStore, sources := getSimpleFlagStore(t)
certPath := "./test-cert/server-cert.pem"
keyPath := "./test-cert/server-key.pem"
socketPath := ""
ctx, cancelFunc := context.WithCancel(context.Background())
doneChan := make(chan interface{})
defer cancelFunc()
go func() {
// error ignored, tests will fail if start is not successful
_ = service.Start(ctx)
close(doneChan)
}()
// trigger manual emits matching sources, so that service can start
for _, source := range sources {
service.Emit(false, source)
_, _, err := createAndStartSyncService(port, sources, flagStore, certPath, keyPath, socketPath, ctx, tc.deadline, false)
if err != nil {
t.Fatal("error creating sync service")
}
// when - derive a client for sync service
var con *grpc.ClientConn
if tc.tls {
tlsCredentials, e := loadTLSClientCredentials(tc.clientCertPath)
if e != nil {
log.Fatal("cannot load TLS credentials: ", e)
}
con, err = grpc.Dial(fmt.Sprintf("0.0.0.0:%d", port), grpc.WithTransportCredentials(tlsCredentials))
} else {
if tc.socketPath != "" {
con, err = grpc.Dial(
fmt.Sprintf("unix://%s", tc.socketPath),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
grpc.WithTimeout(2*time.Second),
)
} else {
con, err = grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", port), grpc.WithTransportCredentials(insecure.NewCredentials()))
}
}
if err != nil {
t.Fatal(fmt.Printf("error creating grpc dial ctx: %v", err))
return
}
serviceClient := syncv1grpc.NewFlagSyncServiceClient(con)
serviceClient := getSyncClient(t, "./test-cert/ca-cert.pem", "", true, port, nil)
// then
@ -106,89 +201,111 @@ func TestSyncServiceEndToEnd(t *testing.T) {
return
}
syncRsp, err := flags.Recv()
if err != nil {
t.Fatal(fmt.Printf("stream error: %v", err))
return
}
dataChan := make(chan any)
errorChan := make(chan error)
if len(syncRsp.GetFlagConfiguration()) == 0 {
t.Error("expected non empty sync response, but got empty")
}
// validate emits
dataReceived := make(chan interface{})
go func() {
_, err := flags.Recv()
if err != nil {
return
for {
data, err := flags.Recv()
dataChan <- data
if err != nil {
errorChan <- err
return
}
}
dataReceived <- nil
}()
// Emit as a resync
service.Emit(true, "A")
select {
case <-dataReceived:
t.Fatal("expected no data as this is a resync")
case <-time.After(1 * time.Second):
break
}
// Emit as a resync
service.Emit(false, "A")
select {
case <-dataReceived:
break
case <-time.After(1 * time.Second):
t.Fatal("expected data but timeout waiting for sync")
}
// fetch all flags
allRsp, err := serviceClient.FetchAllFlags(ctx, &v1.FetchAllFlagsRequest{})
if err != nil {
t.Fatal(fmt.Printf("fetch all error: %v", err))
return
}
if allRsp.GetFlagConfiguration() != syncRsp.GetFlagConfiguration() {
t.Errorf("expected both sync and fetch all responses to be same, but got %s from sync & %s from fetch all",
syncRsp.GetFlagConfiguration(), allRsp.GetFlagConfiguration())
}
// metadata request
metadataRsp, err := serviceClient.GetMetadata(ctx, &v1.GetMetadataRequest{})
if err != nil {
t.Fatal(fmt.Printf("metadata error: %v", err))
return
}
asMap := metadataRsp.GetMetadata().AsMap()
// expect `sources` to be present
if asMap["sources"] == nil {
t.Fatal("expected sources entry in the metadata, but got nil")
}
if asMap["sources"] != "A,B,C" {
t.Fatal("incorrect sources entry in metadata")
}
// validate shutdown from context cancellation
go func() {
cancelFunc()
}()
select {
case <-doneChan:
// exit successful
return
case <-time.After(2 * time.Second):
t.Fatal("service did not exist within sufficient timeframe")
for {
select {
case <-dataChan:
// received data, continuing..
break
case err := <-errorChan:
st, _ := status.FromError(err)
if st.Code() == codes.DeadlineExceeded {
if tc.deadline == 0 {
t.Fatal("ran into deadline exceeded error even though no deadline was configured.")
}
// expected error due to deadline
return
}
t.Fatal("unexpected error: ", err)
case <-time.After(tc.deadline + 1*time.Second):
if tc.deadline == 0 {
return
}
t.Fatal("not expected as the deadline should result in other cases.")
}
}
})
}
}
func createAndStartSyncService(
port int,
sources []string,
store *store.Store,
certPath string,
keyPath string,
socketPath string,
ctx context.Context,
deadline time.Duration,
disableSyncMetadata bool,
) (*Service, chan interface{}, error) {
service, err := NewSyncService(SvcConfigurations{
Logger: logger.NewLogger(nil, false),
Port: uint16(port),
Sources: sources,
Store: store,
CertPath: certPath,
KeyPath: keyPath,
SocketPath: socketPath,
StreamDeadline: deadline,
DisableSyncMetadata: disableSyncMetadata,
})
if err != nil {
return nil, nil, err
}
doneChan := make(chan interface{})
go func() {
// error ignored, tests will fail if start is not successful
_ = service.Start(ctx)
close(doneChan)
}()
// trigger manual emits matching sources, so that service can start
for _, source := range sources {
service.Emit(source)
}
return service, doneChan, err
}
func getSyncClient(t *testing.T, clientCertPath string, socketPath string, tls bool, port int, ctx context.Context) syncv1grpc.FlagSyncServiceClient {
var con *grpc.ClientConn
var err error
if tls {
tlsCredentials, e := loadTLSClientCredentials(clientCertPath)
if e != nil {
log.Fatal("cannot load TLS credentials: ", e)
}
con, err = grpc.Dial(fmt.Sprintf("0.0.0.0:%d", port), grpc.WithTransportCredentials(tlsCredentials))
} else {
if socketPath != "" {
con, err = grpc.Dial(
fmt.Sprintf("unix://%s", socketPath),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
grpc.WithTimeout(2*time.Second),
)
} else {
con, err = grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", port), grpc.WithTransportCredentials(insecure.NewCredentials()))
}
}
if err != nil {
t.Fatal(fmt.Printf("error creating grpc dial ctx: %v", err))
}
serviceClient := syncv1grpc.NewFlagSyncServiceClient(con)
return serviceClient
}

View File

@ -5,46 +5,57 @@ import (
"crypto/x509"
"fmt"
"os"
"testing"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/store"
"google.golang.org/grpc/credentials"
)
// getSimpleFlagStore returns a flag store pre-filled with flags from sources A & B & C, which C empty
func getSimpleFlagStore() (*store.State, []string) {
variants := map[string]any{
"true": true,
"false": false,
}
flagStore := store.NewFlags()
flagStore.Set("flagA", model.Flag{
var testSource1 = "testSource1"
var testSource2 = "testSource2"
var testVariants = map[string]any{
"true": true,
"false": false,
}
var testSource1Flags = map[string]model.Flag{
"flagA": {
State: "ENABLED",
DefaultVariant: "false",
Variants: variants,
Source: "A",
})
flagStore.Set("flagB", model.Flag{
Variants: testVariants,
},
}
var testSource2Flags = map[string]model.Flag{
"flagB": {
State: "ENABLED",
DefaultVariant: "true",
Variants: variants,
Source: "B",
})
Variants: testVariants,
},
}
flagStore.MetadataPerSource["A"] = model.Metadata{
// getSimpleFlagStore is a test util which returns a flag store pre-filled with flags from sources testSource1 and testSource2.
func getSimpleFlagStore(t testing.TB) (*store.Store, []string) {
t.Helper()
sources := []string{testSource1, testSource2}
flagStore, err := store.NewStore(logger.NewLogger(nil, false), sources)
if err != nil {
t.Fatalf("error creating flag store: %v", err)
}
flagStore.Update(testSource1, testSource1Flags, model.Metadata{
"keyDuped": "value",
"keyA": "valueA",
}
})
flagStore.MetadataPerSource["B"] = model.Metadata{
flagStore.Update(testSource2, testSource2Flags, model.Metadata{
"keyDuped": "value",
"keyB": "valueB",
}
})
return flagStore, []string{"A", "B", "C"}
return flagStore, sources
}
func loadTLSClientCredentials(certPath string) (credentials.TransportCredentials, error) {

View File

@ -68,6 +68,7 @@ func (m Middleware) Measure(ctx context.Context, handlerID string, reporter Repo
hid,
reporter.Method(),
code,
reporter.Scheme(),
)
m.cfg.MetricRecorder.InFlightRequestStart(ctx, httpAttrs)
@ -112,6 +113,7 @@ type Reporter interface {
URLPath() string
StatusCode() int
BytesWritten() int64
Scheme() string
}
type stdReporter struct {
@ -127,6 +129,13 @@ func (s *stdReporter) StatusCode() int { return s.w.statusCode }
func (s *stdReporter) BytesWritten() int64 { return int64(s.w.bytesWritten) }
func (s *stdReporter) Scheme() string {
if s.r.TLS != nil {
return "https"
}
return "http"
}
// responseWriterInterceptor is a simple wrapper to intercept set data on a
// ResponseWriter.
type responseWriterInterceptor struct {

View File

@ -190,6 +190,10 @@ func (m *MockReporter) BytesWritten() int64 {
return m.Bytes
}
func (m *MockReporter) Scheme() string {
return "http"
}
func (m *MockReporter) URLCalled() bool { return m.urlCalled }
func (m *MockReporter) MethodCalled() bool { return m.methodCalled }
func (m *MockReporter) StatusCalled() bool { return m.statusCalled }

View File

@ -58,6 +58,11 @@ markdown_extensions:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
slugify: !!python/object/apply:pymdownx.slugs.slugify
kwds:
case: lower
- tables
- attr_list
- md_in_html

View File

@ -1,7 +1,7 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"constraints": {
"go": "1.22"
"go": "1.24"
},
"extends": ["github>open-feature/community-tooling"],
"includePaths": [

@ -1 +1 @@
Subproject commit bb763438abc5b0e97dc26a795bc72b7a9f3e4020
Subproject commit 08b4c52d3b86d686f11e74322dbfee775db91656

Some files were not shown because too many files have changed in this diff Show More