Skip to content

Commit bde3bd9

Browse files
authored
Fix logs-only check in agent and container environment merge (#1392)
1 parent b023efb commit bde3bd9

File tree

11 files changed

+221
-101
lines changed

11 files changed

+221
-101
lines changed

RELEASE_NOTES

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
========================================================================
2+
Amazon CloudWatch Agent 1.300048.1 (2024-10-15)
3+
========================================================================
4+
Bug Fixes:
5+
* Fix logs-only check in agent and container environment merge
6+
17
========================================================================
28
Amazon CloudWatch Agent 1.300048.0 (2024-10-11)
39
========================================================================

cmd/amazon-cloudwatch-agent/amazon-cloudwatch-agent.go

+38-36
Original file line numberDiff line numberDiff line change
@@ -310,20 +310,19 @@ func runAgent(ctx context.Context,
310310
}
311311
}
312312

313-
isOnlyDefaultConfigPath := len(fOtelConfigs) == 1 && fOtelConfigs[0] == paths.YamlConfigPath
314-
315313
if len(c.Inputs) != 0 && len(c.Outputs) != 0 {
316314
log.Println("creating new logs agent")
317315
logAgent := logs.NewLogAgent(c)
318316
// Always run logAgent as goroutine regardless of whether starting OTEL or Telegraf.
319317
go logAgent.Run(ctx)
320318

321-
// If only the default CWAgent config yaml is provided and does not exist, then ASSUME
319+
// If only a single YAML is provided and does not exist, then ASSUME the agent is
322320
// just monitoring logs since this is the default when no OTEL config flag is provided.
323321
// So just start Telegraf.
324-
if isOnlyDefaultConfigPath {
322+
if len(fOtelConfigs) == 1 {
325323
_, err = os.Stat(fOtelConfigs[0])
326324
if errors.Is(err, os.ErrNotExist) {
325+
log.Println("I! running in logs-only mode")
327326
useragent.Get().SetComponents(&otelcol.Config{}, c)
328327
return ag.Run(ctx)
329328
}
@@ -333,20 +332,20 @@ func runAgent(ctx context.Context,
333332
level := cwaLogger.ConvertToAtomicLevel(wlog.LogLevel())
334333
logger, loggerOptions := cwaLogger.NewLogger(writer, level)
335334

336-
uris := fOtelConfigs
337-
// merge configs together
338-
if len(uris) > 1 {
339-
result, err := mergeConfigs(uris, isOnlyDefaultConfigPath)
340-
if err != nil {
341-
return err
342-
}
343-
_ = os.Setenv(envconfig.CWAgentMergedOtelConfig, toyamlconfig.ToYamlConfig(result.ToStringMap()))
344-
uris = []string{"env:" + envconfig.CWAgentMergedOtelConfig}
335+
otelConfigs := fOtelConfigs
336+
// try merging configs together, will return nil if nothing to merge
337+
merged, err := mergeConfigs(otelConfigs)
338+
if err != nil {
339+
return err
340+
}
341+
if merged != nil {
342+
_ = os.Setenv(envconfig.CWAgentMergedOtelConfig, toyamlconfig.ToYamlConfig(merged.ToStringMap()))
343+
otelConfigs = []string{"env:" + envconfig.CWAgentMergedOtelConfig}
345344
} else {
346345
_ = os.Unsetenv(envconfig.CWAgentMergedOtelConfig)
347346
}
348347

349-
providerSettings := configprovider.GetSettings(uris, logger)
348+
providerSettings := configprovider.GetSettings(otelConfigs, logger)
350349
provider, err := otelcol.NewConfigProvider(providerSettings)
351350
if err != nil {
352351
return fmt.Errorf("error while initializing config provider: %v", err)
@@ -382,7 +381,7 @@ func runAgent(ctx context.Context,
382381
// docs: https://github.com/open-telemetry/opentelemetry-collector/blob/93cbae436ae61b832279dbbb18a0d99214b7d305/otelcol/command.go#L63
383382
// *************************************************************************************************
384383
var e []string
385-
for _, uri := range uris {
384+
for _, uri := range otelConfigs {
386385
e = append(e, "--config="+uri)
387386
}
388387
cmd.SetArgs(e)
@@ -405,32 +404,35 @@ func getCollectorParams(factories otelcol.Factories, providerSettings otelcol.Co
405404
}
406405
}
407406

408-
func mergeConfigs(configPaths []string, isOnlyDefaultConfigPath bool) (*confmap.Conf, error) {
409-
result := confmap.New()
410-
content, ok := os.LookupEnv(envconfig.CWOtelConfigContent)
411-
// Similar to translator, for containerized agent, try to use env variable if no other supplemental config paths
412-
// are provided.
413-
if ok && len(content) > 0 && isOnlyDefaultConfigPath && envconfig.IsRunningInContainer() {
414-
log.Printf("D! Merging OTEL configuration from: %s", envconfig.CWOtelConfigContent)
415-
conf, err := confmap.LoadFromBytes([]byte(content))
416-
if err != nil {
417-
return nil, fmt.Errorf("failed to load OTEL configs: %w", err)
418-
}
419-
if err = result.Merge(conf); err != nil {
420-
return nil, fmt.Errorf("failed to merge OTEL configs: %w", err)
407+
// mergeConfigs tries to merge configurations together. If nothing to merge, returns nil without an error.
408+
func mergeConfigs(configPaths []string) (*confmap.Conf, error) {
409+
var loaders []confmap.Loader
410+
if envconfig.IsRunningInContainer() {
411+
content, ok := os.LookupEnv(envconfig.CWOtelConfigContent)
412+
if ok && len(content) > 0 {
413+
log.Printf("D! Merging OTEL configuration from: %s", envconfig.CWOtelConfigContent)
414+
loaders = append(loaders, confmap.NewByteLoader(envconfig.CWOtelConfigContent, []byte(content)))
421415
}
422416
}
423-
log.Printf("D! Merging OTEL configurations from: %s", configPaths)
424-
for _, configPath := range configPaths {
425-
conf, err := confmap.LoadFromFile(configPath)
426-
if err != nil {
427-
return nil, fmt.Errorf("failed to load OTEL configs: %w", err)
417+
// If using environment variable or passing in more than one config
418+
if len(loaders) > 0 || len(configPaths) > 1 {
419+
log.Printf("D! Merging OTEL configurations from: %s", configPaths)
420+
for _, configPath := range configPaths {
421+
loaders = append(loaders, confmap.NewFileLoader(configPath))
428422
}
429-
if err = result.Merge(conf); err != nil {
430-
return nil, fmt.Errorf("failed to merge OTEL configs: %w", err)
423+
result := confmap.New()
424+
for _, loader := range loaders {
425+
conf, err := loader.Load()
426+
if err != nil {
427+
return nil, fmt.Errorf("failed to load OTEL configs: %w", err)
428+
}
429+
if err = result.Merge(conf); err != nil {
430+
return nil, fmt.Errorf("failed to merge OTEL configs: %w", err)
431+
}
431432
}
433+
return result, nil
432434
}
433-
return result, nil
435+
return nil, nil
434436
}
435437

436438
func components(telegrafConfig *config.Config) (otelcol.Factories, error) {

cmd/amazon-cloudwatch-agent/amazon-cloudwatch-agent_test.go

+42-41
Original file line numberDiff line numberDiff line change
@@ -76,62 +76,60 @@ extensions:
7676
service:
7777
extensions: [nop]
7878
pipelines:
79-
traces:
79+
traces/1:
8080
receivers: [nop/1]
8181
exporters: [nop]
8282
`
8383
testCases := map[string]struct {
84-
input []string
85-
isContainer bool
86-
isOnlyDefaultConfigPath bool
87-
envValue string
88-
want *confmap.Conf
89-
wantErr bool
84+
input []string
85+
isContainer bool
86+
envValue string
87+
want *confmap.Conf
88+
wantErr bool
9089
}{
91-
"WithoutInvalidFile": {
92-
input: []string{filepath.Join("not", "a", "file")},
90+
"WithInvalidFile": {
91+
input: []string{filepath.Join("not", "a", "file"), filepath.Join("testdata", "base.yaml")},
9392
wantErr: true,
9493
},
94+
"WithNoMerge": {
95+
input: []string{filepath.Join("testdata", "base.yaml")},
96+
wantErr: false,
97+
},
9598
"WithoutEnv/Container": {
96-
input: []string{filepath.Join("testdata", "base.yaml")},
97-
isContainer: true,
98-
isOnlyDefaultConfigPath: true,
99-
want: mustLoadFromFile(t, filepath.Join("testdata", "base.yaml")),
99+
input: []string{filepath.Join("testdata", "base.yaml"), filepath.Join("testdata", "merge.yaml")},
100+
isContainer: true,
101+
want: mustLoadFromFile(t, filepath.Join("testdata", "base+merge.yaml")),
100102
},
101103
"WithEnv/NonContainer": {
102-
input: []string{filepath.Join("testdata", "base.yaml")},
103-
isContainer: false,
104-
isOnlyDefaultConfigPath: true,
105-
envValue: testEnvValue,
106-
want: mustLoadFromFile(t, filepath.Join("testdata", "base.yaml")),
104+
input: []string{filepath.Join("testdata", "base.yaml"), filepath.Join("testdata", "merge.yaml")},
105+
isContainer: false,
106+
envValue: testEnvValue,
107+
want: mustLoadFromFile(t, filepath.Join("testdata", "base+merge.yaml")),
107108
},
108109
"WithEnv/Container": {
109-
input: []string{filepath.Join("testdata", "base.yaml")},
110-
isContainer: true,
111-
isOnlyDefaultConfigPath: true,
112-
envValue: testEnvValue,
113-
want: mustLoadFromFile(t, filepath.Join("testdata", "base+env.yaml")),
110+
input: []string{filepath.Join("testdata", "base.yaml")},
111+
isContainer: true,
112+
envValue: testEnvValue,
113+
want: mustLoadFromFile(t, filepath.Join("testdata", "base+env.yaml")),
114114
},
115115
"WithEmptyEnv/Container": {
116-
input: []string{filepath.Join("testdata", "base.yaml")},
117-
isContainer: true,
118-
isOnlyDefaultConfigPath: true,
119-
envValue: "",
120-
want: mustLoadFromFile(t, filepath.Join("testdata", "base.yaml")),
116+
input: []string{filepath.Join("testdata", "base.yaml")},
117+
isContainer: true,
118+
envValue: "",
119+
want: nil,
120+
wantErr: false,
121121
},
122122
"WithInvalidEnv/Container": {
123-
input: []string{filepath.Join("testdata", "base.yaml")},
124-
isContainer: true,
125-
isOnlyDefaultConfigPath: true,
126-
envValue: "test",
127-
wantErr: true,
123+
input: []string{filepath.Join("testdata", "base.yaml")},
124+
isContainer: true,
125+
envValue: "test",
126+
wantErr: true,
128127
},
129-
"WithIgnoredEnv/Container": {
130-
input: []string{filepath.Join("testdata", "base.yaml")},
131-
isContainer: true,
132-
isOnlyDefaultConfigPath: false,
133-
envValue: testEnvValue,
134-
want: mustLoadFromFile(t, filepath.Join("testdata", "base.yaml")),
128+
"WithEnv/Container/MultipleFiles": {
129+
input: []string{filepath.Join("testdata", "base.yaml"), filepath.Join("testdata", "merge.yaml")},
130+
isContainer: true,
131+
envValue: testEnvValue,
132+
want: mustLoadFromFile(t, filepath.Join("testdata", "base+merge+env.yaml")),
135133
},
136134
}
137135
for name, testCase := range testCases {
@@ -140,10 +138,13 @@ service:
140138
t.Setenv(envconfig.RunInContainer, envconfig.TrueValue)
141139
}
142140
t.Setenv(envconfig.CWOtelConfigContent, testCase.envValue)
143-
got, err := mergeConfigs(testCase.input, testCase.isOnlyDefaultConfigPath)
141+
got, err := mergeConfigs(testCase.input)
144142
if testCase.wantErr {
145143
assert.Error(t, err)
146144
assert.Nil(t, got)
145+
} else if testCase.want == nil {
146+
assert.NoError(t, err)
147+
assert.Nil(t, got)
147148
} else {
148149
assert.NoError(t, err)
149150
assert.NotNil(t, got)
@@ -154,7 +155,7 @@ service:
154155
}
155156

156157
func mustLoadFromFile(t *testing.T, path string) *confmap.Conf {
157-
conf, err := confmap.LoadFromFile(path)
158+
conf, err := confmap.NewFileLoader(path).Load()
158159
require.NoError(t, err)
159160
return conf
160161
}

cmd/amazon-cloudwatch-agent/testdata/base+env.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ service:
1414
metrics:
1515
receivers: [nop]
1616
exporters: [nop]
17-
traces:
17+
traces/1:
1818
receivers: [nop/1]
1919
exporters: [nop]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
receivers:
2+
nop:
3+
nop/1:
4+
5+
exporters:
6+
nop:
7+
8+
extensions:
9+
nop:
10+
11+
service:
12+
extensions: [nop]
13+
pipelines:
14+
metrics:
15+
receivers: [nop]
16+
exporters: [nop]
17+
traces:
18+
receivers: [nop]
19+
exporters: [nop]
20+
traces/1:
21+
receivers: [nop/1]
22+
exporters: [nop]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
receivers:
2+
nop:
3+
4+
exporters:
5+
nop:
6+
7+
service:
8+
pipelines:
9+
metrics:
10+
receivers: [nop]
11+
exporters: [nop]
12+
traces:
13+
receivers: [nop]
14+
exporters: [nop]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
receivers:
2+
nop:
3+
4+
exporters:
5+
nop:
6+
7+
service:
8+
pipelines:
9+
traces:
10+
receivers: [nop]
11+
exporters: [nop]

internal/merge/confmap/confmap.go

-22
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,10 @@
44
package confmap
55

66
import (
7-
"fmt"
8-
"os"
9-
"path/filepath"
10-
117
"github.com/knadh/koanf/maps"
128
"github.com/knadh/koanf/providers/confmap"
139
"github.com/knadh/koanf/v2"
1410
otelconfmap "go.opentelemetry.io/collector/confmap"
15-
"gopkg.in/yaml.v3"
1611
)
1712

1813
const (
@@ -48,20 +43,3 @@ func (c *Conf) mergeFromStringMap(data map[string]any) error {
4843
func (c *Conf) ToStringMap() map[string]any {
4944
return maps.Unflatten(c.k.All(), KeyDelimiter)
5045
}
51-
52-
func LoadFromFile(path string) (*Conf, error) {
53-
// Clean the path before using it.
54-
content, err := os.ReadFile(filepath.Clean(path))
55-
if err != nil {
56-
return nil, fmt.Errorf("unable to read the file %v: %w", path, err)
57-
}
58-
return LoadFromBytes(content)
59-
}
60-
61-
func LoadFromBytes(content []byte) (*Conf, error) {
62-
var rawConf map[string]any
63-
if err := yaml.Unmarshal(content, &rawConf); err != nil {
64-
return nil, err
65-
}
66-
return NewFromStringMap(rawConf), nil
67-
}

internal/merge/confmap/confmap_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestMerge(t *testing.T) {
5757
}
5858

5959
func mustLoadFromFile(t *testing.T, path string) *Conf {
60-
conf, err := LoadFromFile(path)
60+
conf, err := NewFileLoader(path).Load()
6161
require.NoError(t, err)
6262
return conf
6363
}

0 commit comments

Comments
 (0)