Skip to content

Commit

Permalink
Add config marshaler.
Browse files Browse the repository at this point in the history
  • Loading branch information
jefchien committed Aug 11, 2022
1 parent c80d1c9 commit 58921ec
Show file tree
Hide file tree
Showing 19 changed files with 940 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
### 🚩 Deprecations 🚩

### 💡 Enhancements 💡
- Add config marshaler (#5566)

### 🧰 Bug fixes 🧰

Expand Down
16 changes: 16 additions & 0 deletions config/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ type Unmarshallable interface {
Unmarshal(component *confmap.Conf) error
}

// Marshallable defines an optional interface for custom configuration marshalling.
// A configuration struct can implement this interface to override the default marshalling.
type Marshallable interface {
// Marshal is a function that marshals the config into a confmap.Conf in a custom way.
// The confmap.Conf will be empty and can be merged into.
Marshal(component *confmap.Conf) error
}

// DataType is a special Type that represents the data types supported by the collector. We currently support
// collecting metrics, traces and logs, this can expand in the future.
type DataType = Type
Expand All @@ -57,3 +65,11 @@ func unmarshal(componentSection *confmap.Conf, intoCfg interface{}) error {

return componentSection.UnmarshalExact(intoCfg)
}

func marshal(componentSection *confmap.Conf, outCfg interface{}) error {
if cm, ok := outCfg.(Marshallable); ok {
return cm.Marshal(componentSection)
}

return componentSection.Marshal(outCfg)
}
6 changes: 6 additions & 0 deletions config/configtelemetry/configtelemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
// that every component should generate.
type Level int32

var _ encoding.TextMarshaler = (*Level)(nil)
var _ encoding.TextUnmarshaler = (*Level)(nil)

func (l Level) String() string {
Expand All @@ -57,6 +58,11 @@ func (l Level) String() string {
return "unknown"
}

// MarshalText marshals Level to text.
func (l Level) MarshalText() (text []byte, err error) {
return []byte(l.String()), nil
}

// UnmarshalText unmarshalls text to a Level.
func (l *Level) UnmarshalText(text []byte) error {
if l == nil {
Expand Down
7 changes: 7 additions & 0 deletions config/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ func UnmarshalExporter(conf *confmap.Conf, cfg Exporter) error {
return unmarshal(conf, cfg)
}

// MarshalExporter helper function to marshal an Exporter config.
// It checks if the config implements Marshallable and uses that if available,
// otherwise uses Map.Marshal.
func MarshalExporter(conf *confmap.Conf, cfg Exporter) error {
return marshal(conf, cfg)
}

// ExporterSettings defines common settings for a component.Exporter configuration.
// Specific exporters can embed this struct and extend it with more fields if needed.
//
Expand Down
7 changes: 7 additions & 0 deletions config/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ func UnmarshalExtension(conf *confmap.Conf, cfg Extension) error {
return unmarshal(conf, cfg)
}

// MarshalExtension helper function to marshal an Extension config.
// It checks if the config implements Marshallable and uses that if available,
// otherwise uses Map.Marshal.
func MarshalExtension(conf *confmap.Conf, cfg Extension) error {
return marshal(conf, cfg)
}

// ExtensionSettings defines common settings for a component.Extension configuration.
// Specific processors can embed this struct and extend it with more fields if needed.
//
Expand Down
5 changes: 5 additions & 0 deletions config/identifiable.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ func (id ComponentID) Name() string {
return id.nameVal
}

// MarshalText implements the encoding.TextMarshaler interface.
func (id ComponentID) MarshalText() (text []byte, err error) {
return []byte(id.String()), nil
}

// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (id *ComponentID) UnmarshalText(text []byte) error {
idStr := string(text)
Expand Down
7 changes: 7 additions & 0 deletions config/identifiable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,10 @@ func TestIDFromString(t *testing.T) {
})
}
}

func TestMarshalText(t *testing.T) {
id := NewComponentIDWithName("test", "name")
got, err := id.MarshalText()
assert.NoError(t, err)
assert.Equal(t, id.String(), string(got))
}
7 changes: 7 additions & 0 deletions config/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ func UnmarshalProcessor(conf *confmap.Conf, cfg Processor) error {
return unmarshal(conf, cfg)
}

// MarshalProcessor helper function to marshal a Processor config.
// It checks if the config implements Marshallable and uses that if available,
// otherwise uses Map.Marshal.
func MarshalProcessor(conf *confmap.Conf, cfg Processor) error {
return marshal(conf, cfg)
}

// ProcessorSettings defines common settings for a component.Processor configuration.
// Specific processors can embed this struct and extend it with more fields if needed.
//
Expand Down
7 changes: 7 additions & 0 deletions config/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ func UnmarshalReceiver(conf *confmap.Conf, cfg Receiver) error {
return unmarshal(conf, cfg)
}

// MarshalReceiver helper function to marshal a Receiver config.
// It checks if the config implements Marshallable and uses that if available,
// otherwise uses Map.Marshal.
func MarshalReceiver(conf *confmap.Conf, cfg Receiver) error {
return marshal(conf, cfg)
}

// ReceiverSettings defines common settings for a component.Receiver configuration.
// Specific receivers can embed this struct and extend it with more fields if needed.
//
Expand Down
15 changes: 15 additions & 0 deletions confmap/confmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/knadh/koanf/maps"
"github.com/knadh/koanf/providers/confmap"
"github.com/mitchellh/mapstructure"

encoder "go.opentelemetry.io/collector/service/configmarshaler/encoder/mapstructure"
)

const (
Expand Down Expand Up @@ -76,6 +78,19 @@ func (l *Conf) UnmarshalExact(rawVal interface{}) error {
return decoder.Decode(l.ToStringMap())
}

// Marshal encodes the rawVal and merges it into the Conf.
func (l *Conf) Marshal(rawVal interface{}) error {
data, err := encoder.Encode(rawVal)
if err != nil {
return err
}
out, ok := data.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid config encoding")
}
return l.Merge(NewFromStringMap(out))
}

// Get can retrieve any value given the key to use.
func (l *Conf) Get(key string) interface{} {
return l.k.Get(key)
Expand Down
38 changes: 38 additions & 0 deletions confmap/confmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ func (tID *TestID) UnmarshalText(text []byte) error {
return nil
}

func (tID TestID) MarshalText() (text []byte, err error) {
out := string(tID)
if !strings.HasSuffix(out, "_") {
out += "_"
}
return []byte(out), nil
}

type TestIDConfig struct {
Boolean bool `mapstructure:"bool"`
Map map[TestID]string `mapstructure:"map"`
Expand Down Expand Up @@ -218,6 +226,36 @@ func TestMapKeyStringToMapKeyTextUnmarshalerHookFuncErrorUnmarshal(t *testing.T)
assert.Error(t, conf.UnmarshalExact(cfg))
}

func TestMarshal(t *testing.T) {
conf := New()
cfg := &TestIDConfig{
Boolean: true,
Map: map[TestID]string{
"string": "this is a string",
},
}
assert.NoError(t, conf.Marshal(cfg))
assert.Equal(t, true, conf.Get("bool"))
assert.Equal(t, map[string]interface{}{"string_": "this is a string"}, conf.Get("map"))
}

func TestMarshalDuplicateID(t *testing.T) {
conf := New()
cfg := &TestIDConfig{
Boolean: true,
Map: map[TestID]string{
"string": "this is a string",
"string_": "this is another string",
},
}
assert.Error(t, conf.Marshal(cfg))
}

func TestMarshalError(t *testing.T) {
conf := New()
assert.Error(t, conf.Marshal(nil))
}

// newConfFromFile creates a new Conf by reading the given file.
func newConfFromFile(t testing.TB, fileName string) map[string]interface{} {
content, err := ioutil.ReadFile(filepath.Clean(fileName))
Expand Down
4 changes: 2 additions & 2 deletions receiver/otlpreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const (

// Protocols is the configuration for the supported protocols.
type Protocols struct {
GRPC *configgrpc.GRPCServerSettings `mapstructure:"grpc"`
HTTP *confighttp.HTTPServerSettings `mapstructure:"http"`
GRPC *configgrpc.GRPCServerSettings `mapstructure:"grpc,omitempty"`
HTTP *confighttp.HTTPServerSettings `mapstructure:"http,omitempty"`
}

// Config defines configuration for OTLP receiver.
Expand Down
158 changes: 158 additions & 0 deletions service/configmarshaler/defaultmarshaler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package configmarshaler // import "go.opentelemetry.io/collector/service/configmarshaler"

import (
"bytes"

"go.uber.org/multierr"
"gopkg.in/yaml.v3"

"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/service"
"go.opentelemetry.io/collector/service/configmarshaler/encoder/mapstructure"
)

// YAML top-level configuration keys.
const (
// extensionsKeyName is the configuration key name for extensions section.
extensionsKeyName = "extensions"

// receiversKeyName is the configuration key name for receivers section.
receiversKeyName = "receivers"

// exportersKeyName is the configuration key name for exporters section.
exportersKeyName = "exporters"

// processorsKeyName is the configuration key name for processors section.
processorsKeyName = "processors"

// serviceKeyName is the configuration key name for service section.
serviceKeyName = "service"

defaultIndent = 2
)

var (
// emptyNode is a workaround to omit empty maps
emptyNode = &yaml.Node{
Kind: yaml.ScalarNode,
Value: "",
Style: yaml.FlowStyle,
}
)

type ConfigMarshaler struct {
}

// New creates a new default ConfigMarshaler.
func New() ConfigMarshaler {
return ConfigMarshaler{}
}

// Marshal converts a service.Config into YAML bytes.
func (ConfigMarshaler) Marshal(cfg *service.Config) ([]byte, error) {
output := make(map[string]interface{})
var errs, err error
if output[extensionsKeyName], err = marshalExtensions(cfg.Extensions); err != nil {
errs = multierr.Append(errs, err)
}
if output[receiversKeyName], err = marshalReceivers(cfg.Receivers); err != nil {
errs = multierr.Append(errs, err)
}
if output[processorsKeyName], err = marshalProcessors(cfg.Processors); err != nil {
errs = multierr.Append(errs, err)
}
if output[exportersKeyName], err = marshalExporters(cfg.Exporters); err != nil {
errs = multierr.Append(errs, err)
}
if output[serviceKeyName], err = mapstructure.Encode(cfg.Service); err != nil {
errs = multierr.Append(errs, err)
}
if errs != nil {
return nil, errs
}
var buffer bytes.Buffer
enc := yaml.NewEncoder(&buffer)
enc.SetIndent(defaultIndent)
if err = enc.Encode(output); err != nil {
return nil, err
}
if err = enc.Close(); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}

func marshalExtensions(exts map[config.ComponentID]config.Extension) (interface{}, error) {
if len(exts) == 0 {
return emptyNode, nil
}
extensions := make(map[string]interface{})
for id, cfg := range exts {
conf := confmap.New()
if err := config.MarshalExtension(conf, cfg); err != nil {
return nil, err
}
extensions[id.String()] = conf.ToStringMap()
}
return extensions, nil
}

func marshalReceivers(recvs map[config.ComponentID]config.Receiver) (interface{}, error) {
if len(recvs) == 0 {
return emptyNode, nil
}
receivers := make(map[string]interface{})
for id, cfg := range recvs {
conf := confmap.New()
if err := config.MarshalReceiver(conf, cfg); err != nil {
return nil, err
}
receivers[id.String()] = conf.ToStringMap()
}
return receivers, nil
}

func marshalExporters(exps map[config.ComponentID]config.Exporter) (interface{}, error) {
if len(exps) == 0 {
return emptyNode, nil
}
exporters := make(map[string]interface{})
for id, cfg := range exps {
conf := confmap.New()
if err := config.MarshalExporter(conf, cfg); err != nil {
return nil, err
}
exporters[id.String()] = conf.ToStringMap()
}
return exporters, nil
}

func marshalProcessors(procs map[config.ComponentID]config.Processor) (interface{}, error) {
if len(procs) == 0 {
return emptyNode, nil
}
processors := make(map[string]interface{})
for id, cfg := range procs {
conf := confmap.New()
if err := config.MarshalProcessor(conf, cfg); err != nil {
return nil, err
}
processors[id.String()] = conf.ToStringMap()
}
return processors, nil
}
Loading

0 comments on commit 58921ec

Please sign in to comment.