Skip to content

Commit c3b2bb3

Browse files
feat: process all config fragments as golang templates (#114)
### Description OB-37310 Process all config fragments as golang templates Replaces #100 and #111 ### Checklist - [x] Created tests which fail without the change (if possible) - [ ] Extended the README / documentation, if necessary Co-authored-by: Alex Lew <[email protected]>
1 parent 0d2a488 commit c3b2bb3

File tree

19 files changed

+925
-45
lines changed

19 files changed

+925
-45
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ require (
1111
github.com/spf13/viper v1.20.0-alpha.6
1212
github.com/stretchr/testify v1.9.0
1313
go.opentelemetry.io/collector/otelcol v0.110.0
14+
go.uber.org/zap v1.27.0
1415
golang.org/x/sys v0.25.0
1516
gopkg.in/yaml.v2 v2.4.0
17+
gopkg.in/yaml.v3 v3.0.1
1618
)
1719

1820
require (
@@ -337,7 +339,6 @@ require (
337339
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
338340
go.uber.org/atomic v1.11.0 // indirect
339341
go.uber.org/multierr v1.11.0 // indirect
340-
go.uber.org/zap v1.27.0 // indirect
341342
golang.org/x/crypto v0.27.0 // indirect
342343
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
343344
golang.org/x/mod v0.19.0 // indirect
@@ -357,7 +358,6 @@ require (
357358
gopkg.in/inf.v0 v0.9.1 // indirect
358359
gopkg.in/ini.v1 v1.67.0 // indirect
359360
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
360-
gopkg.in/yaml.v3 v3.0.1 // indirect
361361
k8s.io/api v0.31.1 // indirect
362362
k8s.io/apimachinery v0.31.1 // indirect
363363
k8s.io/client-go v0.31.1 // indirect

internal/commands/start/start.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,61 @@ Copyright © 2024 NAME HERE <EMAIL ADDRESS>
44
package start
55

66
import (
7+
"context"
78
"os"
89

10+
logger "github.com/observeinc/observe-agent/internal/commands/util"
911
"github.com/observeinc/observe-agent/internal/config"
12+
"github.com/observeinc/observe-agent/internal/connections"
1013
"github.com/observeinc/observe-agent/internal/root"
1114
"github.com/observeinc/observe-agent/observecol"
1215
"github.com/spf13/cobra"
1316
"github.com/spf13/viper"
17+
collector "go.opentelemetry.io/collector/otelcol"
1418
)
1519

20+
func SetupAndGenerateCollectorSettings() (*collector.CollectorSettings, func(), error) {
21+
ctx := logger.WithCtx(context.Background(), logger.Get())
22+
// Set Env Vars from config
23+
err := config.SetEnvVars()
24+
if err != nil {
25+
return nil, nil, err
26+
}
27+
// Set up our temp dir annd temp config files
28+
tmpDir, err := os.MkdirTemp("", connections.TempFilesFolder)
29+
if err != nil {
30+
return nil, nil, err
31+
}
32+
configFilePaths, overridePath, err := config.GetAllOtelConfigFilePaths(ctx, tmpDir)
33+
cleanup := func() {
34+
if overridePath != "" {
35+
os.Remove(overridePath)
36+
}
37+
os.RemoveAll(tmpDir)
38+
}
39+
if err != nil {
40+
cleanup()
41+
return nil, nil, err
42+
}
43+
// Generate collector settings with all config files
44+
colSettings := observecol.GenerateCollectorSettings(configFilePaths)
45+
return colSettings, cleanup, nil
46+
}
47+
1648
var startCmd = &cobra.Command{
1749
Use: "start",
1850
Short: "Start the Observe agent process.",
1951
Long: `The Observe agent is based on the OpenTelemetry Collector.
2052
This command reads in the local config and env vars and starts the
2153
collector on the current host.`,
2254
RunE: func(cmd *cobra.Command, args []string) error {
23-
// Set Env Vars from config
24-
err := config.SetEnvVars()
55+
colSettings, cleanup, err := SetupAndGenerateCollectorSettings()
2556
if err != nil {
2657
return err
2758
}
28-
//
29-
configFilePaths, overridePath, err := config.GetAllOtelConfigFilePaths()
30-
if err != nil {
31-
return err
32-
}
33-
if overridePath != "" {
34-
defer os.Remove(overridePath)
59+
if cleanup != nil {
60+
defer cleanup()
3561
}
36-
// Generate collector settings with all config files
37-
colSettings := observecol.GenerateCollectorSettings(configFilePaths)
3862
otelCmd := observecol.GetOtelCollectorCommand(colSettings)
3963
return otelCmd.RunE(cmd, args)
4064
},

internal/commands/util/logger.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package logger
2+
3+
import (
4+
"context"
5+
6+
"go.uber.org/zap"
7+
)
8+
9+
type ctxKey struct{}
10+
11+
func Get() *zap.Logger {
12+
logger, _ := zap.NewProduction()
13+
return logger
14+
}
15+
16+
func GetDev() *zap.Logger {
17+
logger, _ := zap.NewDevelopment()
18+
return logger
19+
}
20+
21+
func FromCtx(ctx context.Context) *zap.Logger {
22+
if l, ok := ctx.Value(ctxKey{}).(*zap.Logger); ok {
23+
return l
24+
}
25+
26+
return Get()
27+
}
28+
29+
func WithCtx(ctx context.Context, l *zap.Logger) context.Context {
30+
if lp, ok := ctx.Value(ctxKey{}).(*zap.Logger); ok {
31+
if lp == l {
32+
return ctx
33+
}
34+
}
35+
36+
return context.WithValue(ctx, ctxKey{}, l)
37+
}

internal/config/confighandler.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"context"
45
"fmt"
56
"net/url"
67
"os"
@@ -11,14 +12,18 @@ import (
1112
"github.com/spf13/viper"
1213
)
1314

14-
func GetAllOtelConfigFilePaths() ([]string, string, error) {
15+
func GetAllOtelConfigFilePaths(ctx context.Context, tmpDir string) ([]string, string, error) {
1516
// Initialize config file paths with base config
1617
configFilePaths := []string{filepath.Join(GetDefaultConfigFolder(), "otel-collector.yaml")}
1718
var err error
1819
// Get additional config paths based on connection configs
1920
for _, conn := range connections.AllConnectionTypes {
2021
if viper.IsSet(conn.Name) {
21-
configFilePaths = append(configFilePaths, conn.GetConfigFilePaths()...)
22+
connectionPaths, err := conn.GetConfigFilePaths(ctx, tmpDir)
23+
if err != nil {
24+
return nil, "", err
25+
}
26+
configFilePaths = append(configFilePaths, connectionPaths...)
2227
}
2328
}
2429
// Read in otel-config flag and add to paths if set

internal/connections/connections.go

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package connections
22

33
import (
4+
"context"
5+
"fmt"
46
"os"
57
"path/filepath"
68
"runtime"
9+
"text/template"
710

11+
logger "github.com/observeinc/observe-agent/internal/commands/util"
812
"github.com/spf13/viper"
13+
"go.uber.org/zap"
914
)
1015

16+
var TempFilesFolder = "observe-agent"
17+
1118
type ConfigFieldHandler interface {
1219
GenerateCollectorConfigFragment() interface{}
1320
}
@@ -20,6 +27,10 @@ type CollectorConfigFragment struct {
2027
type ConnectionType struct {
2128
Name string
2229
ConfigFields []CollectorConfigFragment
30+
Type string
31+
32+
configFolderPath string
33+
getConfig func() *viper.Viper
2334
}
2435

2536
func GetConfigFolderPath() string {
@@ -39,23 +50,111 @@ func GetConfigFolderPath() string {
3950
}
4051
}
4152

42-
func (c ConnectionType) GetConfigFilePaths() []string {
43-
var rawConnConfig = viper.Sub(c.Name)
44-
configPaths := make([]string, 0)
45-
if rawConnConfig == nil || !rawConnConfig.GetBool("enabled") {
46-
return configPaths
53+
func (c *ConnectionType) GetTemplateFilepath(tplFilename string) string {
54+
return filepath.Join(c.configFolderPath, c.Name, tplFilename)
55+
}
56+
57+
func (c *ConnectionType) RenderConfigTemplate(ctx context.Context, tmpDir string, tplFilename string, confValues any) (string, error) {
58+
tplPath := c.GetTemplateFilepath(tplFilename)
59+
tmpl, err := template.New("").Funcs(GetTemplateFuncMap()).ParseFiles(tplPath)
60+
if err != nil {
61+
logger.FromCtx(ctx).Error("failed to parse config fragment template", zap.String("file", tplPath), zap.Error(err))
62+
return "", err
4763
}
64+
f, err := os.CreateTemp(tmpDir, fmt.Sprintf("*-%s", tplFilename))
65+
if err != nil {
66+
logger.FromCtx(ctx).Error("failed to create temporary config fragment file", zap.String("file", tplPath), zap.Error(err))
67+
return "", err
68+
}
69+
err = tmpl.ExecuteTemplate(f, tplFilename, confValues)
70+
if err != nil {
71+
logger.FromCtx(ctx).Error("failed to execute config fragment template", zap.String("file", tplPath), zap.Error(err))
72+
return "", err
73+
}
74+
return f.Name(), nil
75+
}
76+
77+
func (c *ConnectionType) ProcessConfigFields(ctx context.Context, tmpDir string, rawConnConfig *viper.Viper, confValues any) ([]string, error) {
78+
paths := make([]string, 0)
4879
for _, field := range c.ConfigFields {
4980
val := rawConnConfig.GetBool(field.configYAMLPath)
5081
if val && field.colConfigFilePath != "" {
51-
configPath := filepath.Join(GetConfigFolderPath(), c.Name, field.colConfigFilePath)
52-
configPaths = append(configPaths, configPath)
82+
configPath, err := c.RenderConfigTemplate(ctx, tmpDir, field.colConfigFilePath, confValues)
83+
if err != nil {
84+
return nil, err
85+
}
86+
paths = append(paths, configPath)
87+
}
88+
}
89+
return paths, nil
90+
}
91+
92+
func (c *ConnectionType) GetConfigFilePaths(ctx context.Context, tmpDir string) ([]string, error) {
93+
var rawConnConfig = c.getConfig()
94+
var configPaths []string
95+
if rawConnConfig == nil || !rawConnConfig.GetBool("enabled") {
96+
return configPaths, nil
97+
}
98+
switch c.Type {
99+
case SelfMonitoringConnectionTypeName:
100+
conf := &SelfMonitoringConfig{}
101+
err := rawConnConfig.Unmarshal(conf)
102+
if err != nil {
103+
logger.FromCtx(ctx).Error("failed to unmarshal config", zap.String("connection", c.Name))
104+
return nil, err
105+
}
106+
configPaths, err = c.ProcessConfigFields(ctx, tmpDir, rawConnConfig, conf)
107+
if err != nil {
108+
return nil, err
53109
}
110+
case HostMonitoringConnectionTypeName:
111+
conf := &HostMonitoringConfig{}
112+
err := rawConnConfig.Unmarshal(conf)
113+
if err != nil {
114+
logger.FromCtx(ctx).Error("failed to unmarshal config", zap.String("connection", c.Name))
115+
return nil, err
116+
}
117+
configPaths, err = c.ProcessConfigFields(ctx, tmpDir, rawConnConfig, conf)
118+
if err != nil {
119+
return nil, err
120+
}
121+
default:
122+
logger.FromCtx(ctx).Error("unknown connection type", zap.String("type", c.Type))
123+
return nil, fmt.Errorf("unknown connection type %s", c.Type)
124+
}
125+
return configPaths, nil
126+
}
127+
128+
type ConnectionTypeOption func(*ConnectionType)
129+
130+
func MakeConnectionType(Name string, ConfigFields []CollectorConfigFragment, Type string, opts ...ConnectionTypeOption) *ConnectionType {
131+
var c = &ConnectionType{Name: Name, ConfigFields: ConfigFields, Type: Type}
132+
c.getConfig = func() *viper.Viper {
133+
return viper.Sub(c.Name)
134+
}
135+
c.configFolderPath = GetConfigFolderPath()
136+
137+
// Apply provided options
138+
for _, opt := range opts {
139+
opt(c)
140+
}
141+
142+
return c
143+
}
144+
145+
func WithConfigFolderPath(configFolderPath string) ConnectionTypeOption {
146+
return func(c *ConnectionType) {
147+
c.configFolderPath = configFolderPath
148+
}
149+
}
150+
151+
func WithGetConfig(getConfig func() *viper.Viper) ConnectionTypeOption {
152+
return func(c *ConnectionType) {
153+
c.getConfig = getConfig
54154
}
55-
return configPaths
56155
}
57156

58157
var AllConnectionTypes = []*ConnectionType{
59-
&HostMonitoringConnectionType,
60-
&SelfMonitoringConnectionType,
158+
HostMonitoringConnectionType,
159+
SelfMonitoringConnectionType,
61160
}

0 commit comments

Comments
 (0)