From 607b652c8c7fe72c4048f973ff538ec3f5b760ec Mon Sep 17 00:00:00 2001 From: Jeffrey Chien Date: Mon, 16 Oct 2023 17:20:17 -0400 Subject: [PATCH] [extension/awsmiddleware] Add AWS middleware extension interface (#116) --- .github/CODEOWNERS | 1 + .../awsemfexporter/metric_translator_test.go | 24 +- extension/awsmiddleware/Makefile | 1 + extension/awsmiddleware/README.md | 29 ++ extension/awsmiddleware/config.go | 27 ++ extension/awsmiddleware/config_test.go | 67 +++++ extension/awsmiddleware/doc.go | 6 + extension/awsmiddleware/go.mod | 51 ++++ extension/awsmiddleware/go.sum | 157 +++++++++++ extension/awsmiddleware/middleware.go | 176 ++++++++++++ extension/awsmiddleware/middleware_test.go | 252 ++++++++++++++++++ extension/awsmiddleware/wrapper.go | 65 +++++ 12 files changed, 844 insertions(+), 12 deletions(-) create mode 100644 extension/awsmiddleware/Makefile create mode 100644 extension/awsmiddleware/README.md create mode 100644 extension/awsmiddleware/config.go create mode 100644 extension/awsmiddleware/config_test.go create mode 100644 extension/awsmiddleware/doc.go create mode 100644 extension/awsmiddleware/go.mod create mode 100644 extension/awsmiddleware/go.sum create mode 100644 extension/awsmiddleware/middleware.go create mode 100644 extension/awsmiddleware/middleware_test.go create mode 100644 extension/awsmiddleware/wrapper.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c2d1b3825ec6..41a6c72f218d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -89,6 +89,7 @@ exporter/tencentcloudlogserviceexporter/ @open-telemetry/collect exporter/zipkinexporter/ @open-telemetry/collector-contrib-approvers @MovieStoreGuy @astencel-sumo @crobert-1 extension/asapauthextension/ @open-telemetry/collector-contrib-approvers @jamesmoessis @MovieStoreGuy +extension/awsmiddleware @open-telemetry/collector-contrib-approvers @jefchien extension/awsproxy/ @open-telemetry/collector-contrib-approvers @Aneurysm9 @mxiamxia extension/basicauthextension/ @open-telemetry/collector-contrib-approvers @jpkrohling @svrakitin @frzifus extension/bearertokenauthextension/ @open-telemetry/collector-contrib-approvers @jpkrohling @pavankrish123 @frzifus diff --git a/exporter/awsemfexporter/metric_translator_test.go b/exporter/awsemfexporter/metric_translator_test.go index 5e5b7b3997be..3d32ad9caa83 100644 --- a/exporter/awsemfexporter/metric_translator_test.go +++ b/exporter/awsemfexporter/metric_translator_test.go @@ -1456,22 +1456,22 @@ func TestGroupedMetricToCWMeasurementsWithFilters(t *testing.T) { MetricNameSelectors: []string{"metric(1|3)"}, }, }, []cWMeasurement{ - { - Namespace: namespace, - Dimensions: [][]string{{}}, - Metrics: []map[string]string{ - { - "Name": "metric1", - "Unit": "Count", - }, - { - "Name": "metric3", - "Unit": "Seconds", + { + Namespace: namespace, + Dimensions: [][]string{{}}, + Metrics: []map[string]string{ + { + "Name": "metric1", + "Unit": "Count", + }, + { + "Name": "metric3", + "Unit": "Seconds", + }, }, }, }, }, - }, { "label matchers", []*MetricDeclaration{ diff --git a/extension/awsmiddleware/Makefile b/extension/awsmiddleware/Makefile new file mode 100644 index 000000000000..ded7a36092dc --- /dev/null +++ b/extension/awsmiddleware/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common diff --git a/extension/awsmiddleware/README.md b/extension/awsmiddleware/README.md new file mode 100644 index 000000000000..67885f503174 --- /dev/null +++ b/extension/awsmiddleware/README.md @@ -0,0 +1,29 @@ +# AWS Middleware + +An AWS middleware extension provides request and/or response handlers that can be configured on AWS SDK v1/v2 clients. +Other components can configure their AWS SDK clients using the `awsmiddleware.ConfigureSDKv1` and `awsmiddleware.ConfigureSDKv2` functions. + +The `awsmiddleware.Extension` interface extends `component.Extension` by adding the following methods: +``` +RequestHandlers() []RequestHandler +ResponseHandlers() []ResponseHandler +``` + +The `awsmiddleware.RequestHandler` interface contains the following methods: +``` +ID() string +Position() HandlerPosition +HandleRequest(r *http.Request) +``` + +The `awsmiddleware.ResponseHandler` interface contains the following methods: +``` +ID() string +Position() HandlerPosition +HandleResponse(r *http.Response) +``` + +- `ID` uniquely identifies a handler. Middleware will fail if there is clashing +- `Position` determines whether the handler is appended to the front or back of the existing list. Insertion is done +in the order of the handlers provided. +- `HandleRequest/Response` provides a hook to handle the request/response before and after they've been sent. \ No newline at end of file diff --git a/extension/awsmiddleware/config.go b/extension/awsmiddleware/config.go new file mode 100644 index 000000000000..f221a347dd35 --- /dev/null +++ b/extension/awsmiddleware/config.go @@ -0,0 +1,27 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package awsmiddleware // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/awsmiddleware" + +import ( + "fmt" + + "go.opentelemetry.io/collector/component" +) + +// Config defines the configuration for an AWS Middleware extension. +type Config struct { + // MiddlewareID is the ID of the Middleware extension. + MiddlewareID component.ID `mapstructure:"middleware"` +} + +// GetMiddleware retrieves the extension implementing Middleware based on the MiddlewareID. +func (c Config) GetMiddleware(extensions map[component.ID]component.Component) (Middleware, error) { + if ext, found := extensions[c.MiddlewareID]; found { + if mw, ok := ext.(Middleware); ok { + return mw, nil + } + return nil, errNotMiddleware + } + return nil, fmt.Errorf("failed to resolve AWS client handler %q: %w", c.MiddlewareID, errNotFound) +} diff --git a/extension/awsmiddleware/config_test.go b/extension/awsmiddleware/config_test.go new file mode 100644 index 000000000000..cb317acd6b46 --- /dev/null +++ b/extension/awsmiddleware/config_test.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package awsmiddleware + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension/extensiontest" +) + +type testMiddlewareExtension struct { + component.StartFunc + component.ShutdownFunc + requestHandlers []RequestHandler + responseHandlers []ResponseHandler +} + +var _ Extension = (*testMiddlewareExtension)(nil) + +func (t *testMiddlewareExtension) RequestHandlers() []RequestHandler { + return t.requestHandlers +} + +func (t *testMiddlewareExtension) ResponseHandlers() []ResponseHandler { + return t.responseHandlers +} + +func TestGetMiddleware(t *testing.T) { + id := component.NewID("test") + cfg := &Config{MiddlewareID: id} + nopExtension, err := extensiontest.NewNopBuilder().Create(context.Background(), extensiontest.NewNopCreateSettings()) + require.Error(t, err) + testCases := map[string]struct { + extensions map[component.ID]component.Component + wantErr error + }{ + "WithNoExtensions": { + extensions: map[component.ID]component.Component{}, + wantErr: errNotFound, + }, + "WithNonMiddlewareExtension": { + extensions: map[component.ID]component.Component{id: nopExtension}, + wantErr: errNotMiddleware, + }, + "WithMiddlewareExtension": { + extensions: map[component.ID]component.Component{id: &testMiddlewareExtension{}}, + }, + } + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + got, err := cfg.GetMiddleware(testCase.extensions) + if testCase.wantErr != nil { + assert.Error(t, err) + assert.ErrorIs(t, err, testCase.wantErr) + assert.Nil(t, got) + } else { + assert.NoError(t, err) + assert.NotNil(t, got) + } + }) + } +} diff --git a/extension/awsmiddleware/doc.go b/extension/awsmiddleware/doc.go new file mode 100644 index 000000000000..dfc6a74dc27e --- /dev/null +++ b/extension/awsmiddleware/doc.go @@ -0,0 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package awsmiddleware defines an extension interface providing request and response handlers that can be +// configured on AWS SDK clients. +package awsmiddleware // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/awsmiddleware" diff --git a/extension/awsmiddleware/go.mod b/extension/awsmiddleware/go.mod new file mode 100644 index 000000000000..2106c0e24556 --- /dev/null +++ b/extension/awsmiddleware/go.mod @@ -0,0 +1,51 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/extension/awsmiddleware + +go 1.20 + +require ( + github.com/aws/aws-sdk-go v1.45.24 + github.com/aws/aws-sdk-go-v2 v1.21.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0 + github.com/aws/smithy-go v1.15.0 + github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/collector/component v0.87.0 + go.opentelemetry.io/collector/extension v0.87.0 +) + +require ( + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.42 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.0.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.87.0 // indirect + go.opentelemetry.io/collector/confmap v0.87.0 // indirect + go.opentelemetry.io/collector/featuregate v1.0.0-rcv0016 // indirect + go.opentelemetry.io/collector/pdata v1.0.0-rcv0016 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/extension/awsmiddleware/go.sum b/extension/awsmiddleware/go.sum new file mode 100644 index 000000000000..8daaa48f656f --- /dev/null +++ b/extension/awsmiddleware/go.sum @@ -0,0 +1,157 @@ +github.com/aws/aws-sdk-go v1.45.24 h1:TZx/CizkmCQn8Rtsb11iLYutEQVGK5PK9wAhwouELBo= +github.com/aws/aws-sdk-go v1.45.24/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= +github.com/aws/aws-sdk-go-v2 v1.21.1 h1:wjHYshtPpYOZm+/mu3NhVgRRc0baM6LJZOmxPZ5Cwzs= +github.com/aws/aws-sdk-go-v2 v1.21.1/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 h1:OPLEkmhXf6xFPiz0bLeDArZIDx1NNS4oJyG4nv3Gct0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13/go.mod h1:gpAbvyDGQFozTEmlTFO8XcQKHzubdq0LzRyJpG6MiXM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.42 h1:817VqVe6wvwE46xXy6YF5RywvjOX6U2zRQQ6IbQFK0s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.42/go.mod h1:oDfgXoBBmj+kXnqxDDnIDnC56QBosglKp8ftRCTxR+0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.36 h1:7ZApaXzWbo8slc+W5TynuUlB4z66g44h7uqa3/d/BsY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.36/go.mod h1:rwr4WnmFi3RJO0M4dxbJtgi9BPLMpVBMX1nUte5ha9U= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 h1:6lJvvkQ9HmbHZ4h/IEwclwv2mrTW8Uq1SOB/kXy0mfw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4/go.mod h1:1PrKYwxTM+zjpw9Y41KFtoJCQrJ34Z47Y4VgVbfndjo= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 h1:m0QTSI6pZYJTk5WSKx3fm5cNW/DCicVzULBgU/6IyD0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14/go.mod h1:dDilntgHy9WnHXsh7dDtUPgHKEfTJIBUTHM8OWm0f/0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 h1:eev2yZX7esGRjqRbnVk1UxMLw4CyVZDpZXRCcy75oQk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36/go.mod h1:lGnOkH9NJATw0XEPcAknFBj3zzNTEGRHtSw+CwC1YTg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.36 h1:YXlm7LxwNlauqb2OrinWlcvtsflTzP8GaMvYfQBhoT4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.36/go.mod h1:ou9ffqJ9hKOVZmjlC6kQ6oROAyG1M4yBKzR+9BKbDwk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 h1:v0jkRigbSD6uOdwcaUQmgEwG1BkPfAPDqaeNt/29ghg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4/go.mod h1:LhTyt8J04LL+9cIt7pYJ5lbS/U98ZmXovLOR/4LUsk8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0 h1:wl5dxN1NONhTDQD9uaEvNsDRX29cBmGED/nl0jkWlt4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0/go.mod h1:rDGMZA7f4pbmTtPOk5v5UM2lmX6UAbRnMDJeDvnH7AM= +github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8= +github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= +github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 h1:BpfhmLKZf+SjVanKKhCgf3bg+511DmU9eDQTen7LLbY= +github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/collector/component v0.87.0 h1:Q+lwM5WAa2x4a5lgyaF6SjFBpIij5gyjsoiv9KFG36A= +go.opentelemetry.io/collector/component v0.87.0/go.mod h1:LsfDQRkwJRHOSHNnM1/pdi/6EQNj41WpIxpZRqSdI0E= +go.opentelemetry.io/collector/config/configtelemetry v0.87.0 h1:xUqayM9b41OvXkjU3p8RkUr8hUrCjfDUmO+oKhRNSwc= +go.opentelemetry.io/collector/config/configtelemetry v0.87.0/go.mod h1:+LAXM5WFMW/UbTlAuSs6L/W72WC+q8TBJt/6z39FPOU= +go.opentelemetry.io/collector/confmap v0.87.0 h1:LFnyDKIOMtlJm5EsdcFN2t0rcU/QLbS9QEs/awM2HOA= +go.opentelemetry.io/collector/confmap v0.87.0/go.mod h1:inqYRP70+bMrUwGGnuhcWyyufxyU3VQT6rl3/EX0f+g= +go.opentelemetry.io/collector/extension v0.87.0 h1:EMIaEequ5rjWzoid6vNImjQGVMfzbME+8JSa5XACYKs= +go.opentelemetry.io/collector/extension v0.87.0/go.mod h1:D3srNZC99QVTAdLNUVuqfmmgJge4sQHDrnt5XWscvxI= +go.opentelemetry.io/collector/featuregate v1.0.0-rcv0016 h1:/6N9990tbjotvXgrXpV5AbaFiyxTdFEXDypGBHVDSQM= +go.opentelemetry.io/collector/featuregate v1.0.0-rcv0016/go.mod h1:fLmJMf1AoHttkF8p5oJAc4o5ZpHu8yO5XYJ7gbLCLzo= +go.opentelemetry.io/collector/pdata v1.0.0-rcv0016 h1:qCPXSQCoD3qeWFb1RuIks8fw9Atxpk78bmtVdi15KhE= +go.opentelemetry.io/collector/pdata v1.0.0-rcv0016/go.mod h1:OdN0alYOlYhHXu6BDlGehrZWgtBuiDsz/rlNeJeXiNg= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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/extension/awsmiddleware/middleware.go b/extension/awsmiddleware/middleware.go new file mode 100644 index 000000000000..82e42e1d4b28 --- /dev/null +++ b/extension/awsmiddleware/middleware.go @@ -0,0 +1,176 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package awsmiddleware // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/awsmiddleware" + +import ( + "encoding" + "errors" + "fmt" + "net/http" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/smithy-go/middleware" + "go.opentelemetry.io/collector/extension" +) + +var ( + errNotFound = errors.New("middleware not found") + errNotMiddleware = errors.New("extension is not an AWS middleware") + errInvalidHandler = errors.New("invalid handler") + errUnsupportedPosition = errors.New("unsupported position") +) + +// HandlerPosition is the relative position of a handler used during insertion. +type HandlerPosition int + +var _ encoding.TextMarshaler = (*HandlerPosition)(nil) +var _ encoding.TextUnmarshaler = (*HandlerPosition)(nil) + +const ( + After HandlerPosition = iota + Before + + afterStr = "after" + beforeStr = "before" +) + +// String returns the string representation of the position. +// Returns an empty string if position is unsupported. +func (h HandlerPosition) String() string { + switch h { + case Before: + return beforeStr + case After: + return afterStr + default: + return "" + } +} + +// MarshalText converts the position into a byte slice. +// Returns an error if unsupported. +func (h HandlerPosition) MarshalText() (text []byte, err error) { + s := h.String() + if s == "" { + return nil, fmt.Errorf("%w: %[2]T(%[2]d)", errUnsupportedPosition, h) + } + return []byte(h.String()), nil +} + +// UnmarshalText converts the string into a position. Returns an error +// if unsupported. +func (h *HandlerPosition) UnmarshalText(text []byte) error { + switch s := string(text); s { + case afterStr: + *h = After + case beforeStr: + *h = Before + default: + return fmt.Errorf("%w: %s", errUnsupportedPosition, s) + } + return nil +} + +// handlerConfig is used to differentiate between handlers and determine +// relative positioning within their groups. +type handlerConfig interface { + // ID must be unique. It cannot clash with existing middleware. + ID() string + // Position to insert the handler. + Position() HandlerPosition +} + +// RequestHandler allows for custom processing of requests. +type RequestHandler interface { + handlerConfig + HandleRequest(r *http.Request) +} + +// ResponseHandler allows for custom processing of responses. +type ResponseHandler interface { + handlerConfig + HandleResponse(r *http.Response) +} + +// Middleware defines the request and response handlers to be configured +// on AWS Clients. +type Middleware interface { + RequestHandlers() []RequestHandler + ResponseHandlers() []ResponseHandler +} + +// Extension is an extension that implements Middleware. +type Extension interface { + extension.Extension + Middleware +} + +// ConfigureSDKv1 adds middleware to the AWS SDK v1. Request handlers are added to the +// Build handler list and response handlers are added to the Unmarshal handler list. +func ConfigureSDKv1(mw Middleware, handlers *request.Handlers) error { + var errs error + for _, handler := range mw.RequestHandlers() { + if err := appendHandler(&handlers.Build, namedRequestHandler(handler), handler.Position()); err != nil { + errs = errors.Join(errs, fmt.Errorf("%w (%q): %w", errInvalidHandler, handler.ID(), err)) + } + } + for _, handler := range mw.ResponseHandlers() { + if err := appendHandler(&handlers.Unmarshal, namedResponseHandler(handler), handler.Position()); err != nil { + errs = errors.Join(errs, fmt.Errorf("%w (%q): %w", errInvalidHandler, handler.ID(), err)) + } + } + return errs +} + +// ConfigureSDKv2 adds middleware to the AWS SDK v2. Request handlers are added to the +// Build step and response handlers are added to the Deserialize step. +func ConfigureSDKv2(mw Middleware, config *aws.Config) error { + var errs error + for _, handler := range mw.RequestHandlers() { + relativePosition, err := toRelativePosition(handler.Position()) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("%w (%q): %w", errInvalidHandler, handler.ID(), err)) + continue + } + config.APIOptions = append(config.APIOptions, withBuildOption(&requestMiddleware{RequestHandler: handler}, relativePosition)) + } + for _, handler := range mw.ResponseHandlers() { + relativePosition, err := toRelativePosition(handler.Position()) + if err != nil { + errs = errors.Join(errs, fmt.Errorf("%w (%q): %w", errInvalidHandler, handler.ID(), err)) + continue + } + config.APIOptions = append(config.APIOptions, withDeserializeOption(&responseMiddleware{ResponseHandler: handler}, relativePosition)) + } + return errs +} + +// addHandlerToList adds the handler to the list based on the position. +func appendHandler(handlerList *request.HandlerList, handler request.NamedHandler, position HandlerPosition) error { + relativePosition, err := toRelativePosition(position) + if err != nil { + return err + } + switch relativePosition { + case middleware.Before: + handlerList.PushFrontNamed(handler) + case middleware.After: + handlerList.PushBackNamed(handler) + } + return nil +} + +// toRelativePosition maps the HandlerPosition to a middleware.RelativePosition. It also validates that +// the HandlerPosition provided is supported and returns an errUnsupportedPosition if it isn't. +func toRelativePosition(position HandlerPosition) (middleware.RelativePosition, error) { + switch position { + case Before: + return middleware.Before, nil + case After: + return middleware.After, nil + default: + return -1, fmt.Errorf("%w: %s", errUnsupportedPosition, position) + } +} diff --git a/extension/awsmiddleware/middleware_test.go b/extension/awsmiddleware/middleware_test.go new file mode 100644 index 000000000000..9a21a306600b --- /dev/null +++ b/extension/awsmiddleware/middleware_test.go @@ -0,0 +1,252 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package awsmiddleware + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + awsv2 "github.com/aws/aws-sdk-go-v2/aws" + s3v2 "github.com/aws/aws-sdk-go-v2/service/s3" + awsv1 "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/awstesting" + s3v1 "github.com/aws/aws-sdk-go/service/s3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + testUserAgent = "user/agent" + testLatency = time.Millisecond +) + +type testHandler struct { + id string + position HandlerPosition + handleRequest func(r *http.Request) + handleResponse func(r *http.Response) + start time.Time + end time.Time +} + +var _ RequestHandler = (*testHandler)(nil) +var _ ResponseHandler = (*testHandler)(nil) + +func (t *testHandler) ID() string { + return t.id +} + +func (t *testHandler) Position() HandlerPosition { + return t.position +} + +func (t *testHandler) HandleRequest(r *http.Request) { + t.start = time.Now() + if t.handleRequest != nil { + t.handleRequest(r) + } +} + +func (t *testHandler) HandleResponse(r *http.Response) { + t.end = time.Now() + if t.handleResponse != nil { + t.handleResponse(r) + } +} + +func (t *testHandler) Latency() time.Duration { + return t.end.Sub(t.start) +} + +type recordOrder struct { + order []string +} + +func (ro *recordOrder) handle(id string) func(*http.Request) { + return func(*http.Request) { + ro.order = append(ro.order, id) + } +} + +func TestHandlerPosition(t *testing.T) { + testCases := []struct { + position HandlerPosition + str string + }{ + {position: After, str: "after"}, + {position: Before, str: "before"}, + } + for _, testCase := range testCases { + position := testCase.position + got, err := position.MarshalText() + assert.NoError(t, err) + assert.EqualValues(t, testCase.str, got) + assert.NoError(t, position.UnmarshalText(got)) + assert.Equal(t, position, testCase.position) + } +} + +func TestInvalidHandlerPosition(t *testing.T) { + position := HandlerPosition(-1) + got, err := position.MarshalText() + assert.Error(t, err) + assert.ErrorIs(t, err, errUnsupportedPosition) + assert.Nil(t, got) + err = position.UnmarshalText([]byte("HandlerPosition(-1)")) + assert.Error(t, err) + assert.ErrorIs(t, err, errUnsupportedPosition) +} + +func TestInvalidHandlers(t *testing.T) { + invalidHandler := &testHandler{id: "invalid handler", position: -1} + testExtension := &testMiddlewareExtension{ + requestHandlers: []RequestHandler{invalidHandler}, + responseHandlers: []ResponseHandler{invalidHandler}, + } + // v1 + client := awstesting.NewClient() + err := ConfigureSDKv1(testExtension, &client.Handlers) + assert.Error(t, err) + assert.True(t, errors.Is(err, errInvalidHandler)) + assert.True(t, errors.Is(err, errUnsupportedPosition)) + // v2 + err = ConfigureSDKv2(testExtension, &awsv2.Config{}) + assert.Error(t, err) + assert.True(t, errors.Is(err, errInvalidHandler)) + assert.True(t, errors.Is(err, errUnsupportedPosition)) +} + +func TestAppendOrder(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + testCases := map[string]struct { + requestHandlers []*testHandler + wantOrder []string + }{ + "WithBothBefore": { + requestHandlers: []*testHandler{ + {id: "1", position: Before}, + {id: "2", position: Before}, + }, + wantOrder: []string{"2", "1"}, + }, + "WithBothAfter": { + requestHandlers: []*testHandler{ + {id: "1", position: After}, + {id: "2", position: After}, + }, + wantOrder: []string{"1", "2"}, + }, + "WithBeforeAfter": { + requestHandlers: []*testHandler{ + {id: "1", position: Before}, + {id: "2", position: After}, + }, + wantOrder: []string{"1", "2"}, + }, + "WithAfterBefore": { + requestHandlers: []*testHandler{ + {id: "1", position: After}, + {id: "2", position: Before}, + }, + wantOrder: []string{"2", "1"}, + }, + } + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + middleware := &testMiddlewareExtension{} + recorder := &recordOrder{} + for _, handler := range testCase.requestHandlers { + handler.handleRequest = recorder.handle(handler.id) + middleware.requestHandlers = append(middleware.requestHandlers, handler) + } + // v1 + client := awstesting.NewClient(&awsv1.Config{ + Region: awsv1.String("mock-region"), + DisableSSL: awsv1.Bool(true), + Endpoint: awsv1.String(server.URL), + }) + assert.NoError(t, ConfigureSDKv1(middleware, &client.Handlers)) + s3v1Client := &s3v1.S3{Client: client} + _, err := s3v1Client.ListBuckets(&s3v1.ListBucketsInput{}) + require.NoError(t, err) + assert.Equal(t, testCase.wantOrder, recorder.order) + recorder.order = nil + // v2 + cfg := awsv2.Config{Region: "us-east-1"} + assert.NoError(t, ConfigureSDKv2(middleware, &cfg)) + s3v2Client := s3v2.NewFromConfig(cfg, func(options *s3v2.Options) { + options.BaseEndpoint = awsv2.String(server.URL) + }) + _, err = s3v2Client.ListBuckets(context.Background(), &s3v2.ListBucketsInput{}) + require.NoError(t, err) + assert.Equal(t, testCase.wantOrder, recorder.order) + }) + } +} + +func TestConfigureSDKv1(t *testing.T) { + middleware, recorder, server := setup(t) + defer server.Close() + client := awstesting.NewClient(&awsv1.Config{ + Region: awsv1.String("mock-region"), + DisableSSL: awsv1.Bool(true), + Endpoint: awsv1.String(server.URL), + }) + require.Equal(t, 3, client.Handlers.Build.Len()) + require.Equal(t, 0, client.Handlers.Unmarshal.Len()) + assert.NoError(t, ConfigureSDKv1(middleware, &client.Handlers)) + assert.Equal(t, 5, client.Handlers.Build.Len()) + assert.Equal(t, 1, client.Handlers.Unmarshal.Len()) + s3Client := &s3v1.S3{Client: client} + output, err := s3Client.ListBuckets(&s3v1.ListBucketsInput{}) + require.NoError(t, err) + assert.NotNil(t, output) + assert.GreaterOrEqual(t, recorder.Latency(), testLatency) +} + +func TestConfigureSDKv2(t *testing.T) { + middleware, recorder, server := setup(t) + defer server.Close() + cfg := awsv2.Config{Region: "us-east-1"} + assert.NoError(t, ConfigureSDKv2(middleware, &cfg)) + s3Client := s3v2.NewFromConfig(cfg, func(options *s3v2.Options) { + options.BaseEndpoint = awsv2.String(server.URL) + }) + output, err := s3Client.ListBuckets(context.Background(), &s3v2.ListBucketsInput{}) + require.NoError(t, err) + assert.NotNil(t, output) + assert.GreaterOrEqual(t, recorder.Latency(), testLatency) +} + +func setup(t *testing.T) (Middleware, *testHandler, *httptest.Server) { + t.Helper() + recorder := &testHandler{id: "LatencyTest", position: After} + middleware := &testMiddlewareExtension{ + requestHandlers: []RequestHandler{ + &testHandler{ + id: "UserAgentTest", + position: Before, + handleRequest: func(r *http.Request) { + r.Header.Set("User-Agent", testUserAgent) + }, + }, + recorder, + }, + responseHandlers: []ResponseHandler{recorder}, + } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotUserAgent := r.Header.Get("User-Agent") + assert.Contains(t, gotUserAgent, testUserAgent) + time.Sleep(testLatency) + w.WriteHeader(http.StatusOK) + })) + return middleware, recorder, server +} diff --git a/extension/awsmiddleware/wrapper.go b/extension/awsmiddleware/wrapper.go new file mode 100644 index 000000000000..77aa87fd9b83 --- /dev/null +++ b/extension/awsmiddleware/wrapper.go @@ -0,0 +1,65 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package awsmiddleware // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/awsmiddleware" + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/smithy-go/middleware" + "github.com/aws/smithy-go/transport/http" +) + +func namedRequestHandler(handler RequestHandler) request.NamedHandler { + return request.NamedHandler{Name: handler.ID(), Fn: func(r *request.Request) { + handler.HandleRequest(r.HTTPRequest) + }} +} + +func namedResponseHandler(handler ResponseHandler) request.NamedHandler { + return request.NamedHandler{Name: handler.ID(), Fn: func(r *request.Request) { + handler.HandleResponse(r.HTTPResponse) + }} +} + +type requestMiddleware struct { + RequestHandler +} + +var _ middleware.BuildMiddleware = (*requestMiddleware)(nil) + +func (r requestMiddleware) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (out middleware.BuildOutput, metadata middleware.Metadata, err error) { + req, ok := in.Request.(*http.Request) + if ok { + r.HandleRequest(req.Request) + } + return next.HandleBuild(ctx, in) +} + +func withBuildOption(rmw *requestMiddleware, position middleware.RelativePosition) func(stack *middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Build.Add(rmw, position) + } +} + +type responseMiddleware struct { + ResponseHandler +} + +var _ middleware.DeserializeMiddleware = (*responseMiddleware)(nil) + +func (r responseMiddleware) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (out middleware.DeserializeOutput, metadata middleware.Metadata, err error) { + out, metadata, err = next.HandleDeserialize(ctx, in) + res, ok := out.RawResponse.(*http.Response) + if ok { + r.HandleResponse(res.Response) + } + return +} + +func withDeserializeOption(rmw *responseMiddleware, position middleware.RelativePosition) func(stack *middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Deserialize.Add(rmw, position) + } +}