opentelemetry-collector/cmd/builder/internal/command.go

214 lines
8.9 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/cmd/builder/internal"
import (
"fmt"
"strings"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"go.uber.org/zap"
"go.opentelemetry.io/collector/cmd/builder/internal/builder"
"go.opentelemetry.io/collector/cmd/builder/internal/config"
)
const (
skipGenerateFlag = "skip-generate"
skipCompilationFlag = "skip-compilation"
skipGetModulesFlag = "skip-get-modules"
skipStrictVersioningFlag = "skip-strict-versioning"
ldflagsFlag = "ldflags"
distributionNameFlag = "name"
distributionDescriptionFlag = "description"
distributionVersionFlag = "version"
distributionOtelColVersionFlag = "otelcol-version"
distributionOutputPathFlag = "output-path"
distributionGoFlag = "go"
distributionModuleFlag = "module"
verboseFlag = "verbose"
)
var (
cfgFile string
cfg = builder.NewDefaultConfig()
k = koanf.New(".")
)
// Command is the main entrypoint for this application
func Command() (*cobra.Command, error) {
cmd := &cobra.Command{
SilenceUsage: true, // Don't print usage on Run error.
SilenceErrors: true, // Don't print errors; main does it.
Use: "ocb",
Long: fmt.Sprintf("OpenTelemetry Collector Builder (%s)", version) + `
ocb generates a custom OpenTelemetry Collector binary using the
build configuration given by the "--config" argument. If no build
configuration is provided, ocb will generate a default Collector.
`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if err := initConfig(cmd.Flags()); err != nil {
return err
}
if err := cfg.Validate(); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}
if err := cfg.SetGoPath(); err != nil {
return fmt.Errorf("go not found: %w", err)
}
if err := cfg.SetBackwardsCompatibility(); err != nil {
return fmt.Errorf("unable to compare otelcol version: %w", err)
}
if err := cfg.ParseModules(); err != nil {
return fmt.Errorf("invalid module configuration: %w", err)
}
return builder.GenerateAndCompile(cfg)
},
}
cmd.Flags().StringVar(&cfgFile, "config", "", "build configuration file")
// the distribution parameters, which we accept as CLI flags as well
cmd.Flags().BoolVar(&cfg.SkipGenerate, skipGenerateFlag, false, "Whether builder should skip generating go code (default false)")
cmd.Flags().BoolVar(&cfg.SkipCompilation, skipCompilationFlag, false, "Whether builder should only generate go code with no compile of the collector (default false)")
cmd.Flags().BoolVar(&cfg.SkipGetModules, skipGetModulesFlag, false, "Whether builder should skip updating go.mod and retrieve Go module list (default false)")
cmd.Flags().BoolVar(&cfg.SkipStrictVersioning, skipStrictVersioningFlag, false, "Whether builder should skip strictly checking the calculated versions following dependency resolution")
cmd.Flags().BoolVar(&cfg.Verbose, verboseFlag, false, "Whether builder should print verbose output (default false)")
cmd.Flags().StringVar(&cfg.LDFlags, ldflagsFlag, "", `ldflags to include in the "go build" command`)
cmd.Flags().StringVar(&cfg.Distribution.Name, distributionNameFlag, "otelcol-custom", "The executable name for the OpenTelemetry Collector distribution")
if err := cmd.Flags().MarkDeprecated(distributionNameFlag, "use config distribution::name"); err != nil {
return nil, err
}
cmd.Flags().StringVar(&cfg.Distribution.Description, distributionDescriptionFlag, "Custom OpenTelemetry Collector distribution", "A descriptive name for the OpenTelemetry Collector distribution")
if err := cmd.Flags().MarkDeprecated(distributionDescriptionFlag, "use config distribution::description"); err != nil {
return nil, err
}
cmd.Flags().StringVar(&cfg.Distribution.Version, distributionVersionFlag, "1.0.0", "The version for the OpenTelemetry Collector distribution")
if err := cmd.Flags().MarkDeprecated(distributionVersionFlag, "use config distribution::version"); err != nil {
return nil, err
}
cmd.Flags().StringVar(&cfg.Distribution.OtelColVersion, distributionOtelColVersionFlag, cfg.Distribution.OtelColVersion, "Which version of OpenTelemetry Collector to use as base")
if err := cmd.Flags().MarkDeprecated(distributionOtelColVersionFlag, "use config distribution::otelcol_version"); err != nil {
return nil, err
}
cmd.Flags().StringVar(&cfg.Distribution.OutputPath, distributionOutputPathFlag, cfg.Distribution.OutputPath, "Where to write the resulting files")
if err := cmd.Flags().MarkDeprecated(distributionOutputPathFlag, "use config distribution::output_path"); err != nil {
return nil, err
}
cmd.Flags().StringVar(&cfg.Distribution.Go, distributionGoFlag, "", "The Go binary to use during the compilation phase. Default: go from the PATH")
if err := cmd.Flags().MarkDeprecated(distributionGoFlag, "use config distribution::go"); err != nil {
return nil, err
}
cmd.Flags().StringVar(&cfg.Distribution.Module, distributionModuleFlag, "go.opentelemetry.io/collector/cmd/builder", "The Go module for the new distribution")
if err := cmd.Flags().MarkDeprecated(distributionModuleFlag, "use config distribution::module"); err != nil {
return nil, err
}
// version of this binary
cmd.AddCommand(versionCommand())
return cmd, nil
}
func initConfig(flags *flag.FlagSet) error {
cfg.Logger.Info("OpenTelemetry Collector Builder",
zap.String("version", version))
var provider koanf.Provider
if cfgFile != "" {
// load the config file
provider = file.Provider(cfgFile)
} else {
// or the default if the config isn't provided
provider = config.DefaultProvider()
cfg.Logger.Info("Using default build configuration")
}
if err := k.Load(provider, yaml.Parser()); err != nil {
return fmt.Errorf("failed to load configuration file: %w", err)
}
// handle env variables
if err := k.Load(env.Provider("", ".", func(s string) string {
// Only values from the `dist.` group can be set,
// and the subfields in `dist.` contain `_` in their names.
// All other fields are arrays and the koanf env provider doesn't provide a straightforward way to set arrarys.
return strings.Replace(strings.ToLower(s), "dist_", "dist.", 1)
}), nil); err != nil {
return fmt.Errorf("failed to load environment variables: %w", err)
}
cfgFromFile := builder.Config{}
if err := k.UnmarshalWithConf("", &cfgFromFile, koanf.UnmarshalConf{Tag: "mapstructure"}); err != nil {
return fmt.Errorf("failed to unmarshal configuration: %w", err)
}
applyCfgFromFile(flags, cfgFromFile)
if cfgFile != "" {
cfg.Logger.Info("Using config file", zap.String("path", cfgFile))
}
return nil
}
func applyCfgFromFile(flags *flag.FlagSet, cfgFromFile builder.Config) {
cfg.Exporters = cfgFromFile.Exporters
cfg.Extensions = cfgFromFile.Extensions
cfg.Receivers = cfgFromFile.Receivers
cfg.Processors = cfgFromFile.Processors
cfg.Connectors = cfgFromFile.Connectors
cfg.Providers = cfgFromFile.Providers
cfg.Replaces = cfgFromFile.Replaces
cfg.Excludes = cfgFromFile.Excludes
cfg.ConfResolver.DefaultURIScheme = cfgFromFile.ConfResolver.DefaultURIScheme
if !flags.Changed(skipGenerateFlag) && cfgFromFile.SkipGenerate {
cfg.SkipGenerate = cfgFromFile.SkipGenerate
}
if !flags.Changed(skipCompilationFlag) && cfgFromFile.SkipCompilation {
cfg.SkipCompilation = cfgFromFile.SkipCompilation
}
if !flags.Changed(skipGetModulesFlag) && cfgFromFile.SkipGetModules {
cfg.SkipGetModules = cfgFromFile.SkipGetModules
}
if !flags.Changed(skipStrictVersioningFlag) && cfgFromFile.SkipStrictVersioning {
cfg.SkipStrictVersioning = cfgFromFile.SkipStrictVersioning
}
if !flags.Changed(distributionNameFlag) && cfgFromFile.Distribution.Name != "" {
cfg.Distribution.Name = cfgFromFile.Distribution.Name
}
if !flags.Changed(distributionDescriptionFlag) && cfgFromFile.Distribution.Description != "" {
cfg.Distribution.Description = cfgFromFile.Distribution.Description
}
if !flags.Changed(distributionVersionFlag) && cfgFromFile.Distribution.Version != "" {
cfg.Distribution.Version = cfgFromFile.Distribution.Version
}
if !flags.Changed(distributionOtelColVersionFlag) && cfgFromFile.Distribution.OtelColVersion != "" {
cfg.Distribution.OtelColVersion = cfgFromFile.Distribution.OtelColVersion
}
if !flags.Changed(distributionOutputPathFlag) && cfgFromFile.Distribution.OutputPath != "" {
cfg.Distribution.OutputPath = cfgFromFile.Distribution.OutputPath
}
if !flags.Changed(distributionGoFlag) && cfgFromFile.Distribution.Go != "" {
cfg.Distribution.Go = cfgFromFile.Distribution.Go
}
if !flags.Changed(distributionModuleFlag) && cfgFromFile.Distribution.Module != "" {
cfg.Distribution.Module = cfgFromFile.Distribution.Module
}
cfg.Distribution.DebugCompilation = cfgFromFile.Distribution.DebugCompilation
}