commands.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package cmd
  2. import (
  3. "fmt"
  4. "os"
  5. "strings"
  6. "github.com/opencost/opencost/core/pkg/log"
  7. "github.com/opencost/opencost/pkg/cmd/agent"
  8. "github.com/opencost/opencost/pkg/cmd/costmodel"
  9. "github.com/opencost/opencost/pkg/env"
  10. "github.com/spf13/cobra"
  11. "github.com/spf13/viper"
  12. )
  13. const (
  14. // commandRoot is the root command used to route to sub-commands
  15. commandRoot string = "root"
  16. // CommandCostModel is the command used to execute the metrics emission and cost model querying
  17. CommandCostModel string = "cost-model"
  18. // CommandAgent executes the application in agent mode, which provides only metrics exporting.
  19. CommandAgent string = "agent"
  20. )
  21. // Execute runs the root command for the application. By default, if no command argument is provided,
  22. // on the command line, the cost-model is executed by default.
  23. //
  24. // This function accepts a costModelCmd and agentCmd parameters to provide support for alternate
  25. // implementations for cost-model and/or agent. If the passed in costModelCmd and/or agentCmd are nil,
  26. // then the respective defaults from opencost will be used.
  27. //
  28. // Any additional commands passed in will be added to the root command.
  29. func Execute(costModelCmd *cobra.Command, cmds ...*cobra.Command) error {
  30. // use the open-source cost-model if a command is not provided
  31. if costModelCmd == nil {
  32. costModelCmd = newCostModelCommand()
  33. }
  34. // validate the commands being passed
  35. if err := validate(costModelCmd, CommandCostModel); err != nil {
  36. return err
  37. }
  38. // prepend the -agent command and create a new root command
  39. rootCmd := newRootCommand(costModelCmd, cmds...)
  40. // in the event that no directive/command is passed, we want to default to using the cost-model command
  41. // cobra doesn't provide a way within the API to do this, so we'll prepend the command if it is omitted.
  42. if len(os.Args) > 1 {
  43. // try to find the sub-command from the arguments, if there's an error or the command _is_ the
  44. // root command, prepend the default command
  45. pCmd, _, err := rootCmd.Find(os.Args[1:])
  46. if err != nil || pCmd.Use == rootCmd.Use {
  47. rootCmd.SetArgs(append([]string{CommandCostModel}, os.Args[1:]...))
  48. }
  49. } else {
  50. rootCmd.SetArgs([]string{CommandCostModel})
  51. }
  52. return rootCmd.Execute()
  53. }
  54. // newRootCommand creates a new root command which will act as a sub-command router for the
  55. // cost-model application.
  56. func newRootCommand(costModelCmd *cobra.Command, cmds ...*cobra.Command) *cobra.Command {
  57. cmd := &cobra.Command{
  58. Use: commandRoot,
  59. SilenceUsage: true,
  60. }
  61. // Add our persistent flags, these are global and available anywhere
  62. cmd.PersistentFlags().String("log-level", "info", "Set the log level")
  63. cmd.PersistentFlags().String("log-format", "pretty", "Set the log format - Can be either 'JSON' or 'pretty'")
  64. cmd.PersistentFlags().Bool("disable-log-color", false, "Disable coloring of log output")
  65. viper.BindPFlag("log-level", cmd.PersistentFlags().Lookup("log-level"))
  66. viper.BindPFlag("log-format", cmd.PersistentFlags().Lookup("log-format"))
  67. viper.BindPFlag("disable-log-color", cmd.PersistentFlags().Lookup("disable-log-color"))
  68. // Setup viper to read from the env, this allows reading flags from the command line or the env
  69. // using the format 'LOG_LEVEL'
  70. viper.AutomaticEnv()
  71. viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
  72. // add the modes of operation
  73. cmd.AddCommand(
  74. append([]*cobra.Command{
  75. costModelCmd,
  76. newAgentCommand(),
  77. }, cmds...)...,
  78. )
  79. return cmd
  80. }
  81. // default open-source cost-model command
  82. func newCostModelCommand() *cobra.Command {
  83. config := costmodel.DefaultConfig()
  84. cmCmd := &cobra.Command{
  85. Use: CommandCostModel,
  86. Short: "Cost-Model metric exporter and data aggregator.",
  87. RunE: func(cmd *cobra.Command, args []string) error {
  88. // Init logging here so cobra/viper has processed the command line args and flags
  89. // otherwise only envvars are available during init
  90. log.InitLogging(true)
  91. // Update config with command-line flag values if they were explicitly set
  92. if cmd.Flags().Changed("port") {
  93. port, _ := cmd.Flags().GetInt("port")
  94. config.Port = port
  95. }
  96. if cmd.Flags().Changed("kubernetes-enabled") {
  97. kubernetesEnabled, _ := cmd.Flags().GetBool("kubernetes-enabled")
  98. config.KubernetesEnabled = kubernetesEnabled
  99. }
  100. if cmd.Flags().Changed("carbon-estimates-enabled") {
  101. carbonEstimatesEnabled, _ := cmd.Flags().GetBool("carbon-estimates-enabled")
  102. config.CarbonEstimatesEnabled = carbonEstimatesEnabled
  103. }
  104. if cmd.Flags().Changed("cloud-cost-enabled") {
  105. cloudCostEnabled, _ := cmd.Flags().GetBool("cloud-cost-enabled")
  106. config.CloudCostEnabled = cloudCostEnabled
  107. }
  108. if cmd.Flags().Changed("custom-cost-enabled") {
  109. customCostEnabled, _ := cmd.Flags().GetBool("custom-cost-enabled")
  110. config.CustomCostEnabled = customCostEnabled
  111. }
  112. return costmodel.Execute(config)
  113. },
  114. }
  115. // Add command-line flags for cost-model configuration
  116. cmCmd.Flags().Int("port", config.Port, "Port to bind to")
  117. cmCmd.Flags().Bool("kubernetes-enabled", config.KubernetesEnabled, "Enable Kubernetes metrics")
  118. cmCmd.Flags().Bool("carbon-estimates-enabled", config.CarbonEstimatesEnabled, "Enable Carbon Estimates")
  119. cmCmd.Flags().Bool("cloud-cost-enabled", config.CloudCostEnabled, "Enable Cloud Costs")
  120. cmCmd.Flags().Bool("custom-cost-enabled", config.CustomCostEnabled, "Enable Custom Costs")
  121. return cmCmd
  122. }
  123. func newAgentCommand() *cobra.Command {
  124. opts := &agent.AgentOpts{}
  125. agentCmd := &cobra.Command{
  126. Use: CommandAgent,
  127. Short: "Agent mode operates as a metric exporter only.",
  128. RunE: func(cmd *cobra.Command, args []string) error {
  129. // Init logging here so cobra/viper has processed the command line args and flags
  130. // otherwise only envvars are available during init
  131. log.InitLogging(true)
  132. // Update opts with command-line flag values if they were explicitly set
  133. if cmd.Flags().Changed("port") {
  134. port, _ := cmd.Flags().GetInt("port")
  135. opts.Port = port
  136. }
  137. return agent.Execute(opts)
  138. },
  139. }
  140. // Add command-line flags for agent configuration
  141. agentCmd.Flags().Int("port", env.GetKubecostMetricsPort(), "Port to bind to")
  142. return agentCmd
  143. }
  144. // validate checks the command's use to see if it matches an expected command name.
  145. func validate(cmd *cobra.Command, command string) error {
  146. if cmd.Use != command {
  147. return fmt.Errorf("Incompatible '%s' command provided to run-time.", command)
  148. }
  149. return nil
  150. }