Skip to content

Commit 5f3e4ec

Browse files
authored
Extract ConfigFactory in a ParserProvider interface (#2868)
Signed-off-by: Bogdan Drutu <[email protected]>
1 parent 1ca65c8 commit 5f3e4ec

File tree

15 files changed

+479
-251
lines changed

15 files changed

+479
-251
lines changed

config/configparser/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
// Package configparser implements loading of configuration from Viper configuration.
15+
// Package configparser implements configuration loading from a config.Parser.
1616
// The implementation relies on registered factories that allow creating
1717
// default configuration for each type of receiver/exporter/processor.
1818
package configparser

service/application.go

Lines changed: 28 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package service
1818

1919
import (
2020
"context"
21-
"errors"
2221
"flag"
2322
"fmt"
2423
"os"
@@ -30,13 +29,13 @@ import (
3029
"go.uber.org/zap"
3130

3231
"go.opentelemetry.io/collector/component"
33-
"go.opentelemetry.io/collector/config"
3432
"go.opentelemetry.io/collector/config/configcheck"
3533
"go.opentelemetry.io/collector/config/configparser"
3634
"go.opentelemetry.io/collector/config/configtelemetry"
3735
"go.opentelemetry.io/collector/consumer/consumererror"
3836
"go.opentelemetry.io/collector/internal/collector/telemetry"
3937
"go.opentelemetry.io/collector/service/internal/builder"
38+
"go.opentelemetry.io/collector/service/parserprovider"
4039
)
4140

4241
const (
@@ -66,7 +65,7 @@ type Application struct {
6665

6766
factories component.Factories
6867

69-
configFactory ConfigFactory
68+
parserProvider parserprovider.ParserProvider
7069

7170
// stopTestChan is used to terminate the application in end to end tests.
7271
stopTestChan chan struct{}
@@ -84,58 +83,25 @@ type Parameters struct {
8483
Factories component.Factories
8584
// ApplicationStartInfo provides application start information.
8685
ApplicationStartInfo component.ApplicationStartInfo
87-
// ConfigFactory that creates the configuration.
88-
// If it is not provided the default factory (FileLoaderConfigFactory) is used.
89-
// The default factory loads the configuration file and overrides component's configuration
86+
// ParserProvider provides the configuration's Parser.
87+
// If it is not provided a default provider is used. The default provider loads the configuration
88+
// from a config file define by the --config command line flag and overrides component's configuration
9089
// properties supplied via --set command line flag.
91-
ConfigFactory ConfigFactory
90+
ParserProvider parserprovider.ParserProvider
9291
// LoggingOptions provides a way to change behavior of zap logging.
9392
LoggingOptions []zap.Option
9493
}
9594

96-
// ConfigFactory creates config.
97-
// The ConfigFactory implementation should call AddSetFlagProperties to enable configuration passed via `--set` flag.
98-
// Viper and command instances are passed from the Application.
99-
// The factories also belong to the Application and are equal to the factories passed via Parameters.
100-
type ConfigFactory func(cmd *cobra.Command, factories component.Factories) (*config.Config, error)
101-
102-
// FileLoaderConfigFactory implements ConfigFactory and it creates configuration from file
103-
// and from --set command line flag (if the flag is present).
104-
func FileLoaderConfigFactory(cmd *cobra.Command, factories component.Factories) (*config.Config, error) {
105-
file := builder.GetConfigFile()
106-
if file == "" {
107-
return nil, errors.New("config file not specified")
108-
}
109-
110-
cp, err := config.NewParserFromFile(file)
111-
if err != nil {
112-
return nil, fmt.Errorf("error loading config file %q: %v", file, err)
113-
}
114-
115-
// next overlay the config file with --set flags
116-
if err := AddSetFlagProperties(cp, cmd); err != nil {
117-
return nil, fmt.Errorf("failed to process set flag: %v", err)
118-
}
119-
return configparser.Load(cp, factories)
120-
}
121-
12295
// New creates and returns a new instance of Application.
12396
func New(params Parameters) (*Application, error) {
12497
if err := configcheck.ValidateConfigFromFactories(params.Factories); err != nil {
12598
return nil, err
12699
}
127100

128-
configFactory := params.ConfigFactory
129-
if configFactory == nil {
130-
// use default factory that loads the configuration file
131-
configFactory = FileLoaderConfigFactory
132-
}
133-
134101
app := &Application{
135-
info: params.ApplicationStartInfo,
136-
factories: params.Factories,
137-
stateChannel: make(chan State, Closed+1),
138-
configFactory: configFactory,
102+
info: params.ApplicationStartInfo,
103+
factories: params.Factories,
104+
stateChannel: make(chan State, Closed+1),
139105
}
140106

141107
rootCmd := &cobra.Command{
@@ -159,6 +125,7 @@ func New(params Parameters) (*Application, error) {
159125
flagSet := new(flag.FlagSet)
160126
addFlagsFns := []func(*flag.FlagSet){
161127
configtelemetry.Flags,
128+
parserprovider.Flags,
162129
telemetry.Flags,
163130
builder.Flags,
164131
loggerFlags,
@@ -167,10 +134,15 @@ func New(params Parameters) (*Application, error) {
167134
addFlags(flagSet)
168135
}
169136
rootCmd.Flags().AddGoFlagSet(flagSet)
170-
addSetFlag(rootCmd.Flags())
171-
172137
app.rootCmd = rootCmd
173138

139+
parserProvider := params.ParserProvider
140+
if parserProvider == nil {
141+
// use default provider.
142+
parserProvider = parserprovider.Default()
143+
}
144+
app.parserProvider = parserProvider
145+
174146
return app, nil
175147
}
176148

@@ -249,11 +221,20 @@ func (app *Application) runAndWaitForShutdownEvent() {
249221
func (app *Application) setupConfigurationComponents(ctx context.Context) error {
250222
app.logger.Info("Loading configuration...")
251223

252-
cfg, err := app.configFactory(app.rootCmd, app.factories)
224+
cp, err := app.parserProvider.Get()
225+
if err != nil {
226+
return fmt.Errorf("cannot load configuration's parser: %w", err)
227+
}
228+
229+
cfg, err := configparser.Load(cp, app.factories)
253230
if err != nil {
254231
return fmt.Errorf("cannot load configuration: %w", err)
255232
}
256233

234+
if err = cfg.Validate(); err != nil {
235+
return fmt.Errorf("invalid configuration: %w", err)
236+
}
237+
257238
app.logger.Info("Applying configuration...")
258239

259240
service, err := newService(&settings{
@@ -339,7 +320,7 @@ func (app *Application) createMemoryBallast() ([]byte, uint64) {
339320
}
340321

341322
// updateService shutdowns the current app.service and setups a new one according
342-
// to the latest configuration. It requires that app.configFactory and app.factories
323+
// to the latest configuration. It requires that app.parserProvider and app.factories
343324
// are properly populated to finish successfully.
344325
func (app *Application) updateService(ctx context.Context) error {
345326
if app.service != nil {

service/application_test.go

Lines changed: 28 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,25 @@ import (
1919
"bufio"
2020
"context"
2121
"errors"
22-
"flag"
2322
"fmt"
2423
"net/http"
2524
"runtime"
26-
"sort"
2725
"strconv"
2826
"strings"
2927
"syscall"
3028
"testing"
31-
"time"
3229

3330
"github.com/prometheus/common/expfmt"
34-
"github.com/spf13/cobra"
3531
"github.com/stretchr/testify/assert"
3632
"github.com/stretchr/testify/require"
3733
"go.uber.org/zap"
3834
"go.uber.org/zap/zapcore"
3935

4036
"go.opentelemetry.io/collector/component"
4137
"go.opentelemetry.io/collector/config"
42-
"go.opentelemetry.io/collector/config/configparser"
43-
"go.opentelemetry.io/collector/processor/attributesprocessor"
44-
"go.opentelemetry.io/collector/processor/batchprocessor"
45-
"go.opentelemetry.io/collector/receiver/jaegerreceiver"
4638
"go.opentelemetry.io/collector/service/defaultcomponents"
4739
"go.opentelemetry.io/collector/service/internal/builder"
40+
"go.opentelemetry.io/collector/service/parserprovider"
4841
"go.opentelemetry.io/collector/testutil"
4942
)
5043

@@ -149,10 +142,8 @@ func TestApplication_StartAsGoRoutine(t *testing.T) {
149142

150143
params := Parameters{
151144
ApplicationStartInfo: component.DefaultApplicationStartInfo(),
152-
ConfigFactory: func(_ *cobra.Command, factories component.Factories) (*config.Config, error) {
153-
return constructMimumalOpConfig(t, factories), nil
154-
},
155-
Factories: factories,
145+
ParserProvider: new(minimalParserLoader),
146+
Factories: factories,
156147
}
157148
app, err := New(params)
158149
require.NoError(t, err)
@@ -225,105 +216,9 @@ func assertMetrics(t *testing.T, prefix string, metricsPort uint16, mandatoryLab
225216
}
226217
}
227218

228-
func TestSetFlag(t *testing.T) {
229-
factories, err := defaultcomponents.Components()
230-
require.NoError(t, err)
231-
params := Parameters{
232-
Factories: factories,
233-
}
234-
t.Run("unknown_component", func(t *testing.T) {
235-
app, err := New(params)
236-
require.NoError(t, err)
237-
err = app.rootCmd.ParseFlags([]string{
238-
"--config=testdata/otelcol-config.yaml",
239-
"--set=processors.doesnotexist.timeout=2s",
240-
})
241-
require.NoError(t, err)
242-
cfg, err := FileLoaderConfigFactory(app.rootCmd, factories)
243-
require.Error(t, err)
244-
require.Nil(t, cfg)
245-
246-
})
247-
t.Run("component_not_added_to_pipeline", func(t *testing.T) {
248-
app, err := New(params)
249-
require.NoError(t, err)
250-
err = app.rootCmd.ParseFlags([]string{
251-
"--config=testdata/otelcol-config.yaml",
252-
"--set=processors.batch/foo.timeout=2s",
253-
})
254-
require.NoError(t, err)
255-
cfg, err := FileLoaderConfigFactory(app.rootCmd, factories)
256-
require.NoError(t, err)
257-
assert.NotNil(t, cfg)
258-
err = cfg.Validate()
259-
require.NoError(t, err)
260-
261-
var processors []string
262-
for k := range cfg.Processors {
263-
processors = append(processors, k)
264-
}
265-
sort.Strings(processors)
266-
// batch/foo is not added to the pipeline
267-
assert.Equal(t, []string{"attributes", "batch", "batch/foo"}, processors)
268-
assert.Equal(t, []string{"attributes", "batch"}, cfg.Service.Pipelines["traces"].Processors)
269-
})
270-
t.Run("ok", func(t *testing.T) {
271-
app, err := New(params)
272-
require.NoError(t, err)
273-
274-
err = app.rootCmd.ParseFlags([]string{
275-
"--config=testdata/otelcol-config.yaml",
276-
"--set=processors.batch.timeout=2s",
277-
// Arrays are overridden and object arrays cannot be indexed
278-
// this creates actions array of size 1
279-
"--set=processors.attributes.actions.key=foo",
280-
"--set=processors.attributes.actions.value=bar",
281-
"--set=receivers.jaeger.protocols.grpc.endpoint=localhost:12345",
282-
"--set=extensions.health_check.endpoint=localhost:8080",
283-
})
284-
require.NoError(t, err)
285-
cfg, err := FileLoaderConfigFactory(app.rootCmd, factories)
286-
require.NoError(t, err)
287-
require.NotNil(t, cfg)
288-
err = cfg.Validate()
289-
require.NoError(t, err)
290-
291-
assert.Equal(t, 2, len(cfg.Processors))
292-
batch := cfg.Processors["batch"].(*batchprocessor.Config)
293-
assert.Equal(t, time.Second*2, batch.Timeout)
294-
jaeger := cfg.Receivers["jaeger"].(*jaegerreceiver.Config)
295-
assert.Equal(t, "localhost:12345", jaeger.GRPC.NetAddr.Endpoint)
296-
attributes := cfg.Processors["attributes"].(*attributesprocessor.Config)
297-
require.Equal(t, 1, len(attributes.Actions))
298-
assert.Equal(t, "foo", attributes.Actions[0].Key)
299-
assert.Equal(t, "bar", attributes.Actions[0].Value)
300-
})
301-
}
302-
303-
func TestSetFlag_component_does_not_exist(t *testing.T) {
304-
factories, err := defaultcomponents.Components()
305-
require.NoError(t, err)
306-
307-
cmd := &cobra.Command{}
308-
addSetFlag(cmd.Flags())
309-
fs := &flag.FlagSet{}
310-
builder.Flags(fs)
311-
cmd.Flags().AddGoFlagSet(fs)
312-
cmd.ParseFlags([]string{
313-
"--config=testdata/otelcol-config.yaml",
314-
"--set=processors.batch.timeout=2s",
315-
// Arrays are overridden and object arrays cannot be indexed
316-
// this creates actions array of size 1
317-
"--set=processors.attributes.actions.key=foo",
318-
"--set=processors.attributes.actions.value=bar",
319-
"--set=receivers.jaeger.protocols.grpc.endpoint=localhost:12345",
320-
})
321-
cfg, err := FileLoaderConfigFactory(cmd, factories)
322-
require.NoError(t, err)
323-
require.NotNil(t, cfg)
324-
}
219+
type minimalParserLoader struct{}
325220

326-
func constructMimumalOpConfig(t *testing.T, factories component.Factories) *config.Config {
221+
func (*minimalParserLoader) Get() (*config.Parser, error) {
327222
configStr := `
328223
receivers:
329224
otlp:
@@ -347,37 +242,36 @@ service:
347242
v := config.NewViper()
348243
v.SetConfigType("yaml")
349244
v.ReadConfig(strings.NewReader(configStr))
350-
cfg, err := configparser.Load(config.ParserFromViper(v), factories)
351-
assert.NoError(t, err)
352-
err = cfg.Validate()
353-
assert.NoError(t, err)
354-
return cfg
245+
return config.ParserFromViper(v), nil
246+
}
247+
248+
type errParserLoader struct {
249+
err error
250+
}
251+
252+
func (epl *errParserLoader) Get() (*config.Parser, error) {
253+
return nil, epl.err
355254
}
356255

357256
func TestApplication_updateService(t *testing.T) {
358257
factories, err := defaultcomponents.Components()
359258
require.NoError(t, err)
360259
ctx := context.Background()
361260
sentinelError := errors.New("sentinel error")
362-
returnConfigFactoryFn := func(cfg *config.Config, err error) ConfigFactory {
363-
return func(*cobra.Command, component.Factories) (*config.Config, error) {
364-
return cfg, err
365-
}
366-
}
367261

368262
tests := []struct {
369-
name string
370-
configFactory ConfigFactory
371-
service *service
372-
skip bool
263+
name string
264+
parserProvider parserprovider.ParserProvider
265+
service *service
266+
skip bool
373267
}{
374268
{
375-
name: "first_load_err",
376-
configFactory: returnConfigFactoryFn(nil, sentinelError),
269+
name: "first_load_err",
270+
parserProvider: &errParserLoader{err: sentinelError},
377271
},
378272
{
379-
name: "retire_service_ok_load_err",
380-
configFactory: returnConfigFactoryFn(nil, sentinelError),
273+
name: "retire_service_ok_load_err",
274+
parserProvider: &errParserLoader{err: sentinelError},
381275
service: &service{
382276
logger: zap.NewNop(),
383277
builtExporters: builder.Exporters{},
@@ -387,8 +281,8 @@ func TestApplication_updateService(t *testing.T) {
387281
},
388282
},
389283
{
390-
name: "retire_service_ok_load_ok",
391-
configFactory: returnConfigFactoryFn(constructMimumalOpConfig(t, factories), nil),
284+
name: "retire_service_ok_load_ok",
285+
parserProvider: new(minimalParserLoader),
392286
service: &service{
393287
logger: zap.NewNop(),
394288
builtExporters: builder.Exporters{},
@@ -407,10 +301,10 @@ func TestApplication_updateService(t *testing.T) {
407301
}
408302

409303
app := Application{
410-
logger: zap.NewNop(),
411-
configFactory: tt.configFactory,
412-
factories: factories,
413-
service: tt.service,
304+
logger: zap.NewNop(),
305+
parserProvider: tt.parserProvider,
306+
factories: factories,
307+
service: tt.service,
414308
}
415309

416310
err := app.updateService(ctx)

0 commit comments

Comments
 (0)