diff --git a/collector.yaml b/collector.yaml new file mode 100644 index 00000000000..b38aa29f275 --- /dev/null +++ b/collector.yaml @@ -0,0 +1,22 @@ +receivers: + otlp: + protocols: + http: + endpoint: localhost:4317 + cors: + allowed_origins: + - http://test.com + allowed_headers: + - Example-Header + max_age: 7200 + +exporters: + otlphttp: + endpoint: localhost:4318 + max_idle_conns: 567 + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [otlphttp] diff --git a/config/confighttp/confighttp.go b/config/confighttp/confighttp.go index 3f70b64bf5e..20124d8bff6 100644 --- a/config/confighttp/confighttp.go +++ b/config/confighttp/confighttp.go @@ -28,6 +28,7 @@ import ( "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/config/configtls" "go.opentelemetry.io/collector/config/internal" + "go.opentelemetry.io/collector/confmap/optional" "go.opentelemetry.io/collector/extension/auth" ) @@ -71,11 +72,11 @@ type ClientConfig struct { // MaxIdleConns is used to set a limit to the maximum idle HTTP connections the client can keep open. // By default, it is set to 100. - MaxIdleConns *int `mapstructure:"max_idle_conns"` + MaxIdleConns optional.Optional[int] `mapstructure:"max_idle_conns"` // MaxIdleConnsPerHost is used to set a limit to the maximum idle HTTP connections the host can keep open. // By default, it is set to [http.DefaultTransport.MaxIdleConnsPerHost]. - MaxIdleConnsPerHost *int `mapstructure:"max_idle_conns_per_host"` + MaxIdleConnsPerHost optional.Optional[int] `mapstructure:"max_idle_conns_per_host"` // MaxConnsPerHost limits the total number of connections per host, including connections in the dialing, // active, and idle states. @@ -125,8 +126,8 @@ func NewDefaultClientConfig() ClientConfig { ReadBufferSize: defaultTransport.ReadBufferSize, WriteBufferSize: defaultTransport.WriteBufferSize, Headers: map[string]configopaque.String{}, - MaxIdleConns: &defaultTransport.MaxIdleConns, - MaxIdleConnsPerHost: &defaultTransport.MaxIdleConnsPerHost, + MaxIdleConns: optional.WithDefault(12345), + MaxIdleConnsPerHost: optional.Optional[int]{}, MaxConnsPerHost: &defaultTransport.MaxConnsPerHost, IdleConnTimeout: &defaultTransport.IdleConnTimeout, } @@ -149,12 +150,14 @@ func (hcs *ClientConfig) ToClient(ctx context.Context, host component.Host, sett transport.WriteBufferSize = hcs.WriteBufferSize } - if hcs.MaxIdleConns != nil { - transport.MaxIdleConns = *hcs.MaxIdleConns + fmt.Printf("MaxIdleConns: %+v\n", hcs.MaxIdleConns) + if hcs.MaxIdleConns.HasValue() { + transport.MaxIdleConns = hcs.MaxIdleConns.Value() } - if hcs.MaxIdleConnsPerHost != nil { - transport.MaxIdleConnsPerHost = *hcs.MaxIdleConnsPerHost + fmt.Printf("MaxIdleConnsPerHost: %+v\n", hcs.MaxIdleConnsPerHost) + if hcs.MaxIdleConnsPerHost.HasValue() { + transport.MaxIdleConnsPerHost = hcs.MaxIdleConnsPerHost.Value() } if hcs.MaxConnsPerHost != nil { @@ -279,10 +282,10 @@ type ServerConfig struct { TLSSetting *configtls.ServerConfig `mapstructure:"tls"` // CORS configures the server for HTTP cross-origin resource sharing (CORS). - CORS *CORSConfig `mapstructure:"cors"` + CORS optional.Optional[CORSConfig] `mapstructure:"cors"` // Auth for this receiver - Auth *AuthConfig `mapstructure:"auth"` + Auth optional.Optional[AuthConfig] `mapstructure:"auth"` // MaxRequestBodySize sets the maximum request body size in bytes. Default: 20MiB. MaxRequestBodySize int64 `mapstructure:"max_request_body_size"` @@ -336,7 +339,7 @@ func NewDefaultServerConfig() ServerConfig { return ServerConfig{ ResponseHeaders: map[string]configopaque.String{}, TLSSetting: &tlsDefaultServerConfig, - CORS: NewDefaultCORSConfig(), + CORS: optional.WithDefault(*NewDefaultCORSConfig()), WriteTimeout: 30 * time.Second, ReadHeaderTimeout: 1 * time.Minute, IdleTimeout: 1 * time.Minute, @@ -433,26 +436,29 @@ func (hss *ServerConfig) ToServer(_ context.Context, host component.Host, settin handler = maxRequestBodySizeInterceptor(handler, hss.MaxRequestBodySize) } - if hss.Auth != nil { - server, err := hss.Auth.GetServerAuthenticator(context.Background(), host.GetExtensions()) + fmt.Printf("AUTH: %+v\n", hss.Auth) + if hss.Auth.HasValue() { + server, err := hss.Auth.Value().GetServerAuthenticator(context.Background(), host.GetExtensions()) if err != nil { return nil, err } - handler = authInterceptor(handler, server, hss.Auth.RequestParameters) + handler = authInterceptor(handler, server, hss.Auth.Value().RequestParameters) } - if hss.CORS != nil && len(hss.CORS.AllowedOrigins) > 0 { + fmt.Printf("CORS: %+v\n", hss.CORS) + if hss.CORS.HasValue() && len(hss.CORS.Value().AllowedOrigins) > 0 { co := cors.Options{ - AllowedOrigins: hss.CORS.AllowedOrigins, + AllowedOrigins: hss.CORS.Value().AllowedOrigins, AllowCredentials: true, - AllowedHeaders: hss.CORS.AllowedHeaders, - MaxAge: hss.CORS.MaxAge, + AllowedHeaders: hss.CORS.Value().AllowedHeaders, + MaxAge: hss.CORS.Value().MaxAge, } handler = cors.New(co).Handler(handler) } - if hss.CORS != nil && len(hss.CORS.AllowedOrigins) == 0 && len(hss.CORS.AllowedHeaders) > 0 { + if hss.CORS.HasValue() && len(hss.CORS.Value().AllowedOrigins) == 0 && len(hss.CORS.Value().AllowedHeaders) > 0 { settings.Logger.Warn("The CORS configuration specifies allowed headers but no allowed origins, and is therefore ignored.") + } if hss.ResponseHeaders != nil { diff --git a/config/confighttp/go.mod b/config/confighttp/go.mod index c90e12b9958..60046f1b284 100644 --- a/config/confighttp/go.mod +++ b/config/confighttp/go.mod @@ -16,6 +16,7 @@ require ( go.opentelemetry.io/collector/config/configtelemetry v0.111.0 go.opentelemetry.io/collector/config/configtls v1.17.0 go.opentelemetry.io/collector/config/internal v0.111.0 + go.opentelemetry.io/collector/confmap v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/extension/auth v0.111.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 go.opentelemetry.io/otel v1.31.0 @@ -31,8 +32,14 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.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.1.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/collector/extension v0.111.0 // indirect go.opentelemetry.io/collector/pdata v1.17.0 // indirect @@ -50,6 +57,8 @@ require ( replace go.opentelemetry.io/collector/config/configauth => ../configauth +replace go.opentelemetry.io/collector/confmap => ../../confmap + replace go.opentelemetry.io/collector/config/configcompression => ../configcompression replace go.opentelemetry.io/collector/config/configopaque => ../configopaque diff --git a/config/confighttp/go.sum b/config/confighttp/go.sum index dbf30aec059..4789441c23a 100644 --- a/config/confighttp/go.sum +++ b/config/confighttp/go.sum @@ -9,6 +9,8 @@ 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/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -23,10 +25,20 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +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.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= +github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/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/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= diff --git a/confmap/confmap.go b/confmap/confmap.go index 0a8a3461159..42be2431013 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -431,11 +431,16 @@ func unmarshalerEmbeddedStructsHookFunc() mapstructure.DecodeHookFuncValue { } } +type primitiveUnmarshaler interface { + UnmarshalPrimitive(val any) error +} + // Provides a mechanism for individual structs to define their own unmarshal logic, // by implementing the Unmarshaler interface, unless skipTopLevelUnmarshaler is // true and the struct matches the top level object being unmarshaled. func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure.DecodeHookFuncValue { return func(from reflect.Value, to reflect.Value) (any, error) { + if !to.CanAddr() { return from.Interface(), nil } @@ -453,7 +458,14 @@ func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure. } if _, ok = from.Interface().(map[string]any); !ok { - return from.Interface(), nil + unmarshaler, ok := toPtr.(primitiveUnmarshaler) + if !ok { + return from.Interface(), nil + } + if err := unmarshaler.UnmarshalPrimitive(from.Interface()); err != nil { + return nil, err + } + return unmarshaler, nil } // Use the current object if not nil (to preserve other configs in the object), otherwise zero initialize. diff --git a/confmap/optional/optional.go b/confmap/optional/optional.go new file mode 100644 index 00000000000..96dddc81bc4 --- /dev/null +++ b/confmap/optional/optional.go @@ -0,0 +1,47 @@ +package optional + +import ( + "fmt" + "reflect" + + "go.opentelemetry.io/collector/confmap" +) + +var _ confmap.Unmarshaler = (*Optional[any])(nil) + +type Optional[T any] struct { + value T + hasValue bool +} + +func (o *Optional[T]) HasValue() bool { + return o.hasValue +} + +func (o *Optional[T]) Value() T { + return o.value +} + +func WithDefault[T any](val T) (o Optional[T]) { + o.hasValue = true + o.value = val + return +} + +func (o *Optional[T]) Unmarshal(conf *confmap.Conf) error { + o.hasValue = true + if err := conf.Unmarshal(&o.value); err != nil { + return err + } + return nil +} + +func (o *Optional[T]) UnmarshalPrimitive(val any) error { + o.hasValue = true + valT, ok := val.(T) + if !ok { + return fmt.Errorf("cannot cast val to %v", reflect.TypeOf(o.value)) + } + o.value = valT + return nil +}