feat: support yaml in blob, file, and http syncs (#1522)

Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com>
This commit is contained in:
Michael Beemer 2025-01-28 12:53:53 -05:00 committed by GitHub
parent 7a0656713a
commit 76d673ae8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 485 additions and 290 deletions

View File

@ -1,25 +1,5 @@
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20240906125204-0a6a901b42e8.1 h1:18ZObecoJfRbNQDeuW0PoBR829Mw8FrPrmWIbbaA5hs=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20240906125204-0a6a901b42e8.1/go.mod h1:WA65xyBj+VxPfJ3a+EqdZtWGeNdwqiaQO1sriHaNL1Y=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20241220192239-696330adaff0.1 h1:IUAEwQk7D6DNKKJD3QawHOsIyxVzvJEvSwaf4IrOCWo=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20241220192239-696330adaff0.1/go.mod h1:tuqaSMGk0JdcgfeGezlkUyPs5EcRvefx3pY12jl0CWc=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20241220192239-696330adaff0.2 h1:DTzLajNo/KOdqIyYQEf1ycToux8PzatOqeLgppUJYO0=
buf.build/gen/go/open-feature/flagd/grpc/go v1.5.1-20241220192239-696330adaff0.2/go.mod h1:CgtzL3a/OZI2tPbGH0TWbMEyPFGPeE3NT9AXvrGZ9Ic=
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 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/grpc/go v1.5.1-20250127221518-be6d1143b690.2/go.mod h1:b9rfG6rbGXZAlLwQwedvZ0kI0nUcR+aLaYF70pj920E=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.35.2-20240906125204-0a6a901b42e8.1 h1:YH4cN90+V8Ma0oesiknkwAfpaXv1os0wf6hHxZbsYXI=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.35.2-20240906125204-0a6a901b42e8.1/go.mod h1:Xr7OXtIywezTZbrenv3UbcK0eSikuoXJ/Au9MlmMADs=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.0-20240906125204-0a6a901b42e8.1 h1:sLBOg6If+/9NAB/PpHdRWruKdhJINz4Hc6gn5mA3yDQ=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.0-20240906125204-0a6a901b42e8.1/go.mod h1:8XwTwJgC9sKuuFvYbpqZzHNjXsSgv9omI1j6+XC3534=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.0-20241220192239-696330adaff0.1 h1:0zxUV0cXmmlD1QVDfpAB48Gz0dPP6NUdrdQAofUVRQ0=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.0-20241220192239-696330adaff0.1/go.mod h1:8XwTwJgC9sKuuFvYbpqZzHNjXsSgv9omI1j6+XC3534=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.1-20241220192239-696330adaff0.1 h1:CYsQrPu6h2qVEKuc7gu3X4wv2ohFr/OWD29J2thKX6o=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.1-20241220192239-696330adaff0.1/go.mod h1:5XEoBVvGcahkyF86hwFtNBAEwhu8Gi7jhg8kSEAEI4Q=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.2-20241220192239-696330adaff0.1 h1:ksMEWnFsZYnIiKG6FWVknPGvgkqnFCXu8J2RyxyJr2c=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.2-20241220192239-696330adaff0.1/go.mod h1:goa0LsW/Zl0hcpskkrxrk02pdPAvlzNllrriC3GjBSk=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.3-20241220192239-696330adaff0.1 h1:k0Ing4Z+RWUwdMsQkrJP4BtzmiFwKMflkyjKY13Qp8Y=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.3-20241220192239-696330adaff0.1/go.mod h1:i4r8Y6TOuQGjptheWsep7c1PZodfGPohMDQLGoxImfs=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.4-20241220192239-696330adaff0.1 h1:GzWS8kvK11LM7OnrTSofL5RJlOOeSC8DrZ58ExnG1TA=
buf.build/gen/go/open-feature/flagd/protocolbuffers/go v1.36.4-20241220192239-696330adaff0.1/go.mod h1:wemFLfCpuNfhrBQ7NwzbtYxbg+IihAYqJcNeS+fLpLI=
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 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.4-20250127221518-be6d1143b690.1/go.mod h1:wemFLfCpuNfhrBQ7NwzbtYxbg+IihAYqJcNeS+fLpLI=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
@ -29,8 +9,6 @@ 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 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 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= 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.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4=
@ -39,8 +17,6 @@ cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nT
cloud.google.com/go/longrunning v0.5.12/go.mod h1:S5hMV8CDJ6r50t2ubVJSKQVv5u0rmik5//KgLO3k4lU= 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 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk=
connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= 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 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY=
@ -117,14 +93,6 @@ 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.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 h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diegoholiveira/jsonlogic/v3 v3.6.0 h1:pSVxMTeNOVwsQsJgUV8ENNvtZSev528FbE0fV0+XsWo=
github.com/diegoholiveira/jsonlogic/v3 v3.6.0/go.mod h1:3nnfWovrlZq2rTpucrJ2KMIS8TMf6IoFneofmeqk/qk=
github.com/diegoholiveira/jsonlogic/v3 v3.6.1 h1:EBHcGqqP7cJi1ygvNs7+APzyNBWzcVhHVwogCxQqj+w=
github.com/diegoholiveira/jsonlogic/v3 v3.6.1/go.mod h1:3nnfWovrlZq2rTpucrJ2KMIS8TMf6IoFneofmeqk/qk=
github.com/diegoholiveira/jsonlogic/v3 v3.7.0 h1:fDGVmOyIS8H4WJhtv0YUOoeoUXo7sZLu+VXVr70BR1U=
github.com/diegoholiveira/jsonlogic/v3 v3.7.0/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
github.com/diegoholiveira/jsonlogic/v3 v3.7.1 h1:N9nEU+sFL793cZc11fsZ/Rlicvlg7hcOFOI6CVjvIhk=
github.com/diegoholiveira/jsonlogic/v3 v3.7.1/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
github.com/diegoholiveira/jsonlogic/v3 v3.7.3 h1:orvGaTW5Qdcowpr7vgfMNuqYNjQYcqtw9W1Bk4QyI38= 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.3/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 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
@ -212,10 +180,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 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 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= 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.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= 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.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
@ -253,12 +217,6 @@ 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/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 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/open-feature/flagd-schemas v0.2.9-0.20240708163558-2aa89b314322 h1:5zbNHqcZAc9jlhSrC0onuVL2RPpvYcDaNvW2wOZBfUY=
github.com/open-feature/flagd-schemas v0.2.9-0.20240708163558-2aa89b314322/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
github.com/open-feature/flagd-schemas v0.2.9-0.20241220191753-b81a56eea3b2 h1:96TDmaXEkKcR5mC3L3s+XcYeD/1bk1J3S1su5sTGFDU=
github.com/open-feature/flagd-schemas v0.2.9-0.20241220191753-b81a56eea3b2/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
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 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.20250127221449-bb763438abc5/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 h1:0r4Z+RnJltuHdRBv79NFgAckhna6/M3Wcec6gzNX5vI=
@ -275,8 +233,6 @@ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/j
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= 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.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
@ -291,7 +247,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@ -323,64 +278,24 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.5
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/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 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 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.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 h1:7F29RDmnlqk6B5d+sUqemt8TBfDqxryYW5gX6L74RFA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0/go.mod h1:ZiGDq7xwDMKmWDrN1XsXAj0iC7hns+2DhxBFSncNHSE=
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 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.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
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 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.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
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 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.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU=
go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU=
go.opentelemetry.io/otel/exporters/prometheus v0.55.0 h1:sSPw658Lk2NWAv74lkD3B/RSDb+xRFx46GjkrL3VUZo=
go.opentelemetry.io/otel/exporters/prometheus v0.55.0/go.mod h1:nC00vyCmQixoeaxF6KNyP42II/RHa9UdruK02qBmHvI=
go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E= 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.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 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.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 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.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU=
go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 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.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 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.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 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.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -399,17 +314,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 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.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= 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-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae h1:COZdc9Ut6wLq7MO9GIYxfZl4n4ScmgqQLoHocKXrxco= golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae h1:COZdc9Ut6wLq7MO9GIYxfZl4n4ScmgqQLoHocKXrxco=
@ -440,15 +347,9 @@ 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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 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.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 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.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= 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.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -475,8 +376,6 @@ 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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.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.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 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.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -485,8 +384,6 @@ 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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 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.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= 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.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -512,8 +409,8 @@ 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -531,16 +428,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 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 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM=
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc=
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g=
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
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 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-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= 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-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -548,14 +437,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI=
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= 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.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@ -567,16 +448,6 @@ 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.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.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.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= 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.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -1,14 +1,16 @@
package blob package blob
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"path/filepath"
"time" "time"
"github.com/open-feature/flagd/core/pkg/logger" "github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/sync" "github.com/open-feature/flagd/core/pkg/sync"
"github.com/open-feature/flagd/core/pkg/utils"
"gocloud.dev/blob" "gocloud.dev/blob"
_ "gocloud.dev/blob/azureblob" // needed to initialize Azure Blob Storage driver _ "gocloud.dev/blob/azureblob" // needed to initialize Azure Blob Storage driver
_ "gocloud.dev/blob/gcsblob" // needed to initialize GCS driver _ "gocloud.dev/blob/gcsblob" // needed to initialize GCS driver
@ -126,11 +128,20 @@ func (hs *Sync) fetchObjectModificationTime(ctx context.Context, bucket *blob.Bu
} }
func (hs *Sync) fetchObject(ctx context.Context, bucket *blob.Bucket) (string, error) { func (hs *Sync) fetchObject(ctx context.Context, bucket *blob.Bucket) (string, error) {
buf := bytes.NewBuffer(nil) r, err := bucket.NewReader(ctx, hs.Object, nil)
err := bucket.Download(ctx, hs.Object, buf, nil) if err != nil {
return "", fmt.Errorf("error opening reader for object %s/%s: %w", hs.Bucket, hs.Object, err)
}
defer r.Close()
data, err := io.ReadAll(r)
if err != nil { if err != nil {
return "", fmt.Errorf("error downloading object %s/%s: %w", hs.Bucket, hs.Object, err) return "", fmt.Errorf("error downloading object %s/%s: %w", hs.Bucket, hs.Object, err)
} }
return buf.String(), nil json, err := utils.ConvertToJSON(data, filepath.Ext(hs.Object), r.ContentType())
if err != nil {
return "", fmt.Errorf("error converting blob data to json: %w", err)
}
return json, nil
} }

View File

@ -12,13 +12,32 @@ import (
"go.uber.org/mock/gomock" "go.uber.org/mock/gomock"
) )
const ( func TestBlobSync(t *testing.T) {
scheme = "xyz" tests := map[string]struct {
bucket = "b" scheme string
object = "o" bucket string
) object string
content string
convertedContent string
}{
"json file type": {
scheme: "xyz",
bucket: "b",
object: "flags.json",
content: "{\"flags\":{}}",
convertedContent: "{\"flags\":{}}",
},
"yaml file type": {
scheme: "xyz",
bucket: "b",
object: "flags.yaml",
content: "flags: []",
convertedContent: "{\"flags\":[]}",
},
}
func TestSync(t *testing.T) { for name, tt := range tests {
t.Run(name, func(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
mockCron := synctesting.NewMockCron(ctrl) mockCron := synctesting.NewMockCron(ctrl)
mockCron.EXPECT().AddFunc(gomock.Any(), gomock.Any()).DoAndReturn(func(spec string, cmd func()) error { mockCron.EXPECT().AddFunc(gomock.Any(), gomock.Any()).DoAndReturn(func(spec string, cmd func()) error {
@ -27,12 +46,12 @@ func TestSync(t *testing.T) {
mockCron.EXPECT().Start().Times(1) mockCron.EXPECT().Start().Times(1)
blobSync := &Sync{ blobSync := &Sync{
Bucket: scheme + "://" + bucket, Bucket: tt.scheme + "://" + tt.bucket,
Object: object, Object: tt.object,
Cron: mockCron, Cron: mockCron,
Logger: logger.NewLogger(nil, false), Logger: logger.NewLogger(nil, false),
} }
blobMock := NewMockBlob(scheme, func() *Sync { blobMock := NewMockBlob(tt.scheme, func() *Sync {
return blobSync return blobSync
}) })
blobSync.BlobURLMux = blobMock.URLMux() blobSync.BlobURLMux = blobMock.URLMux()
@ -40,8 +59,7 @@ func TestSync(t *testing.T) {
ctx := context.Background() ctx := context.Background()
dataSyncChan := make(chan sync.DataSync, 1) dataSyncChan := make(chan sync.DataSync, 1)
config := "my-config" blobMock.AddObject(tt.object, tt.content)
blobMock.AddObject(object, config)
go func() { go func() {
err := blobSync.Sync(ctx, dataSyncChan) err := blobSync.Sync(ctx, dataSyncChan)
@ -52,17 +70,19 @@ func TestSync(t *testing.T) {
}() }()
data := <-dataSyncChan // initial sync data := <-dataSyncChan // initial sync
if data.FlagData != config { if data.FlagData != tt.convertedContent {
t.Errorf("expected content: %s, but received content: %s", config, data.FlagData) t.Errorf("expected content: %s, but received content: %s", tt.convertedContent, data.FlagData)
} }
tickWithConfigChange(t, mockCron, dataSyncChan, blobMock, "new config") tickWithConfigChange(t, mockCron, dataSyncChan, blobMock, tt.object, tt.convertedContent)
tickWithoutConfigChange(t, mockCron, dataSyncChan) tickWithoutConfigChange(t, mockCron, dataSyncChan)
tickWithConfigChange(t, mockCron, dataSyncChan, blobMock, "new config 2") tickWithConfigChange(t, mockCron, dataSyncChan, blobMock, tt.object, tt.convertedContent)
tickWithoutConfigChange(t, mockCron, dataSyncChan) tickWithoutConfigChange(t, mockCron, dataSyncChan)
tickWithoutConfigChange(t, mockCron, dataSyncChan) tickWithoutConfigChange(t, mockCron, dataSyncChan)
})
}
} }
func tickWithConfigChange(t *testing.T, mockCron *synctesting.MockCron, dataSyncChan chan sync.DataSync, blobMock *MockBlob, newConfig string) { func tickWithConfigChange(t *testing.T, mockCron *synctesting.MockCron, dataSyncChan chan sync.DataSync, blobMock *MockBlob, object string, newConfig string) {
time.Sleep(1 * time.Millisecond) // sleep so the new file has different modification date time.Sleep(1 * time.Millisecond) // sleep so the new file has different modification date
blobMock.AddObject(object, newConfig) blobMock.AddObject(object, newConfig)
mockCron.Tick() mockCron.Tick()
@ -73,7 +93,7 @@ func tickWithConfigChange(t *testing.T, mockCron *synctesting.MockCron, dataSync
t.Errorf("expected content: %s, but received content: %s", newConfig, data.FlagData) t.Errorf("expected content: %s, but received content: %s", newConfig, data.FlagData)
} }
} else { } else {
t.Errorf("data channel unexpecdly closed") t.Errorf("data channel unexpectedly closed")
} }
default: default:
t.Errorf("data channel has no expected update") t.Errorf("data channel has no expected update")
@ -87,13 +107,18 @@ func tickWithoutConfigChange(t *testing.T, mockCron *synctesting.MockCron, dataS
if ok { if ok {
t.Errorf("unexpected update: %s", data.FlagData) t.Errorf("unexpected update: %s", data.FlagData)
} else { } else {
t.Errorf("data channel unexpecdly closed") t.Errorf("data channel unexpectedly closed")
} }
default: default:
} }
} }
func TestReSync(t *testing.T) { func TestReSync(t *testing.T) {
const (
scheme = "xyz"
bucket = "b"
object = "flags.json"
)
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
mockCron := synctesting.NewMockCron(ctrl) mockCron := synctesting.NewMockCron(ctrl)

View File

@ -2,17 +2,17 @@ package file
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"strings" "path/filepath"
msync "sync" msync "sync"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/open-feature/flagd/core/pkg/logger" "github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/sync" "github.com/open-feature/flagd/core/pkg/sync"
"gopkg.in/yaml.v3" "github.com/open-feature/flagd/core/pkg/utils"
) )
const ( const (
@ -32,8 +32,6 @@ type Watcher interface {
type Sync struct { type Sync struct {
URI string URI string
Logger *logger.Logger Logger *logger.Logger
// FileType indicates the file type e.g., json, yaml/yml etc.,
fileType string
// watchType indicates how to watch the file FSNOTIFY|FILEINFO // watchType indicates how to watch the file FSNOTIFY|FILEINFO
watchType string watchType string
watcher Watcher watcher Watcher
@ -176,42 +174,22 @@ func (fs *Sync) fetch(_ context.Context) (string, error) {
if fs.URI == "" { if fs.URI == "" {
return "", errors.New("no filepath string set") return "", errors.New("no filepath string set")
} }
if fs.fileType == "" {
uriSplit := strings.Split(fs.URI, ".") file, err := os.Open(fs.URI)
fs.fileType = uriSplit[len(uriSplit)-1] if err != nil {
return "", fmt.Errorf("error opening file %s: %w", fs.URI, err)
} }
rawFile, err := os.ReadFile(fs.URI) defer file.Close()
data, err := io.ReadAll(file)
if err != nil { if err != nil {
return "", fmt.Errorf("error reading file %s: %w", fs.URI, err) return "", fmt.Errorf("error reading file %s: %w", fs.URI, err)
} }
switch fs.fileType { // File extension is used to determine the content type, so media type is unnecessary
case "yaml", "yml": json, err := utils.ConvertToJSON(data, filepath.Ext(fs.URI), "")
return yamlToJSON(rawFile)
case "json":
return string(rawFile), nil
default:
return "", fmt.Errorf("filepath extension for URI: '%s' is not supported", fs.URI)
}
}
// yamlToJSON is a generic helper function to convert
// yaml to json
func yamlToJSON(rawFile []byte) (string, error) {
if len(rawFile) == 0 {
return "", nil
}
var ms map[string]interface{}
// yaml.Unmarshal unmarshals to map[interface]interface{}
if err := yaml.Unmarshal(rawFile, &ms); err != nil {
return "", fmt.Errorf("unmarshal yaml: %w", err)
}
r, err := json.Marshal(ms)
if err != nil { if err != nil {
return "", fmt.Errorf("convert yaml to json: %w", err) return "", fmt.Errorf("error converting file content to json: %w", err)
} }
return json, nil
return string(r), err
} }

View File

@ -190,7 +190,7 @@ func TestSimpleSync(t *testing.T) {
func TestFilePathSync_Fetch(t *testing.T) { func TestFilePathSync_Fetch(t *testing.T) {
successDirName := t.TempDir() successDirName := t.TempDir()
falureDirName := t.TempDir() failureDirName := t.TempDir()
tests := map[string]struct { tests := map[string]struct {
fpSync Sync fpSync Sync
handleResponse func(t *testing.T, fetched string, err error) handleResponse func(t *testing.T, fetched string, err error)
@ -213,9 +213,9 @@ func TestFilePathSync_Fetch(t *testing.T) {
}, },
}, },
"not found": { "not found": {
fetchDirName: falureDirName, fetchDirName: failureDirName,
fpSync: Sync{ fpSync: Sync{
URI: fmt.Sprintf("%s/%s", falureDirName, "not_found"), URI: fmt.Sprintf("%s/%s", failureDirName, "not_found"),
Logger: logger.NewLogger(nil, false), Logger: logger.NewLogger(nil, false),
}, },
handleResponse: func(t *testing.T, fetched string, err error) { handleResponse: func(t *testing.T, fetched string, err error) {
@ -309,31 +309,3 @@ func writeToFile(t *testing.T, fetchDirName, fileContents string) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestFilePathSync_yamlToJSON(t *testing.T) {
tests := map[string]struct {
input []byte
handleResponse func(t *testing.T, output string, err error)
}{
"empty": {
input: []byte(""),
handleResponse: func(t *testing.T, output string, err error) {
if err != nil {
t.Fatalf("expect no err, got err = %v", err)
}
if output != "" {
t.Fatalf("expect output = '', got output = '%v'", output)
}
},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
output, err := yamlToJSON(tt.input)
tt.handleResponse(t, output, err)
})
}
}

View File

@ -8,9 +8,12 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
parseUrl "net/url"
"path/filepath"
"github.com/open-feature/flagd/core/pkg/logger" "github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/sync" "github.com/open-feature/flagd/core/pkg/sync"
"github.com/open-feature/flagd/core/pkg/utils"
"golang.org/x/crypto/sha3" //nolint:gosec "golang.org/x/crypto/sha3" //nolint:gosec
) )
@ -77,7 +80,7 @@ func (hs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
return return
} }
if len(body) == 0 { if body == "" {
hs.Logger.Debug("configuration deleted") hs.Logger.Debug("configuration deleted")
} else { } else {
if hs.LastBodySHA == "" { if hs.LastBodySHA == "" {
@ -89,7 +92,7 @@ func (hs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI, Type: sync.ALL} dataSync <- sync.DataSync{FlagData: msg, Source: hs.URI, Type: sync.ALL}
} }
} else { } else {
currentSHA := hs.generateSha(body) currentSHA := hs.generateSha([]byte(body))
if hs.LastBodySHA != currentSHA { if hs.LastBodySHA != currentSHA {
hs.Logger.Debug("configuration modified") hs.Logger.Debug("configuration modified")
msg, err := hs.Fetch(ctx) msg, err := hs.Fetch(ctx)
@ -115,13 +118,14 @@ func (hs *Sync) Sync(ctx context.Context, dataSync chan<- sync.DataSync) error {
return nil return nil
} }
func (hs *Sync) fetchBodyFromURL(ctx context.Context, url string) ([]byte, error) { func (hs *Sync) fetchBodyFromURL(ctx context.Context, url string) (string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(nil)) req, err := http.NewRequestWithContext(ctx, "GET", url, bytes.NewBuffer(nil))
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating request for url %s: %w", url, err) return "", fmt.Errorf("error creating request for url %s: %w", url, err)
} }
req.Header.Add("Accept", "application/json") req.Header.Add("Accept", "application/json")
req.Header.Add("Accept", "application/yaml")
if hs.AuthHeader != "" { if hs.AuthHeader != "" {
req.Header.Set("Authorization", hs.AuthHeader) req.Header.Set("Authorization", hs.AuthHeader)
@ -132,7 +136,7 @@ func (hs *Sync) fetchBodyFromURL(ctx context.Context, url string) ([]byte, error
resp, err := hs.Client.Do(req) resp, err := hs.Client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("error calling endpoint %s: %w", url, err) return "", fmt.Errorf("error calling endpoint %s: %w", url, err)
} }
defer func() { defer func() {
err = resp.Body.Close() err = resp.Body.Close()
@ -141,12 +145,31 @@ func (hs *Sync) fetchBodyFromURL(ctx context.Context, url string) ([]byte, error
} }
}() }()
body, err := io.ReadAll(resp.Body) statusOK := resp.StatusCode >= 200 && resp.StatusCode < 300
if err != nil { if !statusOK {
return nil, fmt.Errorf("unable to read body to bytes: %w", err) return "", fmt.Errorf("error fetching from url %s: %s", url, resp.Status)
} }
return body, nil body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("unable to read body to bytes: %w", err)
}
json, err := utils.ConvertToJSON(body, getFileExtensions(url), resp.Header.Get("Content-Type"))
if err != nil {
return "", fmt.Errorf("error converting response body to json: %w", err)
}
return json, nil
}
// getFileExtensions returns the file extension from the URL path
func getFileExtensions(url string) string {
u, err := parseUrl.Parse(url)
if err != nil {
return ""
}
return filepath.Ext(u.Path)
} }
func (hs *Sync) generateSha(body []byte) string { func (hs *Sync) generateSha(body []byte) string {
@ -164,9 +187,9 @@ func (hs *Sync) Fetch(ctx context.Context) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
if len(body) != 0 { if body != "" {
hs.LastBodySHA = hs.generateSha(body) hs.LastBodySHA = hs.generateSha([]byte(body))
} }
return string(body), nil return body, nil
} }

View File

@ -19,19 +19,23 @@ import (
func TestSimpleSync(t *testing.T) { func TestSimpleSync(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
resp := "test response"
mockCron := synctesting.NewMockCron(ctrl) mockCron := synctesting.NewMockCron(ctrl)
mockCron.EXPECT().AddFunc(gomock.Any(), gomock.Any()).DoAndReturn(func(spec string, cmd func()) error { mockCron.EXPECT().AddFunc(gomock.Any(), gomock.Any()).DoAndReturn(func(_ string, _ func()) error {
return nil return nil
}) })
mockCron.EXPECT().Start().Times(1) mockCron.EXPECT().Start().Times(1)
mockClient := syncmock.NewMockClient(ctrl) mockClient := syncmock.NewMockClient(ctrl)
mockClient.EXPECT().Do(gomock.Any()).Return(&http.Response{Body: io.NopCloser(strings.NewReader(resp))}, nil) responseBody := "test response"
resp := &http.Response{
Header: map[string][]string{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader(responseBody)),
StatusCode: http.StatusOK,
}
mockClient.EXPECT().Do(gomock.Any()).Return(resp, nil)
httpSync := Sync{ httpSync := Sync{
URI: "http://localhost", URI: "http://localhost/flags",
Client: mockClient, Client: mockClient,
Cron: mockCron, Cron: mockCron,
LastBodySHA: "", LastBodySHA: "",
@ -51,8 +55,51 @@ func TestSimpleSync(t *testing.T) {
data := <-dataSyncChan data := <-dataSyncChan
if data.FlagData != resp { if data.FlagData != responseBody {
t.Errorf("expected content: %s, but received content: %s", resp, data.FlagData) t.Errorf("expected content: %s, but received content: %s", responseBody, data.FlagData)
}
}
func TestExtensionWithQSSync(t *testing.T) {
ctrl := gomock.NewController(t)
mockCron := synctesting.NewMockCron(ctrl)
mockCron.EXPECT().AddFunc(gomock.Any(), gomock.Any()).DoAndReturn(func(_ string, _ func()) error {
return nil
})
mockCron.EXPECT().Start().Times(1)
mockClient := syncmock.NewMockClient(ctrl)
responseBody := "test response"
resp := &http.Response{
Header: map[string][]string{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader(responseBody)),
StatusCode: http.StatusOK,
}
mockClient.EXPECT().Do(gomock.Any()).Return(resp, nil)
httpSync := Sync{
URI: "http://localhost/flags.json?env=dev",
Client: mockClient,
Cron: mockCron,
LastBodySHA: "",
Logger: logger.NewLogger(nil, false),
}
ctx := context.Background()
dataSyncChan := make(chan sync.DataSync)
go func() {
err := httpSync.Sync(ctx, dataSyncChan)
if err != nil {
log.Fatalf("Error start sync: %s", err.Error())
return
}
}()
data := <-dataSyncChan
if data.FlagData != responseBody {
t.Errorf("expected content: %s, but received content: %s", responseBody, data.FlagData)
} }
} }
@ -68,9 +115,11 @@ func TestHTTPSync_Fetch(t *testing.T) {
handleResponse func(*testing.T, Sync, string, error) handleResponse func(*testing.T, Sync, string, error)
}{ }{
"success": { "success": {
setup: func(t *testing.T, client *syncmock.MockClient) { setup: func(_ *testing.T, client *syncmock.MockClient) {
client.EXPECT().Do(gomock.Any()).Return(&http.Response{ client.EXPECT().Do(gomock.Any()).Return(&http.Response{
Header: map[string][]string{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader("test response")), Body: io.NopCloser(strings.NewReader("test response")),
StatusCode: http.StatusOK,
}, nil) }, nil)
}, },
uri: "http://localhost", uri: "http://localhost",
@ -85,17 +134,19 @@ func TestHTTPSync_Fetch(t *testing.T) {
}, },
}, },
"return an error if no uri": { "return an error if no uri": {
setup: func(t *testing.T, client *syncmock.MockClient) {}, setup: func(_ *testing.T, _ *syncmock.MockClient) {},
handleResponse: func(t *testing.T, _ Sync, fetched string, err error) { handleResponse: func(t *testing.T, _ Sync, _ string, err error) {
if err == nil { if err == nil {
t.Error("expected err, got nil") t.Error("expected err, got nil")
} }
}, },
}, },
"update last body sha": { "update last body sha": {
setup: func(t *testing.T, client *syncmock.MockClient) { setup: func(_ *testing.T, client *syncmock.MockClient) {
client.EXPECT().Do(gomock.Any()).Return(&http.Response{ client.EXPECT().Do(gomock.Any()).Return(&http.Response{
Header: map[string][]string{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader("test response")), Body: io.NopCloser(strings.NewReader("test response")),
StatusCode: http.StatusOK,
}, nil) }, nil)
}, },
uri: "http://localhost", uri: "http://localhost",
@ -121,7 +172,11 @@ func TestHTTPSync_Fetch(t *testing.T) {
if actualAuthHeader != "Bearer "+expectedToken { if actualAuthHeader != "Bearer "+expectedToken {
t.Fatalf("expected Authorization header to be 'Bearer %s', got %s", expectedToken, actualAuthHeader) t.Fatalf("expected Authorization header to be 'Bearer %s', got %s", expectedToken, actualAuthHeader)
} }
return &http.Response{Body: io.NopCloser(strings.NewReader("test response"))}, nil return &http.Response{
Header: map[string][]string{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader("test response")),
StatusCode: http.StatusOK,
}, nil
}) })
}, },
uri: "http://localhost", uri: "http://localhost",
@ -148,7 +203,11 @@ func TestHTTPSync_Fetch(t *testing.T) {
if actualAuthHeader != expectedHeader { if actualAuthHeader != expectedHeader {
t.Fatalf("expected Authorization header to be '%s', got %s", expectedHeader, actualAuthHeader) t.Fatalf("expected Authorization header to be '%s', got %s", expectedHeader, actualAuthHeader)
} }
return &http.Response{Body: io.NopCloser(strings.NewReader("test response"))}, nil return &http.Response{
Header: map[string][]string{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader("test response")),
StatusCode: http.StatusOK,
}, nil
}) })
}, },
uri: "http://localhost", uri: "http://localhost",
@ -167,6 +226,21 @@ func TestHTTPSync_Fetch(t *testing.T) {
} }
}, },
}, },
"unauthorized request": {
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("test response")),
StatusCode: http.StatusUnauthorized,
}, nil)
},
uri: "http://localhost",
handleResponse: func(t *testing.T, _ Sync, _ string, err error) {
if err == nil {
t.Fatalf("expected unauthorized request to return an error")
}
},
},
} }
for name, tt := range tests { for name, tt := range tests {
@ -226,9 +300,11 @@ func TestHTTPSync_Resync(t *testing.T) {
wantNotifications []sync.DataSync wantNotifications []sync.DataSync
}{ }{
"success": { "success": {
setup: func(t *testing.T, client *syncmock.MockClient) { setup: func(_ *testing.T, client *syncmock.MockClient) {
client.EXPECT().Do(gomock.Any()).Return(&http.Response{ client.EXPECT().Do(gomock.Any()).Return(&http.Response{
Body: io.NopCloser(strings.NewReader("test response")), Header: map[string][]string{"Content-Type": {"application/json"}},
Body: io.NopCloser(strings.NewReader("")),
StatusCode: http.StatusOK,
}, nil) }, nil)
}, },
uri: "http://localhost", uri: "http://localhost",
@ -251,8 +327,8 @@ func TestHTTPSync_Resync(t *testing.T) {
}, },
}, },
"error response": { "error response": {
setup: func(t *testing.T, client *syncmock.MockClient) {}, setup: func(_ *testing.T, _ *syncmock.MockClient) {},
handleResponse: func(t *testing.T, _ Sync, fetched string, err error) { handleResponse: func(t *testing.T, _ Sync, _ string, err error) {
if err == nil { if err == nil {
t.Error("expected err, got nil") t.Error("expected err, got nil")
} }

View File

@ -0,0 +1,42 @@
package utils
import (
"fmt"
"mime"
"regexp"
"strings"
)
var alphanumericRegex = regexp.MustCompile("[^a-zA-Z0-9]+")
// ConvertToJSON attempts to convert the content of a file to JSON based on the file extension.
// The media type is used as a fallback in case the file extension is not recognized.
func ConvertToJSON(data []byte, fileExtension string, mediaType string) (string, error) {
var detectedType string
if fileExtension != "" {
// file extension only contains alphanumeric characters
detectedType = alphanumericRegex.ReplaceAllString(fileExtension, "")
} else {
parsedMediaType, _, err := mime.ParseMediaType(mediaType)
if err != nil {
return "", fmt.Errorf("unable to determine file format: %w", err)
}
detectedType = parsedMediaType
}
// Normalize the detected type
detectedType = strings.ToLower(detectedType)
switch detectedType {
case "yaml", "yml", "application/yaml", "application/x-yaml":
str, err := YAMLToJSON(data)
if err != nil {
return "", fmt.Errorf("error converting blob from yaml to json: %w", err)
}
return str, nil
case "json", "application/json":
return string(data), nil
default:
return "", fmt.Errorf("unsupported file format: '%s'", detectedType)
}
}

View File

@ -0,0 +1,107 @@
package utils
import (
"strings"
"testing"
)
func TestConvertToJSON(t *testing.T) {
tests := map[string]struct {
data []byte
fileExtension string
mediaType string
want string
wantErr bool
errContains string
}{
"json file type": {
data: []byte(`{"flags": {"foo": "bar"}}`),
fileExtension: "json",
mediaType: "application/json",
want: `{"flags": {"foo": "bar"}}`,
wantErr: false,
},
"json file type using in http path": {
data: []byte(`{"flags": {"foo": "bar"}}`),
fileExtension: ".json/",
mediaType: "",
want: `{"flags": {"foo": "bar"}}`,
wantErr: false,
},
"json file type with encoding": {
data: []byte(`{"flags": {"foo": "bar"}}`),
fileExtension: "json",
mediaType: "application/json; charset=utf-8",
want: `{"flags": {"foo": "bar"}}`,
wantErr: false,
},
"yaml file type": {
data: []byte("flags:\n foo: bar"),
fileExtension: "yaml",
mediaType: "application/yaml",
want: `{"flags":{"foo":"bar"}}`,
wantErr: false,
},
"yaml file type with encoding": {
data: []byte("flags:\n foo: bar"),
fileExtension: "yaml",
mediaType: "application/yaml; charset=utf-8",
want: `{"flags":{"foo":"bar"}}`,
wantErr: false,
},
"yml file type": {
data: []byte("flags:\n foo: bar"),
fileExtension: "yml",
mediaType: "application/x-yaml",
want: `{"flags":{"foo":"bar"}}`,
wantErr: false,
},
"invalid yaml": {
data: []byte("invalid: [yaml: content"),
fileExtension: "yaml",
mediaType: "application/yaml",
wantErr: true,
errContains: "error converting blob from yaml to json",
},
"unsupported file type": {
data: []byte("some content"),
fileExtension: "txt",
mediaType: "text/plain",
wantErr: true,
errContains: "unsupported file format",
},
"empty file type with valid media type": {
data: []byte(`{"flags": {"foo": "bar"}}`),
fileExtension: "",
mediaType: "application/json",
want: `{"flags": {"foo": "bar"}}`,
wantErr: false,
},
"invalid media type": {
data: []byte("some content"),
fileExtension: "",
mediaType: "invalid/\\type",
wantErr: true,
errContains: "unable to determine file format",
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got, err := ConvertToJSON(tt.data, tt.fileExtension, tt.mediaType)
if (err != nil) != tt.wantErr {
t.Errorf("ConvertToJSON() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
if err == nil || !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("ConvertToJSON() expected error containing %q, got %v", tt.errContains, err)
}
return
}
if got != tt.want {
t.Errorf("ConvertToJSON() = %v, want %v", got, tt.want)
}
})
}
}

27
core/pkg/utils/yaml.go Normal file
View File

@ -0,0 +1,27 @@
package utils
import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
)
// converts YAML byte array to JSON string
func YAMLToJSON(rawFile []byte) (string, error) {
if len(rawFile) == 0 {
return "", nil
}
var ms map[string]interface{}
if err := yaml.Unmarshal(rawFile, &ms); err != nil {
return "", fmt.Errorf("error unmarshaling yaml: %w", err)
}
r, err := json.Marshal(ms)
if err != nil {
return "", fmt.Errorf("error marshaling json: %w", err)
}
return string(r), err
}

View File

@ -0,0 +1,57 @@
package utils
import "testing"
func TestYAMLToJSON(t *testing.T) {
tests := map[string]struct {
input []byte
expected string
expectedError bool
}{
"empty": {
input: []byte(""),
expected: "",
expectedError: false,
},
"simple yaml": {
input: []byte("key: value"),
expected: `{"key":"value"}`,
expectedError: false,
},
"nested yaml": {
input: []byte("parent:\n child: value"),
expected: `{"parent":{"child":"value"}}`,
expectedError: false,
},
"invalid yaml": {
input: []byte("invalid: yaml: : :"),
expectedError: true,
},
"array yaml": {
input: []byte("items:\n - item1\n - item2"),
expected: `{"items":["item1","item2"]}`,
expectedError: false,
},
"complex yaml": {
input: []byte("bool: true\nnum: 123\nstr: hello\nobj:\n nested: value\narr:\n - 1\n - 2"),
expected: `{"arr":[1,2],"bool":true,"num":123,"obj":{"nested":"value"},"str":"hello"}`,
expectedError: false,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
output, err := YAMLToJSON(tt.input)
if tt.expectedError && err == nil {
t.Error("expected error but got none")
}
if !tt.expectedError && err != nil {
t.Errorf("unexpected error: %v", err)
}
if output != tt.expected {
t.Errorf("expected output '%v', got '%v'", tt.expected, output)
}
})
}
}

View File

@ -26,10 +26,10 @@ See [sync source](../reference/sync-configuration.md#source-configuration) confi
The HTTP sync provider fetch flags from a remote source and periodically poll the source for flag definition updates. The HTTP sync provider fetch flags from a remote source and periodically poll the source for flag definition updates.
```shell ```shell
flagd start --uri https://my-flag-source.json flagd start --uri https://my-flag-source/flags.json
``` ```
In this example, `https://my-flag-source.json` is a remote endpoint responding valid feature flag definition when In this example, `https://my-flag-source/flags.json` is a remote endpoint responding valid feature flag definition when
invoked with **HTTP GET** request. invoked with **HTTP GET** request.
The polling interval, port, TLS settings, and authentication information can be configured. The polling interval, port, TLS settings, and authentication information can be configured.
See [sync source](../reference/sync-configuration.md#source-configuration) configuration for details. See [sync source](../reference/sync-configuration.md#source-configuration) configuration for details.

View File

@ -22,11 +22,17 @@ it is passed to the correct implementation:
| `azblob` | `azblob://` | `azblob://my-container/my-flags.json` | | `azblob` | `azblob://` | `azblob://my-container/my-flags.json` |
| `s3` | `s3://` | `s3://my-bucket/my-flags.json` | | `s3` | `s3://` | `s3://my-bucket/my-flags.json` |
### Data Serialization
The `file`, `http`, `gcs`, `azblob` and `s3` sync providers expect the data to be formatted as JSON or YAML.
The file extension is used to determine the serialization format.
If the file extension hasn't been defined, the [media type](https://en.wikipedia.org/wiki/Media_type) will be used instead.
### Custom gRPC Target URI ### Custom gRPC Target URI
Apart from default `dns` resolution, Flagd also support different resolution method e.g. `xds`. Currently, we are supporting all [core resolver](https://grpc.io/docs/guides/custom-name-resolution/) Apart from default `dns` resolution, Flagd also support different resolution method e.g. `xds`.
and one custom resolver for `envoy` proxy resolution. For more details, please refer the Currently, we are supporting all [core resolver](https://grpc.io/docs/guides/custom-name-resolution/) and one custom resolver for `envoy` proxy resolution.
[RFC](https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/proposal/rfc-grpc-custom-name-resolver.md) document. For more details, please refer the [RFC](https://github.com/open-feature/flagd/blob/main/docs/reference/specifications/proposal/rfc-grpc-custom-name-resolver.md) document.
```shell ```shell
./bin/flagd start -x --uri envoy://localhost:9211/test.service ./bin/flagd start -x --uri envoy://localhost:9211/test.service
@ -72,8 +78,8 @@ Sync providers:
- `file` - config/samples/example_flags.json - `file` - config/samples/example_flags.json
- `fsnotify` - config/samples/example_flags.json - `fsnotify` - config/samples/example_flags.json
- `fileinfo` - config/samples/example_flags.json - `fileinfo` - config/samples/example_flags.json
- `http` - <http://my-flag-source.json/> - `http` - <http://my-flag-source.com/flags.json>
- `https` - <https://my-secure-flag-source.json/> - `https` - <https://my-secure-flag-source.com/flags.json>
- `kubernetes` - default/my-flag-config - `kubernetes` - default/my-flag-config
- `grpc`(insecure) - grpc-source:8080 - `grpc`(insecure) - grpc-source:8080
- `grpcs`(secure) - my-flag-source:8080 - `grpcs`(secure) - my-flag-source:8080
@ -88,9 +94,9 @@ Startup command:
--sources='[{"uri":"config/samples/example_flags.json","provider":"file"}, --sources='[{"uri":"config/samples/example_flags.json","provider":"file"},
{"uri":"config/samples/example_flags.json","provider":"fsnotify"}, {"uri":"config/samples/example_flags.json","provider":"fsnotify"},
{"uri":"config/samples/example_flags.json","provider":"fileinfo"}, {"uri":"config/samples/example_flags.json","provider":"fileinfo"},
{"uri":"http://my-flag-source.json","provider":"http","bearerToken":"bearer-dji34ld2l"}, {"uri":"http://my-flag-source/flags.json","provider":"http","bearerToken":"bearer-dji34ld2l"},
{"uri":"https://secure-remote/bearer-auth","provider":"http","authHeader":"Bearer bearer-dji34ld2l"}, {"uri":"https://secure-remote/bearer-auth/flags.json","provider":"http","authHeader":"Bearer bearer-dji34ld2l"},
{"uri":"https://secure-remote/basic-auth","provider":"http","authHeader":"Basic dXNlcjpwYXNz"}, {"uri":"https://secure-remote/basic-auth/flags.json","provider":"http","authHeader":"Basic dXNlcjpwYXNz"},
{"uri":"default/my-flag-config","provider":"kubernetes"}, {"uri":"default/my-flag-config","provider":"kubernetes"},
{"uri":"grpc-source:8080","provider":"grpc"}, {"uri":"grpc-source:8080","provider":"grpc"},
{"uri":"my-flag-source:8080","provider":"grpc", "maxMsgSize": 5242880}, {"uri":"my-flag-source:8080","provider":"grpc", "maxMsgSize": 5242880},
@ -110,7 +116,7 @@ sources:
provider: fsnotify provider: fsnotify
- uri: config/samples/example_flags.json - uri: config/samples/example_flags.json
provider: fileinfo provider: fileinfo
- uri: http://my-flag-source.json - uri: http://my-flag-source/flags.json
provider: http provider: http
bearerToken: bearer-dji34ld2l bearerToken: bearer-dji34ld2l
- uri: default/my-flag-config - uri: default/my-flag-config