Skip to content

feat: add 'config validate' command to diagnose config issues without other checks #232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions internal/commands/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/observeinc/observe-agent/internal/commands/util/logger"
"github.com/observeinc/observe-agent/internal/root"
"github.com/observeinc/observe-agent/observecol"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -41,7 +42,32 @@ bundled OTel configuration.`,
},
}

var configValidateCmd = &cobra.Command{
Use: "validate",
Short: "Validates the configuration for this agent.",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := logger.WithCtx(context.Background(), logger.GetNop())
col, cleanup, err := observecol.GetOtelCollector(ctx)
if cleanup != nil {
defer cleanup()
}
if err != nil {
fmt.Fprintln(os.Stderr, "❌ failed to generate config")
return err
}
err = col.DryRun(ctx)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a built in command? that's cool

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is! The vanilla otelcol has this validate command. We have this exact same call in diagnose as well, but I figured having a simple config check was valuable in addition to the full suite (especially for the helm chart case).

if err != nil {
fmt.Fprintln(os.Stderr, "❌ invalid config")
return err
}
fmt.Fprintln(os.Stderr, "✅ configuration is valid")
return nil
},
}

func init() {
configCmd.AddCommand(configValidateCmd)
configCmd.Flags().Bool("render-otel-details", false, "Print the full resolved otel configuration including default values after the otel components perform their semantic processing.")
configCmd.Flags().Bool("render-otel", false, "Print a single rendered otel configuration file. This file is equivalent to the bundled configuration enabled in the observe-agent config.")
root.RootCmd.AddCommand(configCmd)
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func runSnapshotTest(t *testing.T, test snapshotTest) {
assert.True(t, ok)
curPath := path.Dir(filename)

// Set the template base dir for all connections
// Set the template overrides for all connections
for _, conn := range connections.AllConnectionTypes {
conn.ApplyOptions(connections.WithConfigTemplateOverrides(getTemplateOverrides(t, test.packageType, curPath)))
}
Expand Down
12 changes: 7 additions & 5 deletions internal/connections/bundledconfig/bundledconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"github.com/observeinc/observe-agent/internal/connections/bundledconfig/windows"
)

type ConfigTemplates = map[string]embed.FS

// TODO break up some of the larger connections in order to share more configs.
var SharedTemplateFS = map[string]embed.FS{
var SharedTemplateFS = ConfigTemplates{
"common/attributes.yaml.tmpl": shared.AttributesTemplateFS,
"common/internal_telemetry.yaml.tmpl": shared.InternalTelemetryTemplateFS,
"common/health_check.yaml.tmpl": shared.HealthCheckTemplateFS,
Expand All @@ -23,23 +25,23 @@ var SharedTemplateFS = map[string]embed.FS{
"self_monitoring/logs_and_metrics.yaml.tmpl": shared.LogsAndMetricsTemplateFS,
}

var DockerTemplateFS = map[string]embed.FS{
var DockerTemplateFS = ConfigTemplates{
"common/base.yaml.tmpl": docker.BaseTemplateFS,
"host_monitoring/logs.yaml.tmpl": docker.LogsTemplateFS,
"host_monitoring/host_metrics.yaml.tmpl": docker.HostMetricsTemplateFS,
"host_monitoring/process_metrics.yaml.tmpl": docker.ProcessMetricsTemplateFS,
"self_monitoring/logs_and_metrics.yaml.tmpl": docker.LogsAndMetricsTemplateFS,
}

var LinuxTemplateFS = map[string]embed.FS{
var LinuxTemplateFS = ConfigTemplates{
"host_monitoring/logs.yaml.tmpl": linux.LogsTemplateFS,
"host_monitoring/host_metrics.yaml.tmpl": linux.HostMetricsTemplateFS,
"self_monitoring/logs_and_metrics.yaml.tmpl": linux.LogsAndMetricsTemplateFS,
}

var MacOSTemplateFS = map[string]embed.FS{}
var MacOSTemplateFS = ConfigTemplates{}

var WindowsTemplateFS = map[string]embed.FS{
var WindowsTemplateFS = ConfigTemplates{
"common/base.yaml.tmpl": windows.BaseTemplateFS,
"host_monitoring/logs.yaml.tmpl": windows.LogsTemplateFS,
"host_monitoring/host_metrics.yaml.tmpl": windows.HostMetricsTemplateFS,
Expand Down
6 changes: 2 additions & 4 deletions internal/connections/connections.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,12 @@ type BundledConfigFragment struct {
colConfigFilePath string
}

type ConfigOverrides = map[string]embed.FS

type ConnectionType struct {
Name string
BundledConfigFragments []BundledConfigFragment
EnabledCheck EnabledCheckFn

templateOverrides ConfigOverrides
templateOverrides bundledconfig.ConfigTemplates
}

func (c *ConnectionType) getTemplate(tplName string) (*template.Template, error) {
Expand Down Expand Up @@ -111,7 +109,7 @@ func MakeConnectionType(name string, enabledCheck EnabledCheckFn, fragments []Bu
return c
}

func WithConfigTemplateOverrides(templateOverrides ConfigOverrides) ConnectionTypeOption {
func WithConfigTemplateOverrides(templateOverrides bundledconfig.ConfigTemplates) ConnectionTypeOption {
return func(c *ConnectionType) {
c.templateOverrides = templateOverrides
}
Expand Down
29 changes: 29 additions & 0 deletions internal/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import (
"github.com/observeinc/observe-agent/internal/commands/util/logger"
"github.com/observeinc/observe-agent/internal/config"
"github.com/observeinc/observe-agent/internal/connections"
"github.com/observeinc/observe-agent/internal/connections/bundledconfig"
"github.com/observeinc/observe-agent/observecol"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var CfgFile string
var configMode string

// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Expand All @@ -45,12 +47,39 @@ func init() {

flags := RootCmd.PersistentFlags()
flags.StringVar(&CfgFile, "observe-config", "", "observe-agent config file path")
flags.StringVar(&configMode, "config-mode", "", "The mode to use for bundled config. Valid values are Linux, Docker, Mac, and Windows.")
flags.MarkHidden("config-mode")
observecol.AddConfigFlags(flags)
observecol.AddFeatureGateFlag(flags)
}

func setConfigMode() {
var overrides bundledconfig.ConfigTemplates
switch strings.ToLower(configMode) {
case "":
return
case "linux":
overrides = bundledconfig.LinuxTemplateFS
case "docker":
overrides = bundledconfig.DockerTemplateFS
case "mac":
overrides = bundledconfig.MacOSTemplateFS
case "windows":
overrides = bundledconfig.WindowsTemplateFS
default:
fmt.Fprintf(os.Stderr, "Invalid config mode specified: %s. Valid values are Linux, Docker, Mac, and Windows.\n", configMode)
os.Exit(1)
}
// Set the template overrides for all connections
for _, conn := range connections.AllConnectionTypes {
conn.ApplyOptions(connections.WithConfigTemplateOverrides(overrides))
}

}

// InitConfig reads in config file and ENV variables if set.
func InitConfig() {
setConfigMode()
ctx := logger.WithCtx(context.Background(), logger.Get())
// Some keys in OTEL component configs use "." as part of the key but viper ends up parsing that into
// a subobject since the default key delimiter is "." which causes config validation to fail.
Expand Down
Loading