Skip to content

Commit 08ac21a

Browse files
[OTel] Introduce otel subcommand (#4159)
* Added otel subcommand * test update * lint * notice * added unit tests
1 parent d8b446f commit 08ac21a

File tree

14 files changed

+400
-154
lines changed

14 files changed

+400
-154
lines changed

NOTICE.txt

+38-38
Original file line numberDiff line numberDiff line change
@@ -6139,6 +6139,44 @@ Contents of probable licence file $GOMODCACHE/github.com/spf13/cobra@v1.8.0/LICE
61396139
of your accepting any such warranty or additional liability.
61406140

61416141

6142+
--------------------------------------------------------------------------------
6143+
Dependency : github.com/spf13/pflag
6144+
Version: v1.0.5
6145+
Licence type (autodetected): BSD-3-Clause
6146+
--------------------------------------------------------------------------------
6147+
6148+
Contents of probable licence file $GOMODCACHE/github.com/spf13/pflag@v1.0.5/LICENSE:
6149+
6150+
Copyright (c) 2012 Alex Ogier. All rights reserved.
6151+
Copyright (c) 2012 The Go Authors. All rights reserved.
6152+
6153+
Redistribution and use in source and binary forms, with or without
6154+
modification, are permitted provided that the following conditions are
6155+
met:
6156+
6157+
* Redistributions of source code must retain the above copyright
6158+
notice, this list of conditions and the following disclaimer.
6159+
* Redistributions in binary form must reproduce the above
6160+
copyright notice, this list of conditions and the following disclaimer
6161+
in the documentation and/or other materials provided with the
6162+
distribution.
6163+
* Neither the name of Google Inc. nor the names of its
6164+
contributors may be used to endorse or promote products derived from
6165+
this software without specific prior written permission.
6166+
6167+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6168+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6169+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6170+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
6171+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
6172+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
6173+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
6174+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
6175+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
6176+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
6177+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
6178+
6179+
61426180
--------------------------------------------------------------------------------
61436181
Dependency : github.com/stretchr/testify
61446182
Version: v1.8.4
@@ -26663,44 +26701,6 @@ Contents of probable licence file $GOMODCACHE/github.com/spf13/afero@v1.9.5/LICE
2666326701
of your accepting any such warranty or additional liability.
2666426702

2666526703

26666-
--------------------------------------------------------------------------------
26667-
Dependency : github.com/spf13/pflag
26668-
Version: v1.0.5
26669-
Licence type (autodetected): BSD-3-Clause
26670-
--------------------------------------------------------------------------------
26671-
26672-
Contents of probable licence file $GOMODCACHE/github.com/spf13/pflag@v1.0.5/LICENSE:
26673-
26674-
Copyright (c) 2012 Alex Ogier. All rights reserved.
26675-
Copyright (c) 2012 The Go Authors. All rights reserved.
26676-
26677-
Redistribution and use in source and binary forms, with or without
26678-
modification, are permitted provided that the following conditions are
26679-
met:
26680-
26681-
* Redistributions of source code must retain the above copyright
26682-
notice, this list of conditions and the following disclaimer.
26683-
* Redistributions in binary form must reproduce the above
26684-
copyright notice, this list of conditions and the following disclaimer
26685-
in the documentation and/or other materials provided with the
26686-
distribution.
26687-
* Neither the name of Google Inc. nor the names of its
26688-
contributors may be used to endorse or promote products derived from
26689-
this software without specific prior written permission.
26690-
26691-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26692-
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26693-
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26694-
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26695-
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26696-
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26697-
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26698-
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26699-
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26700-
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26701-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26702-
26703-
2670426704
--------------------------------------------------------------------------------
2670526705
Dependency : github.com/stretchr/objx
2670626706
Version: v0.5.0

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ require (
5454
github.com/shirou/gopsutil/v3 v3.23.12
5555
github.com/sirupsen/logrus v1.9.3
5656
github.com/spf13/cobra v1.8.0
57+
github.com/spf13/pflag v1.0.5
5758
github.com/stretchr/testify v1.8.4
5859
github.com/tsg/go-daemon v0.0.0-20200207173439-e704b93fd89b
5960
go.elastic.co/apm/module/apmgorilla v1.15.0
@@ -187,7 +188,6 @@ require (
187188
github.com/sergi/go-diff v1.2.0 // indirect
188189
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
189190
github.com/shoenig/go-m1cpu v0.1.6 // indirect
190-
github.com/spf13/pflag v1.0.5 // indirect
191191
github.com/stretchr/objx v0.5.0 // indirect
192192
github.com/tklauser/go-sysconf v0.3.12 // indirect
193193
github.com/tklauser/numcpus v0.6.1 // indirect

internal/pkg/agent/application/application.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ func New(
134134

135135
// testing mode uses a config manager that takes configuration from over the control protocol
136136
configMgr = newTestingModeConfigManager(log)
137+
} else if runAsOtel {
138+
// ignoring configuration in elastic-agent.yml
139+
configMgr = otel.NewOtelModeConfigManager()
137140
} else if configuration.IsStandalone(cfg.Fleet) {
138141
log.Info("Parsed configuration and determined agent is managed locally")
139142

@@ -146,9 +149,6 @@ func New(
146149
log.Debugf("Reloading of configuration is on, frequency is set to %s", cfg.Settings.Reload.Period)
147150
configMgr = newPeriodic(log, cfg.Settings.Reload.Period, discover, loader)
148151
}
149-
} else if runAsOtel {
150-
// ignoring configuration in elastic-agent.yml
151-
configMgr = otel.NewOtelModeConfigManager()
152152
} else {
153153
isManaged = true
154154
var store storage.Store

internal/pkg/agent/application/paths/common.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
const (
2525
// DefaultConfigName is the default name of the configuration file.
2626
DefaultConfigName = "elastic-agent.yml"
27+
// DefaultOtelConfigName is the default name of the otel configuration file.
28+
DefaultOtelConfigName = "otel.yml"
2729

2830
// AgentLockFileName is the name of the overall Elastic Agent file lock.
2931
AgentLockFileName = "agent.lock"
@@ -151,8 +153,19 @@ func SetConfig(path string) {
151153

152154
// ConfigFile returns the path to the configuration file.
153155
func ConfigFile() string {
156+
157+
return configFileWithDefaultOverride(DefaultConfigName)
158+
}
159+
160+
// OtelConfigFile returns the path to the otel configuration file.
161+
func OtelConfigFile() string {
162+
return configFileWithDefaultOverride(DefaultOtelConfigName)
163+
}
164+
165+
// configFileWithDefaultOverride returns the path to the configuration file overriding default value.
166+
func configFileWithDefaultOverride(defaultConfig string) string {
154167
if configFilePath == "" || configFilePath == DefaultConfigName {
155-
return filepath.Join(Config(), DefaultConfigName)
168+
return filepath.Join(Config(), defaultConfig)
156169
}
157170
if filepath.IsAbs(configFilePath) {
158171
return configFilePath

internal/pkg/agent/cmd/common.go

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func NewCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command {
8181
cmd.AddCommand(newDiagnosticsCommand(args, streams))
8282
cmd.AddCommand(newComponentCommandWithArgs(args, streams))
8383
cmd.AddCommand(newLogsCommandWithArgs(args, streams))
84+
cmd.AddCommand(newOtelCommandWithArgs(args, streams))
8485

8586
// windows special hidden sub-command (only added on Windows)
8687
reexec := newReExecWindowsCommand(args, streams)

internal/pkg/agent/cmd/otel.go

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package cmd
6+
7+
import (
8+
"context"
9+
"sync"
10+
11+
"github.com/hashicorp/go-multierror"
12+
"github.com/spf13/cobra"
13+
"github.com/spf13/pflag"
14+
15+
"github.com/elastic/elastic-agent-libs/service"
16+
"github.com/elastic/elastic-agent/internal/pkg/agent/errors"
17+
"github.com/elastic/elastic-agent/internal/pkg/cli"
18+
"github.com/elastic/elastic-agent/internal/pkg/otel"
19+
)
20+
21+
const (
22+
configFlagName = "config"
23+
setFlagName = "set"
24+
)
25+
26+
func newOtelCommandWithArgs(_ []string, _ *cli.IOStreams) *cobra.Command {
27+
cmd := &cobra.Command{
28+
Use: "otel",
29+
Short: "Start the Elastic Agent in otel mode",
30+
Long: "This command starts the Elastic Agent in otel mode.",
31+
RunE: func(cmd *cobra.Command, _ []string) error {
32+
cfgFiles, err := getConfigFiles(cmd)
33+
if err != nil {
34+
return err
35+
}
36+
return runCollector(cmd.Context(), cfgFiles)
37+
},
38+
PreRun: func(c *cobra.Command, args []string) {
39+
// hide inherited flags not to bloat help with flags not related to otel
40+
hideInheritedFlags(c)
41+
},
42+
SilenceUsage: true,
43+
SilenceErrors: true,
44+
}
45+
46+
cmd.SetHelpFunc(func(c *cobra.Command, s []string) {
47+
hideInheritedFlags(c)
48+
c.Parent().HelpFunc()(c, s)
49+
})
50+
51+
cmd.Flags().StringArray(configFlagName, []string{}, "Locations to the config file(s), note that only a"+
52+
" single location can be set per flag entry e.g. `--config=file:/path/to/first --config=file:path/to/second`.")
53+
54+
cmd.Flags().StringArray(setFlagName, []string{}, "Set arbitrary component config property. The component has to be defined in the config file and the flag"+
55+
" has a higher precedence. Array config properties are overridden and maps are joined. Example --set=processors.batch.timeout=2s")
56+
return cmd
57+
}
58+
59+
func hideInheritedFlags(c *cobra.Command) {
60+
c.InheritedFlags().VisitAll(func(f *pflag.Flag) {
61+
f.Hidden = true
62+
})
63+
}
64+
65+
func runCollector(cmdCtx context.Context, configFiles []string) error {
66+
// Windows: Mark service as stopped.
67+
// After this is run, the service is considered by the OS to be stopped.
68+
// This must be the first deferred cleanup task (last to execute).
69+
defer func() {
70+
service.NotifyTermination()
71+
service.WaitExecutionDone()
72+
}()
73+
74+
service.BeforeRun()
75+
defer service.Cleanup()
76+
77+
stop := make(chan bool)
78+
ctx, cancel := context.WithCancel(cmdCtx)
79+
80+
var stopCollector = func() {
81+
close(stop)
82+
}
83+
84+
defer cancel()
85+
go service.ProcessWindowsControlEvents(stopCollector)
86+
87+
var otelStartWg sync.WaitGroup
88+
var resErr error
89+
var awaiters awaiters
90+
91+
otelAwaiter := make(chan struct{})
92+
awaiters = append(awaiters, otelAwaiter)
93+
94+
otelStartWg.Add(1)
95+
go func() {
96+
otelStartWg.Done()
97+
if err := otel.Run(ctx, stop, configFiles); err != nil {
98+
resErr = multierror.Append(resErr, err)
99+
// otel collector finished with an error, exit run loop
100+
cancel()
101+
}
102+
103+
// close awaiter handled in run loop
104+
close(otelAwaiter)
105+
}()
106+
107+
// wait for otel to start
108+
otelStartWg.Wait()
109+
110+
if err := runElasticAgent(
111+
ctx,
112+
cancel,
113+
nil, // no config overrides
114+
stop, // service hook
115+
false, // not in testing mode
116+
0, // no fleet config
117+
true, // is otel mode
118+
awaiters, // wait for otel to finish
119+
); err != nil && !errors.Is(err, context.Canceled) {
120+
resErr = multierror.Append(resErr, err)
121+
}
122+
123+
return resErr
124+
125+
}

internal/pkg/agent/cmd/otel_flags.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package cmd
6+
7+
import (
8+
"fmt"
9+
"strings"
10+
11+
"github.com/spf13/cobra"
12+
13+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
14+
)
15+
16+
func getConfigFiles(cmd *cobra.Command) ([]string, error) {
17+
configFiles, err := cmd.Flags().GetStringArray(configFlagName)
18+
if err != nil {
19+
return nil, fmt.Errorf("failed to retrieve config flags: %w", err)
20+
}
21+
22+
if len(configFiles) == 0 {
23+
configFiles = append(configFiles, paths.OtelConfigFile())
24+
}
25+
26+
setVals, err := cmd.Flags().GetStringArray(setFlagName)
27+
if err != nil {
28+
return nil, fmt.Errorf("failed to retrieve set flags: %w", err)
29+
}
30+
31+
sets, err := getSets(setVals)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
configFiles = append(configFiles, sets...)
37+
return configFiles, nil
38+
}
39+
40+
func getSets(setVals []string) ([]string, error) {
41+
var sets []string
42+
for _, s := range setVals {
43+
idx := strings.Index(s, "=")
44+
if idx == -1 {
45+
return nil, fmt.Errorf("missing equal sign for set value %q", s)
46+
}
47+
sets = append(sets, setToYaml(s, idx))
48+
}
49+
return sets, nil
50+
}
51+
52+
func setToYaml(set string, eqIdx int) string {
53+
if len(set) == 0 {
54+
return set
55+
}
56+
return "yaml:" + strings.TrimSpace(strings.ReplaceAll(set[:eqIdx], ".", "::")) + ": " + strings.TrimSpace(set[eqIdx+1:])
57+
}

0 commit comments

Comments
 (0)