Skip to content

Commit

Permalink
Add new factory unmarshaler to all components, deprecate old way
Browse files Browse the repository at this point in the history
Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>
  • Loading branch information
bogdandrutu committed Aug 2, 2020
1 parent f954863 commit 097d59d
Show file tree
Hide file tree
Showing 18 changed files with 202 additions and 116 deletions.
21 changes: 21 additions & 0 deletions component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package component
import (
"context"

"github.com/spf13/viper"

"go.opentelemetry.io/collector/config/configmodels"
)

Expand Down Expand Up @@ -86,3 +88,22 @@ type Factory interface {
// Type gets the type of the component created by this factory.
Type() configmodels.Type
}

// FactoryUnmarshaler interface is an optional interface that if implemented by a factory,
// the config loader will use to unmarshal the config.
type FactoryUnmarshaler interface {
// Unmarshal is a function that un-marshals a viper data into a config struct in a custom way.
// componentViperSection *viper.Viper
// The config for this specific component. May be nil or empty if no config available.
// intoCfg interface{}
// An empty interface wrapping a pointer to the config struct to unmarshal into.
Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error
}

// CustomUnmarshaler is a function that un-marshals a viper data into a config struct
// in a custom way.
// componentViperSection *viper.Viper
// The config for this specific component. May be nil or empty if no config available.
// intoCfg interface{}
// An empty interface wrapping a pointer to the config struct to unmarshal into.
type CustomUnmarshaler func(componentViperSection *viper.Viper, intoCfg interface{}) error
57 changes: 22 additions & 35 deletions component/componenttest/example_factories.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"fmt"

"github.com/spf13/viper"
"go.uber.org/zap"

"go.opentelemetry.io/collector/component"
Expand Down Expand Up @@ -56,11 +57,6 @@ func (f *ExampleReceiverFactory) Type() configmodels.Type {
return "examplereceiver"
}

// CustomUnmarshaler returns nil because we don't need custom unmarshaling for this factory.
func (f *ExampleReceiverFactory) CustomUnmarshaler() component.CustomUnmarshaler {
return nil
}

// CreateDefaultConfig creates the default configuration for the Receiver.
func (f *ExampleReceiverFactory) CreateDefaultConfig() configmodels.Receiver {
return &ExampleReceiver{
Expand All @@ -77,6 +73,11 @@ func (f *ExampleReceiverFactory) CreateDefaultConfig() configmodels.Receiver {
}
}

// CustomUnmarshaler implements the deprecated way to provide custom unmarshalers.
func (f *ExampleReceiverFactory) CustomUnmarshaler() component.CustomUnmarshaler {
return nil
}

// CreateTraceReceiver creates a trace receiver based on this config.
func (f *ExampleReceiverFactory) CreateTraceReceiver(
_ context.Context,
Expand Down Expand Up @@ -163,31 +164,8 @@ var exampleReceivers = map[configmodels.Receiver]*ExampleReceiverProducer{}
// MultiProtoReceiver is for testing purposes. We are defining an example multi protocol
// config and factory for "multireceiver" receiver type.
type MultiProtoReceiver struct {
TypeVal configmodels.Type `mapstructure:"-"`
NameVal string `mapstructure:"-"`
Protocols map[string]MultiProtoReceiverOneCfg `mapstructure:"protocols"`
}

var _ configmodels.Receiver = (*MultiProtoReceiver)(nil)

// Name gets the exporter name.
func (rs *MultiProtoReceiver) Name() string {
return rs.NameVal
}

// SetName sets the receiver name.
func (rs *MultiProtoReceiver) SetName(name string) {
rs.NameVal = name
}

// Type sets the receiver type.
func (rs *MultiProtoReceiver) Type() configmodels.Type {
return rs.TypeVal
}

// SetType sets the receiver type.
func (rs *MultiProtoReceiver) SetType(typeStr configmodels.Type) {
rs.TypeVal = typeStr
configmodels.ReceiverSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
Protocols map[string]MultiProtoReceiverOneCfg `mapstructure:"protocols"`
}

// MultiProtoReceiverOneCfg is multi proto receiver config.
Expand All @@ -205,16 +183,18 @@ func (f *MultiProtoReceiverFactory) Type() configmodels.Type {
return "multireceiver"
}

// CustomUnmarshaler returns nil because we don't need custom unmarshaling for this factory.
func (f *MultiProtoReceiverFactory) CustomUnmarshaler() component.CustomUnmarshaler {
return nil
// Unmarshal implements the FactoryUnmarshaler interface.
func (f *MultiProtoReceiverFactory) Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error {
return componentViperSection.UnmarshalExact(intoCfg)
}

// CreateDefaultConfig creates the default configuration for the Receiver.
func (f *MultiProtoReceiverFactory) CreateDefaultConfig() configmodels.Receiver {
return &MultiProtoReceiver{
TypeVal: f.Type(),
NameVal: string(f.Type()),
ReceiverSettings: configmodels.ReceiverSettings{
TypeVal: f.Type(),
NameVal: string(f.Type()),
},
Protocols: map[string]MultiProtoReceiverOneCfg{
"http": {
Endpoint: "example.com:8888",
Expand Down Expand Up @@ -277,6 +257,13 @@ func (f *ExampleExporterFactory) CreateDefaultConfig() configmodels.Exporter {
}
}

// CustomUnmarshaler implements the deprecated way to provide custom unmarshalers.
func (f *ExampleExporterFactory) CustomUnmarshaler() component.CustomUnmarshaler {
return func(componentViperSection *viper.Viper, intoCfg interface{}) error {
return componentViperSection.UnmarshalExact(intoCfg)
}
}

// CreateTraceExporter creates a trace exporter based on this config.
func (f *ExampleExporterFactory) CreateTraceExporter(_ *zap.Logger, _ configmodels.Exporter) (component.TraceExporterOld, error) {
return &ExampleExporterConsumer{}, nil
Expand Down
14 changes: 0 additions & 14 deletions component/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package component
import (
"context"

"github.com/spf13/viper"
"go.uber.org/zap"

"go.opentelemetry.io/collector/config/configmodels"
Expand Down Expand Up @@ -68,21 +67,8 @@ type ReceiverFactoryBase interface {
// 'configcheck.ValidateConfig'. It is recommended to have such check in the
// tests of any implementation of the Factory interface.
CreateDefaultConfig() configmodels.Receiver

// CustomUnmarshaler returns a custom unmarshaler for the configuration or nil if
// there is no need for custom unmarshaling. This is typically used if viper.UnmarshalExact()
// is not sufficient to unmarshal correctly.
CustomUnmarshaler() CustomUnmarshaler
}

// CustomUnmarshaler is a function that un-marshals a viper data into a config struct
// in a custom way.
// componentViperSection *viper.Viper
// The config for this specific component. May be nil or empty if no config available.
// intoCfg interface{}
// An empty interface wrapping a pointer to the config struct to unmarshal into.
type CustomUnmarshaler func(componentViperSection *viper.Viper, intoCfg interface{}) error

// ReceiverFactoryOld can create TraceReceiver and MetricsReceiver.
type ReceiverFactoryOld interface {
ReceiverFactoryBase
Expand Down
5 changes: 0 additions & 5 deletions component/receiver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ func (f *TestReceiverFactory) Type() configmodels.Type {
return f.name
}

// CustomUnmarshaler returns nil because we don't need custom unmarshaling for this factory.
func (f *TestReceiverFactory) CustomUnmarshaler() CustomUnmarshaler {
return nil
}

// CreateDefaultConfig creates the default configuration for the Receiver.
func (f *TestReceiverFactory) CreateDefaultConfig() configmodels.Receiver {
return nil
Expand Down
40 changes: 27 additions & 13 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ const (
pipelinesKeyName = "pipelines"
)

type DeprecatedUnmarshaler interface {
// CustomUnmarshaler returns a custom unmarshaler for the configuration or nil if
// there is no need for custom unmarshaling. This is typically used if viper.UnmarshalExact()
// is not sufficient to unmarshal correctly.
CustomUnmarshaler() component.CustomUnmarshaler
}

// typeAndNameSeparator is the separator that is used between type and name in type/name composite keys.
const typeAndNameSeparator = "/"

Expand Down Expand Up @@ -259,7 +266,7 @@ func loadExtensions(v *viper.Viper, factories map[configmodels.Type]component.Ex

// Now that the default config struct is created we can Unmarshal into it
// and it will apply user-defined config on top of the default.
if err := componentConfig.UnmarshalExact(extensionCfg); err != nil {
if err := unmarshal(factory, componentConfig, extensionCfg); err != nil {
return nil, errorUnmarshalError(extensionsKeyName, fullName, err)
}

Expand Down Expand Up @@ -309,16 +316,7 @@ func LoadReceiver(componentConfig *viper.Viper, typeStr configmodels.Type, fullN

// Now that the default config struct is created we can Unmarshal into it
// and it will apply user-defined config on top of the default.
customUnmarshaler := factory.CustomUnmarshaler()
var err error
if customUnmarshaler != nil {
// This configuration requires a custom unmarshaler, use it.
err = customUnmarshaler(componentConfig, receiverCfg)
} else {
err = componentConfig.UnmarshalExact(receiverCfg)
}

if err != nil {
if err := unmarshal(factory, componentConfig, receiverCfg); err != nil {
return nil, errorUnmarshalError(receiversKeyName, fullName, err)
}

Expand Down Expand Up @@ -401,7 +399,7 @@ func loadExporters(v *viper.Viper, factories map[configmodels.Type]component.Exp

// Now that the default config struct is created we can Unmarshal into it
// and it will apply user-defined config on top of the default.
if err := componentConfig.UnmarshalExact(exporterCfg); err != nil {
if err := unmarshal(factory, componentConfig, exporterCfg); err != nil {
return nil, errorUnmarshalError(exportersKeyName, fullName, err)
}

Expand Down Expand Up @@ -450,7 +448,7 @@ func loadProcessors(v *viper.Viper, factories map[configmodels.Type]component.Pr

// Now that the default config struct is created we can Unmarshal into it
// and it will apply user-defined config on top of the default.
if err := componentConfig.UnmarshalExact(processorCfg); err != nil {
if err := unmarshal(factory, componentConfig, processorCfg); err != nil {
return nil, errorUnmarshalError(processorsKeyName, fullName, err)
}

Expand Down Expand Up @@ -722,6 +720,22 @@ func expandEnv(s string) string {
})
}

func unmarshal(factory component.Factory, componentViperSection *viper.Viper, intoCfg interface{}) error {
customUnmarshaler := func(componentViperSection *viper.Viper, intoCfg interface{}) error {
return componentViperSection.UnmarshalExact(intoCfg)
}

if du, ok := factory.(DeprecatedUnmarshaler); ok && du.CustomUnmarshaler() != nil {
customUnmarshaler = du.CustomUnmarshaler()
}

if fu, ok := factory.(component.FactoryUnmarshaler); ok {
customUnmarshaler = fu.Unmarshal
}

return customUnmarshaler(componentViperSection, intoCfg)
}

// Copied from the Viper but changed to use the same delimiter.
// See https://github.com/spf13/viper/issues/871
func ViperSub(v *viper.Viper, key string) *viper.Viper {
Expand Down
12 changes: 8 additions & 4 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,10 @@ func TestDecodeConfig_MultiProto(t *testing.T) {

assert.Equal(t,
&componenttest.MultiProtoReceiver{
TypeVal: "multireceiver",
NameVal: "multireceiver",
ReceiverSettings: configmodels.ReceiverSettings{
TypeVal: "multireceiver",
NameVal: "multireceiver",
},
Protocols: map[string]componenttest.MultiProtoReceiverOneCfg{
"http": {
Endpoint: "example.com:8888",
Expand All @@ -439,8 +441,10 @@ func TestDecodeConfig_MultiProto(t *testing.T) {

assert.Equal(t,
&componenttest.MultiProtoReceiver{
TypeVal: "multireceiver",
NameVal: "multireceiver/myreceiver",
ReceiverSettings: configmodels.ReceiverSettings{
TypeVal: "multireceiver",
NameVal: "multireceiver/myreceiver",
},
Protocols: map[string]componenttest.MultiProtoReceiverOneCfg{
"http": {
Endpoint: "localhost:12345",
Expand Down
30 changes: 29 additions & 1 deletion exporter/exporterhelper/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package exporterhelper
import (
"context"

"github.com/spf13/viper"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configerror"
"go.opentelemetry.io/collector/config/configmodels"
Expand All @@ -40,6 +42,7 @@ type CreateLogsExporter func(context.Context, component.ExporterCreateParams, co
// factory is the factory for Jaeger gRPC exporter.
type factory struct {
cfgType configmodels.Type
customUnmarshaler component.CustomUnmarshaler
createDefaultConfig CreateDefaultConfig
createTraceExporter CreateTraceExporter
createMetricsExporter CreateMetricsExporter
Expand Down Expand Up @@ -69,6 +72,13 @@ func WithLogs(createLogsExporter CreateLogsExporter) FactoryOption {
}
}

// WithCustomUnmarshaler overrides the default "not available" CustomUnmarshaler.
func WithCustomUnmarshaler(customUnmarshaler component.CustomUnmarshaler) FactoryOption {
return func(o *factory) {
o.customUnmarshaler = customUnmarshaler
}
}

// NewFactory returns a component.ExporterFactory that only supports all types.
func NewFactory(
cfgType configmodels.Type,
Expand All @@ -81,7 +91,13 @@ func NewFactory(
for _, opt := range options {
opt(f)
}
return f
var ret component.ExporterFactory
if f.customUnmarshaler != nil {
ret = &factoryWithUnmarshaler{f}
} else {
ret = f
}
return ret
}

// Type gets the type of the Exporter config created by this factory.
Expand Down Expand Up @@ -127,3 +143,15 @@ func (f *factory) CreateLogsExporter(
}
return nil, configerror.ErrDataTypeIsNotSupported
}

var _ component.FactoryUnmarshaler = (*factoryWithUnmarshaler)(nil)

type factoryWithUnmarshaler struct {
*factory
}

// CustomUnmarshaler returns a custom unmarshaler for the configuration or nil if
// there is no need for custom unmarshaling.
func (f *factoryWithUnmarshaler) Unmarshal(componentViperSection *viper.Viper, intoCfg interface{}) error {
return f.customUnmarshaler(componentViperSection, intoCfg)
}
15 changes: 14 additions & 1 deletion exporter/exporterhelper/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ package exporterhelper

import (
"context"
"errors"
"testing"

"github.com/spf13/viper"
"github.com/stretchr/testify/assert"

"go.opentelemetry.io/collector/component"
Expand All @@ -44,6 +46,8 @@ func TestNewFactory(t *testing.T) {
defaultConfig)
assert.EqualValues(t, typeStr, factory.Type())
assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig())
_, ok := factory.(component.FactoryUnmarshaler)
assert.False(t, ok)
_, err := factory.CreateTraceExporter(context.Background(), component.ExporterCreateParams{}, defaultCfg)
assert.Equal(t, configerror.ErrDataTypeIsNotSupported, err)
_, err = factory.CreateMetricsExporter(context.Background(), component.ExporterCreateParams{}, defaultCfg)
Expand All @@ -59,10 +63,15 @@ func TestNewFactory_WithConstructors(t *testing.T) {
defaultConfig,
WithTraces(createTraceExporter),
WithMetrics(createMetricsExporter),
WithLogs(createLogsExporter))
WithLogs(createLogsExporter),
WithCustomUnmarshaler(customUnmarshaler))
assert.EqualValues(t, typeStr, factory.Type())
assert.EqualValues(t, defaultCfg, factory.CreateDefaultConfig())

fu, ok := factory.(component.FactoryUnmarshaler)
assert.True(t, ok)
assert.Equal(t, errors.New("my error"), fu.Unmarshal(nil, nil))

te, err := factory.CreateTraceExporter(context.Background(), component.ExporterCreateParams{}, defaultCfg)
assert.NoError(t, err)
assert.Same(t, nopTracesExporter, te)
Expand Down Expand Up @@ -92,3 +101,7 @@ func createMetricsExporter(context.Context, component.ExporterCreateParams, conf
func createLogsExporter(context.Context, component.ExporterCreateParams, configmodels.Exporter) (component.LogsExporter, error) {
return nopLogsExporter, nil
}

func customUnmarshaler(*viper.Viper, interface{}) error {
return errors.New("my error")
}
Loading

0 comments on commit 097d59d

Please sign in to comment.