Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Do Not Merge] POC optional hook #11409

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions collector.yaml
Original file line number Diff line number Diff line change
@@ -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]
44 changes: 25 additions & 19 deletions config/confighttp/confighttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions config/confighttp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.30.0
Expand All @@ -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.1.0 // 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
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions config/confighttp/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion confmap/confmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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.
Expand Down
48 changes: 48 additions & 0 deletions confmap/optional/optional.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package optional

import (
"fmt"
"reflect"

"go.opentelemetry.io/collector/confmap"
)

var _ confmap.Unmarshaler = (*Optional[any])(nil)
var _ confmap.PrimitiveUnmarshaler = (*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
}