diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index f70d356..ef16cd0 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -102,10 +102,13 @@ docs/WriteAuthorizationModelResponse.md docs/WriteRequest.md docs/WriteRequestDeletes.md docs/WriteRequestWrites.md +docs/opentelemetry.md example/Makefile example/README.md example/example1/example1.go example/example1/go.mod +example/opentelemetry/go.mod +example/opentelemetry/main.go git_push.sh go.mod go.sum @@ -208,4 +211,26 @@ oauth2/token_test.go oauth2/transport.go oauth2/transport_test.go response.go +telemetry/attribute.go +telemetry/attribute_test.go +telemetry/attributes.go +telemetry/attributes_test.go +telemetry/configuration.go +telemetry/configuration_test.go +telemetry/counter.go +telemetry/counter_test.go +telemetry/counters.go +telemetry/counters_test.go +telemetry/histogram.go +telemetry/histogram_test.go +telemetry/histograms.go +telemetry/histograms_test.go +telemetry/interfaces.go +telemetry/interfaces_test.go +telemetry/metric.go +telemetry/metric_test.go +telemetry/metrics.go +telemetry/metrics_test.go +telemetry/telemetry.go +telemetry/telemetry_test.go utils.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 305178e..82cac46 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,4 +43,4 @@ Please do not open issues for general support or usage questions. Instead, join ### Vulnerability Reporting -Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://github.com/openfga/go-sdk/blob/main/.github/SECURITY.md) details the procedure for disclosing security issues. +Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://github.com/openfga/.github/blob/main/SECURITY.md) details the procedure for disclosing security issues. diff --git a/README.md b/README.md index 74538c1..a878f9d 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ If your server is configured with [authentication enabled](https://openfga.dev/d Get a paginated list of stores. -[API Documentation](https://openfga.dev/api/service/docs/api#/Stores/ListStores) +[API Documentation](https://openfga.dev/api/service#/Stores/ListStores) ```golang options := ClientListStoresOptions{ @@ -248,7 +248,7 @@ stores, err := fgaClient.ListStores(context.Background()).Options(options).Execu Create and initialize a store. -[API Documentation](https://openfga.dev/api/service/docs/api#/Stores/CreateStore) +[API Documentation](https://openfga.dev/api/service#/Stores/CreateStore) ```golang body := ClientCreateStoreRequest{Name: "FGA Demo"} @@ -269,7 +269,7 @@ fgaClient.SetStoreId(store.Id) Get information about the current store. -[API Documentation](https://openfga.dev/api/service/docs/api#/Stores/GetStore) +[API Documentation](https://openfga.dev/api/service#/Stores/GetStore) ```golang options := ClientGetStoreOptions{ @@ -288,7 +288,7 @@ if err != nil { Delete a store. -[API Documentation](https://openfga.dev/api/service/docs/api#/Stores/DeleteStore) +[API Documentation](https://openfga.dev/api/service#/Stores/DeleteStore) ```golang options := ClientDeleteStoreOptions{ @@ -508,7 +508,7 @@ By default, write runs in a transaction mode where any invalid operation (deleti ```golang body := ClientWriteRequest{ - Writes: []ClientTupleKey{ { + Writes: &[]ClientTupleKey{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "viewer", Object: "document:roadmap", @@ -517,7 +517,7 @@ body := ClientWriteRequest{ Relation: "viewer", Object: "document:budget", } }, - Deletes: []ClientTupleKeyWithoutCondition{ { + Deletes: &[]ClientTupleKeyWithoutCondition{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "writer", Object: "document:roadmap", @@ -541,7 +541,7 @@ The SDK will split the writes into separate chunks and send them in separate req ```golang body := ClientWriteRequest{ - Writes: []ClientTupleKey{ { + Writes: &[]ClientTupleKey{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "viewer", Object: "document:roadmap", @@ -550,7 +550,7 @@ body := ClientWriteRequest{ Relation: "viewer", Object: "document:budget", } }, - Deletes: []ClientTupleKeyWithoutCondition{ { + Deletes: &[]ClientTupleKeyWithoutCondition{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "writer", Object: "document:roadmap", @@ -602,7 +602,7 @@ body := ClientCheckRequest{ User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "viewer", Object: "document:roadmap", - ContextualTuples: []ClientTupleKey{ { + ContextualTuples: &[]ClientTupleKey{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "editor", Object: "document:roadmap", @@ -640,7 +640,7 @@ body := ClientBatchCheckBody{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "viewer", Object: "document:roadmap", - ContextualTuples: []ClientTupleKey{ { + ContextualTuples: &[]ClientTupleKey{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "editor", Object: "document:roadmap", @@ -649,7 +649,7 @@ body := ClientBatchCheckBody{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "admin", Object: "document:roadmap", - ContextualTuples: []ClientTupleKey{ { + ContextualTuples: &[]ClientTupleKey{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "editor", Object: "document:roadmap", @@ -753,7 +753,7 @@ body := ClientListObjectsRequest{ User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "can_read", Type: "document", - ContextualTuples: []ClientTupleKey{ { + ContextualTuples: &[]ClientTupleKey{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "editor", Object: "folder:product", @@ -788,7 +788,7 @@ body := ClientListRelationsRequest{ User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Object: "document:roadmap", Relations: []string{"can_view", "can_edit", "can_delete", "can_rename"}, - ContextualTuples: []ClientTupleKey{ { + ContextualTuples: &[]ClientTupleKey{ { User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", Relation: "editor", Object: "document:roadmap", @@ -1038,7 +1038,7 @@ Class | Method | HTTP request | Description ### OpenTelemetry -This SDK supports producing metrics that can be consumed as part of an [OpenTelemetry](https://opentelemetry.io/) setup. For more information, please see [the documentation](https://github.com/openfga/go-sdk/blob/main/docs/OpenTelemetry.md) +This SDK supports producing metrics that can be consumed as part of an [OpenTelemetry](https://opentelemetry.io/) setup. For more information, please see [the documentation](https://github.com/openfga/go-sdk/blob/main/docs/opentelemetry.md) ## Contributing diff --git a/client/client.go b/client/client.go index 89abd4a..fb70bdc 100644 --- a/client/client.go +++ b/client/client.go @@ -2018,17 +2018,23 @@ func (client *OpenFgaClient) BatchCheckExecute(request SdkClientBatchCheckReques return nil, err } + checkOptions := &ClientCheckOptions{ + AuthorizationModelId: authorizationModelId, + StoreId: storeId, + } + + if request.GetOptions() != nil && request.GetOptions().Consistency != nil { + checkOptions.Consistency = request.GetOptions().Consistency + } + for index, checkBody := range *request.GetBody() { index, checkBody := index, checkBody group.Go(func() error { singleResponse, err := client.CheckExecute(&SdkClientCheckRequest{ - ctx: ctx, - Client: client, - body: &checkBody, - options: &ClientCheckOptions{ - AuthorizationModelId: authorizationModelId, - StoreId: storeId, - }, + ctx: ctx, + Client: client, + body: &checkBody, + options: checkOptions, }) if _, ok := err.(fgaSdk.FgaApiAuthenticationError); ok { diff --git a/client/client_test.go b/client/client_test.go index a9076cb..dbf9474 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -2202,7 +2202,7 @@ func TestOpenFgaClient(t *testing.T) { options := ClientBatchCheckOptions{ AuthorizationModelId: openfga.PtrString(authModelId), MaxParallelRequests: openfga.PtrInt32(5), - Consistency: openfga.CONSISTENCYPREFERENCE_UNSPECIFIED.Ptr(), + Consistency: openfga.CONSISTENCYPREFERENCE_HIGHER_CONSISTENCY.Ptr(), } var expectedResponse openfga.CheckResponse @@ -2213,7 +2213,7 @@ func TestOpenFgaClient(t *testing.T) { httpmock.Activate() defer httpmock.DeactivateAndReset() httpmock.RegisterMatcherResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, getStoreId(t, fgaClient), test.RequestPath), - httpmock.BodyContainsString(`"consistency":"UNSPECIFIED"`), + httpmock.BodyContainsString(`"consistency":"HIGHER_CONSISTENCY"`), func(req *http.Request) (*http.Response, error) { resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) if err != nil { @@ -2223,10 +2223,16 @@ func TestOpenFgaClient(t *testing.T) { }, ) - _, err := fgaClient.BatchCheck(context.Background()).Body(requestBody).Options(options).Execute() + checks, err := fgaClient.BatchCheck(context.Background()).Body(requestBody).Options(options).Execute() if err != nil { t.Fatalf("%v", err) } + + for _, check := range *checks { + if check.Error != nil { + t.Fatalf("a check failed %v", check.Error) + } + } }) t.Run("Expand", func(t *testing.T) { diff --git a/docs/OpenTelemetry.md b/docs/OpenTelemetry.md index 7afed5e..cab5599 100644 --- a/docs/OpenTelemetry.md +++ b/docs/OpenTelemetry.md @@ -36,8 +36,82 @@ In cases when metrics events are sent, they will not be viewable outside of infr | `url.full` | string | Yes | Full URL of the request | | `user_agent.original` | string | Yes | User Agent used in the query | -## Example +## Customizing Reporting -You can find a basic example integration in the [examples/opentelemetry](../../examples/opentelemetry) directory, which demonstrates how to configure the OpenFGA SDK with OpenTelemetry. +To control which metrics and attributes are reported by the SDK, you can provide your own `TelemetryConfiguration` instance during initialization, as shown in the example above. The `TelemetryConfiguration` class allows you to configure the metrics and attributes that are reported by the SDK, as outlined in [the tables above](#metrics). -Please see [the OpenTelemetry documentation](https://opentelemetry.io/docs/languages/go/) for additional details on how to further configure their SDK for your applications. +## Usage + +### 1. Install Dependencies + +Install the OpenFGA SDK and OpenTelemetry SDK in your application using `pip`: + +```sh +go get "github.com/openfg/go-sdk" \ + "go.opentelemetry.io/otel" \ + "go.opentelemetry.io/otel/sdk/metric" + ``` + +You must also install an OpenTelemetry exporter; for example, the OTLP gRPC exporter: + +```sh +go get "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" +``` + +### 2. Configure OpenTelemetry + +Configure your application to use OpenTelemetry, and set up the metrics provider to export metrics using an exporter: + +```go +package main + +import ( + "context" + "errors" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/sdk/metric" +) + +// Configure OpenTelemetry +metricExporter, _ := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithGRPCConn(conn)) + +meterProvider := sdkmetric.NewMeterProvider( + sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter)), + sdkmetric.WithResource(res), +) +otel.SetMeterProvider(meterProvider) +defer meterProvider.Shutdown() +``` + +### 3. Configure OpenFGA + +Configure the OpenFGA client, and (optionally) customize what metrics and attributes are reported: + +```go +import ( + "github.com/openfga/go-sdk/client" + "os" +) + +fgaClient, err := NewSdkClient(&ClientConfiguration{ + ApiUrl: os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example + StoreId: os.Getenv("FGA_STORE_ID"), // not needed when calling `CreateStore` or `ListStores` + AuthorizationModelId: os.Getenv("FGA_MODEL_ID"), // optional, recommended to be set for production + Telemetry: &telemetry.Configuration{ + Metrics: &telemetry.MetricsConfiguration{ + METRIC_HISTOGRAM_REQUEST_DURATION: &telemetry.MetricConfiguration{ + ATTR_FGA_CLIENT_REQUEST_CLIENT_ID: &telemetry.AttributeConfiguration{Enabled: true}, + ATTR_HTTP_RESPONSE_STATUS_CODE: &telemetry.AttributeConfiguration{Enabled: true}, + } + } + } +}) + +``` + +## Example Integration + +An [example integration](../example/opentelemetry) is provided that also demonstrates how to configure an application with OpenFGA and OpenTelemetry. Please refer to [the README](../example/opentelemetry/README.md) for more information. diff --git a/example/example1/go.mod b/example/example1/go.mod index 3fa01e1..1180d3a 100644 --- a/example/example1/go.mod +++ b/example/example1/go.mod @@ -4,7 +4,14 @@ go 1.22.2 require github.com/openfga/go-sdk v0.6.1 -require golang.org/x/sync v0.8.0 // indirect +require ( + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + golang.org/x/sync v0.8.0 // indirect +) // To reference local build, uncomment below and run `go mod tidy` -replace github.com/openfga/go-sdk v0.6.1 => ../../ +// replace github.com/openfga/go-sdk v0.6.1 => ../../ diff --git a/example/example1/go.sum b/example/example1/go.sum index 20ef020..665f37e 100644 --- a/example/example1/go.sum +++ b/example/example1/go.sum @@ -1,4 +1,27 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +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/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= +github.com/openfga/go-sdk v0.6.1 h1:AlCjX4auM7X9sktHLx9YvFjvU+FoMGuvQ8QkJD627Lo= +github.com/openfga/go-sdk v0.6.1/go.mod h1:zui7pHE3eLAYh2fFmEMrWg9XbxYns2WW5Xr/GEgili4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/example/opentelemetry/go.mod b/example/opentelemetry/go.mod index 5cf93ad..233a3b6 100644 --- a/example/opentelemetry/go.mod +++ b/example/opentelemetry/go.mod @@ -2,11 +2,12 @@ module openfga-opentelemetry-example go 1.21 -replace github.com/openfga/go-sdk => ../.. +// To reference local build, uncomment below and run `go mod tidy` +// replace github.com/openfga/go-sdk v0.6.1 => ../../ require ( github.com/joho/godotenv v1.5.1 - github.com/openfga/go-sdk v0.0.0-00010101000000-000000000000 + github.com/openfga/go-sdk v0.6.1 go.opentelemetry.io/otel v1.29.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 go.opentelemetry.io/otel/sdk v1.29.0 diff --git a/example/opentelemetry/go.sum b/example/opentelemetry/go.sum index 0c8ae80..08d3993 100644 --- a/example/opentelemetry/go.sum +++ b/example/opentelemetry/go.sum @@ -17,6 +17,8 @@ github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInw github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/openfga/go-sdk v0.6.1 h1:AlCjX4auM7X9sktHLx9YvFjvU+FoMGuvQ8QkJD627Lo= +github.com/openfga/go-sdk v0.6.1/go.mod h1:zui7pHE3eLAYh2fFmEMrWg9XbxYns2WW5Xr/GEgili4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= diff --git a/telemetry/attributes.go b/telemetry/attributes.go index 2a4e3f8..033cec0 100644 --- a/telemetry/attributes.go +++ b/telemetry/attributes.go @@ -244,7 +244,7 @@ func (m *Metrics) AttributesFromResendCount(resendCount int, attrs map[*Attribut } func (m *Metrics) BuildTelemetryAttributes(requestMethod string, methodParameters map[string]interface{}, req *http.Request, res *http.Response, requestStarted time.Time, resendCount int) (map[*Attribute]string, float64, float64, error) { - var attrs = make(map[*Attribute]string) + var attrs map[*Attribute]string attrs, _ = m.AttributesFromRequest(req, methodParameters) attrs, _ = m.AttributesFromResponse(res, attrs) diff --git a/telemetry/interfaces.go b/telemetry/interfaces.go index c580ed7..23b9218 100644 --- a/telemetry/interfaces.go +++ b/telemetry/interfaces.go @@ -1,23 +1,23 @@ -package telemetry - -/* -CheckRequestTupleKeyInterface is a simplified interface that defines the methods the CheckRequestTupleKey struct implements, relevant to the context of the telemetry package. -*/ -type CheckRequestTupleKeyInterface interface { - GetUser() *string -} - -/* -CheckRequestInterface is a simplified interface that defines the methods the CheckRequest struct implements, relevant to the context of the telemetry package. -*/ -type CheckRequestInterface interface { - GetTupleKey() CheckRequestTupleKeyInterface - RequestAuthorizationModelIdInterface -} - -/* -RequestAuthorizationModelIdInterface is a generic interface that defines the GetAuthorizationModelId() method a Request struct implements, relevant to the context of the telemetry package. -*/ -type RequestAuthorizationModelIdInterface interface { - GetAuthorizationModelId() *string -} +package telemetry + +/* +CheckRequestTupleKeyInterface is a simplified interface that defines the methods the CheckRequestTupleKey struct implements, relevant to the context of the telemetry package. +*/ +type CheckRequestTupleKeyInterface interface { + GetUser() *string +} + +/* +CheckRequestInterface is a simplified interface that defines the methods the CheckRequest struct implements, relevant to the context of the telemetry package. +*/ +type CheckRequestInterface interface { + GetTupleKey() CheckRequestTupleKeyInterface + RequestAuthorizationModelIdInterface +} + +/* +RequestAuthorizationModelIdInterface is a generic interface that defines the GetAuthorizationModelId() method a Request struct implements, relevant to the context of the telemetry package. +*/ +type RequestAuthorizationModelIdInterface interface { + GetAuthorizationModelId() *string +}