Skip to content

Commit ceb8edc

Browse files
committed
Add env var SPLUNK_CONFIG_YAML for storing configuration YAML
1 parent af39ed9 commit ceb8edc

File tree

6 files changed

+228
-76
lines changed

6 files changed

+228
-76
lines changed

cmd/otelcol/main.go

Lines changed: 116 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package main
1919

2020
import (
21+
"bytes"
2122
"fmt"
2223
"log"
2324
"os"
@@ -26,6 +27,7 @@ import (
2627

2728
"go.opentelemetry.io/collector/component"
2829
"go.opentelemetry.io/collector/service"
30+
"go.opentelemetry.io/collector/service/parserprovider"
2931
"go.uber.org/zap"
3032

3133
"github.com/signalfx/splunk-otel-collector/internal/components"
@@ -37,6 +39,7 @@ import (
3739
const (
3840
ballastEnvVarName = "SPLUNK_BALLAST_SIZE_MIB"
3941
configEnvVarName = "SPLUNK_CONFIG"
42+
configYamlEnvVarName = "SPLUNK_CONFIG_YAML"
4043
configServerEnabledEnvVar = "SPLUNK_DEBUG_CONFIG_SERVER"
4144
memLimitMiBEnvVarName = "SPLUNK_MEMORY_LIMIT_MIB"
4245
memTotalEnvVarName = "SPLUNK_MEMORY_TOTAL_MIB"
@@ -57,8 +60,7 @@ func main() {
5760
// TODO: Use same format as the collector
5861
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
5962

60-
args := os.Args[1:]
61-
if !contains(args, "-h") && !contains(args, "--help") {
63+
if !contains(os.Args[1:], "-h") && !contains(os.Args[1:], "--help") {
6264
checkRuntimeParams()
6365
}
6466

@@ -77,6 +79,7 @@ func main() {
7779
}
7880

7981
parserProvider := configprovider.NewConfigSourceParserProvider(
82+
newBaseParserProvider(),
8083
zap.NewNop(), // The service logger is not available yet, setting it to NoP.
8184
info,
8285
configsources.Get()...,
@@ -109,44 +112,27 @@ func contains(arr []string, str string) bool {
109112

110113
// Get the value of a key in an array
111114
// Support key/value with and with an equal sign
112-
func getKeyValue(args []string, argName string) string {
113-
val := ""
114-
for i, arg := range args {
115+
func getKeyValue(args []string, arg string) string {
116+
argEq := arg + "="
117+
for i := range args {
115118
switch {
116-
case strings.HasPrefix(arg, argName+"="):
117-
s := strings.Split(arg, "=")
118-
val = s[1]
119-
case arg == argName:
120-
i++
121-
val = args[i]
119+
case strings.HasPrefix(args[i], argEq):
120+
if s := strings.SplitN(args[i], "=", 2); len(s) == 2 {
121+
return s[1]
122+
}
123+
case args[i] == arg && i < (len(args)-1):
124+
return args[i+1]
122125
}
123126
}
124-
return val
127+
return ""
125128
}
126129

127130
// Check runtime parameters
128131
// Runtime parameters take priority over environment variables
129132
// Config and ballast flags are checked
130133
// Config and all memory env vars are checked
131134
func checkRuntimeParams() {
132-
args := os.Args[1:]
133-
config := ""
134-
135-
// Check if config flag was passed and its value differs from config env var.
136-
// If so, log that it will be used instead of env var value and set env var with that value.
137-
// This allows users to set `--config` and have it take priority when running from most contexts.
138-
cliConfig := getKeyValue(args, "--config")
139-
if cliConfig != "" {
140-
config = os.Getenv(configEnvVarName)
141-
if config != "" && config != cliConfig {
142-
log.Printf(
143-
"Both %v and '--config' were specified. Overriding %q environment variable value with %q for this session",
144-
configEnvVarName, config, cliConfig,
145-
)
146-
}
147-
os.Setenv(configEnvVarName, cliConfig)
148-
}
149-
setConfig()
135+
setValidConfigPath()
150136

151137
// Set default total memory
152138
memTotalSizeMiB := defaultMemoryTotalMiB
@@ -169,66 +155,119 @@ func checkRuntimeParams() {
169155
// Check if memory ballast flag was passed
170156
// If so, ensure memory ballast env var is not set
171157
// Then set memory ballast and limit properly
172-
ballastSize := getKeyValue(args, "--mem-ballast-size-mib")
158+
ballastSize := getKeyValue(os.Args[1:], "--mem-ballast-size-mib")
173159
if ballastSize != "" {
174-
config = os.Getenv(ballastEnvVarName)
175-
if config != "" {
176-
log.Fatalf("Both %v and '--config' were specified, but only one is allowed", ballastEnvVarName)
160+
if os.Getenv(ballastEnvVarName) != "" {
161+
log.Fatalf("Both %v and '--mem-ballast-size-mib' were specified, but only one is allowed", ballastEnvVarName)
177162
}
178163
os.Setenv(ballastEnvVarName, ballastSize)
179164
}
180165
setMemoryBallast(memTotalSizeMiB)
181166
setMemoryLimit(memTotalSizeMiB)
182167
}
183168

184-
// Validate and set the configuration
185-
func setConfig() {
186-
// Check if the config is specified via the env var.
187-
config := os.Getenv(configEnvVarName)
188-
// If not attempt to use a default config; supports Docker and local
189-
if config == "" {
190-
_, err := os.Stat(defaultDockerSAPMConfig)
191-
if err == nil {
192-
config = defaultDockerSAPMConfig
169+
// Validate then set config file path environment variable to specified config file path flag.
170+
// Validate then set the config file path flag to the specified config file path environment variable if not specified.
171+
// Calls setValidDefaultConfigPath to validate and set the config file path flag, config file path environment variable
172+
// to the default config file path if the config file path flag, config file path environment variable and config YAML
173+
// environment variable are not specified.
174+
func setValidConfigPath() {
175+
configPathFlag := getKeyValue(os.Args[1:], "--config")
176+
configPathVar := os.Getenv(configEnvVarName)
177+
configYaml := os.Getenv(configYamlEnvVarName)
178+
179+
if configPathFlag == "" && configPathVar == "" {
180+
if configYaml == "" {
181+
setValidDefaultConfigPath()
182+
} else {
183+
log.Printf("Using environment variable %s for configuration", configYamlEnvVarName)
193184
}
194-
_, err = os.Stat(defaultLocalSAPMConfig)
195-
if err == nil {
196-
config = defaultLocalSAPMConfig
185+
return
186+
}
187+
188+
if configPathFlag != "" {
189+
validateConfigPath(configPathFlag)
190+
191+
if configPathVar != "" && configPathVar != configPathFlag {
192+
log.Printf("Both %v and '--config' were specified. Overriding %q environment variable value with %q for this session", configEnvVarName, configPathVar, configPathFlag)
197193
}
198-
if config == "" {
199-
log.Fatalf("Unable to find the default configuration file, ensure %s environment variable is set properly", configEnvVarName)
194+
os.Setenv(configEnvVarName, configPathFlag)
195+
196+
if configYaml != "" {
197+
log.Printf("Both %s and '--config' were specified. Using '--config' value %s for this session", configYamlEnvVarName, configPathFlag)
200198
}
201-
} else {
202-
// Check if file exists.
203-
_, err := os.Stat(config)
204-
if err != nil {
205-
log.Fatalf("Unable to find the configuration file (%s) ensure %s environment variable is set properly", config, configEnvVarName)
199+
200+
log.Printf("Set config to %v", configPathFlag)
201+
return
202+
}
203+
204+
if configPathVar != "" {
205+
validateConfigPath(configPathVar)
206+
207+
if !contains(os.Args[1:], "--config") {
208+
// Inject the command line flag that controls the configuration.
209+
os.Args = append(os.Args, "--config="+configPathVar)
210+
}
211+
212+
if configYaml != "" {
213+
log.Printf("Both %s and %s were specified. Using %s environment variable value %s for this session", configEnvVarName, configYamlEnvVarName, configEnvVarName, configPathVar)
206214
}
215+
216+
log.Printf("Set config to %v", configPathVar)
217+
}
218+
}
219+
220+
// Validate and set the config file path flag and config file path environment variable to the default config file path.
221+
// Only Docker and local configuration supported.
222+
func setValidDefaultConfigPath() {
223+
var path string
224+
var err error
225+
226+
if _, err = os.Stat(defaultDockerSAPMConfig); err == nil {
227+
path = defaultDockerSAPMConfig
207228
}
208229

209-
switch config {
230+
if _, err = os.Stat(defaultLocalSAPMConfig); err == nil {
231+
path = defaultLocalSAPMConfig
232+
}
233+
234+
if path == "" {
235+
log.Fatalf("Unable to find the default configuration file, ensure %s environment variable is set properly", configEnvVarName)
236+
}
237+
238+
validateConfigPath(path)
239+
240+
os.Setenv(configEnvVarName, path)
241+
242+
if !contains(os.Args[1:], "--config") {
243+
// Inject the command line flag that controls the configuration.
244+
os.Args = append(os.Args, "--config="+path)
245+
}
246+
247+
log.Printf("Set config to %v", path)
248+
}
249+
250+
func validateConfigPath(path string) {
251+
// Check if the config file exists.
252+
if _, err := os.Stat(path); err != nil {
253+
log.Fatalf("Unable to find the configuration file (%s) ensure %s environment variable is set properly", path, configEnvVarName)
254+
}
255+
256+
// Check environment variables required by default configuration.
257+
switch path {
210258
case
211259
defaultDockerSAPMConfig,
212260
defaultDockerOTLPConfig,
213261
defaultLocalSAPMConfig,
214262
defaultLocalOTLPConfig:
215-
// The following environment variables are required.
216-
// If any are missing stop here.
217263
requiredEnvVars := []string{realmEnvVarName, tokenEnvVarName}
218264
for _, v := range requiredEnvVars {
219265
if len(os.Getenv(v)) == 0 {
220266
log.Printf("Usage: %s=12345 %s=us0 %s", tokenEnvVarName, realmEnvVarName, os.Args[0])
221-
log.Fatalf("ERROR: Missing required environment variable %s with default config path %s", v, config)
267+
log.Fatalf("ERROR: Missing required environment variable %s with default config path %s", v, path)
222268
}
223269
}
224270
}
225-
226-
args := os.Args[1:]
227-
if !contains(args, "--config") {
228-
// Inject the command line flag that controls the configuration.
229-
os.Args = append(os.Args, "--config="+config)
230-
}
231-
log.Printf("Set config to %v", config)
232271
}
233272

234273
// Validate and set the memory ballast
@@ -287,6 +326,19 @@ func setMemoryLimit(memTotalSizeMiB int) {
287326
log.Printf("Set memory limit to %d MiB", memLimit)
288327
}
289328

329+
// Returns a ParserProvider that reads configuration YAML from an environment variable when applicable.
330+
func newBaseParserProvider() parserprovider.ParserProvider {
331+
configPathFlag := getKeyValue(os.Args[1:], "--config")
332+
configPathVar := os.Getenv(configEnvVarName)
333+
configYaml := os.Getenv(configYamlEnvVarName)
334+
335+
if configPathFlag == "" && configPathVar == "" && configYaml != "" {
336+
return parserprovider.NewInMemory(bytes.NewBufferString(configYaml))
337+
}
338+
339+
return parserprovider.Default()
340+
}
341+
290342
func runInteractive(params service.CollectorSettings) error {
291343
app, err := service.New(params)
292344
if err != nil {

cmd/otelcol/main_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestGetKeyValue(t *testing.T) {
6060
func TestCheckRuntimeParams(*testing.T) {
6161
oldArgs := os.Args
6262
os.Setenv(configEnvVarName, "../../"+defaultLocalSAPMConfig)
63-
setConfig()
63+
setValidConfigPath()
6464
os.Unsetenv(configEnvVarName)
6565
checkRuntimeParams()
6666

@@ -101,7 +101,7 @@ func TestUseConfigFromEnvVar(t *testing.T) {
101101
os.Setenv(tokenEnvVarName, "12345")
102102
os.Setenv(realmEnvVarName, "us0")
103103
os.Setenv(configEnvVarName, "../../"+defaultLocalSAPMConfig)
104-
setConfig()
104+
setValidConfigPath()
105105

106106
args := os.Args[1:]
107107
c := getKeyValue(args, "--config")

docs/getting-started/linux-manual.md

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,21 @@ $ docker run --rm -e SPLUNK_ACCESS_TOKEN=12345 -e SPLUNK_REALM=us0 \
139139
140140
### Custom Configuration
141141

142-
When changes to the default configuration file is needed, a custom
143-
configuration file can specified and used instead. Use the `SPLUNK_CONFIG`
144-
environment variable or the `--config` command line argument to provide a
145-
custom configuration.
142+
When changes to the default configuration YAML file are needed, create a
143+
custom configuration file. Use environment variable `SPLUNK_CONFIG` or
144+
command line argument `--config` to provide the path to this file.
145+
146+
Also, you can use environment variable `SPLUNK_CONFIG_YAML` to specify
147+
your custom configuration YAML at the command line. This is useful in
148+
environments where access to the underlying file system is not readily
149+
available. For example, in AWS Fargate you can store your custom configuration
150+
YAML in a parameter in AWS Systems Manager Parameter Store, then in
151+
your container definition specify `SPLUNK_CONFIG_YAML` to get the
152+
configuration from the parameter.
146153

147154
> Command line arguments take precedence over environment variables. This
148-
> applies to `--config` and `--mem-ballast-size-mib`.
155+
> applies to `--config` and `--mem-ballast-size-mib`. `SPLUNK_CONFIG`
156+
> takes precedence over `SPLUNK_CONFIG_YAML`
149157
150158
For example in Docker:
151159

@@ -160,11 +168,41 @@ $ docker run --rm -e SPLUNK_ACCESS_TOKEN=12345 -e SPLUNK_REALM=us0 \
160168

161169
> Use of a SemVer tag over `latest` is highly recommended.
162170
163-
In the case of Docker, a volume mount may be required to load custom
164-
configuration as shown above.
171+
In the case of Docker, a volume mount may be required to load the custom
172+
configuration file, as shown above.
165173

166-
If the custom configuration includes a `memory_limiter` processor then the
174+
If the custom configuration includes a `memory_limiter` processor, then the
167175
`ballast_size_mib` parameter should be the same as the
168176
`SPLUNK_BALLAST_SIZE_MIB` environment variable. See
169177
[gateway_config.yaml](../../cmd/otelcol/config/collector/gateway_config.yaml)
170178
as an example.
179+
180+
The following example shows the `SPLUNK_CONFIG_YAML` environment variable:
181+
```bash
182+
$ CONFIG_YAML=$(cat <<-END
183+
receivers:
184+
hostmetrics:
185+
collection_interval: 1s
186+
scrapers:
187+
cpu:
188+
exporters:
189+
logging:
190+
logLevel: debug
191+
service:
192+
pipelines:
193+
metrics:
194+
receivers: [hostmetrics]
195+
exporters: [logging]
196+
END
197+
)
198+
199+
$ docker run --rm \
200+
-e SPLUNK_CONFIG_YAML=${CONFIG_YAML} \
201+
--name otelcol quay.io/signalfx/splunk-otel-collector:latest
202+
```
203+
The configuration YAML above is for collecting and logging cpu
204+
metrics. The YAML is assigned to parameter `CONFIG_YAML` for
205+
convenience in the first command. In the subsequent `docker run`
206+
command, parameter `CONFIG_YAML` is expanded and assigned to
207+
environment variable `SPLUNK_CONFIG_YAML`. Note that YAML
208+
requires whitespace indentation to be maintained.

internal/configprovider/config_source_provider.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ type configSourceParserProvider struct {
3737
}
3838

3939
// NewConfigSourceParserProvider creates a ParserProvider that uses config sources.
40-
func NewConfigSourceParserProvider(logger *zap.Logger, buildInfo component.BuildInfo, factories ...Factory) parserprovider.ParserProvider {
40+
func NewConfigSourceParserProvider(pp parserprovider.ParserProvider, logger *zap.Logger, buildInfo component.BuildInfo, factories ...Factory) parserprovider.ParserProvider {
41+
if pp == nil {
42+
pp = parserprovider.Default()
43+
}
4144
return &configSourceParserProvider{
42-
pp: parserprovider.Default(),
45+
pp: pp,
4346
logger: logger,
4447
factories: factories,
4548
buildInfo: buildInfo,

internal/configprovider/config_source_provider_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func TestConfigSourceParserProvider(t *testing.T) {
8888
}
8989

9090
pp := NewConfigSourceParserProvider(
91+
parserprovider.Default(),
9192
zap.NewNop(),
9293
component.DefaultBuildInfo(),
9394
factories...,

0 commit comments

Comments
 (0)