2
0

commands.go 5.2 KB

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