commands.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. package cmd
  2. import (
  3. "fmt"
  4. "os"
  5. "strings"
  6. "github.com/kubecost/cost-model/pkg/cmd/agent"
  7. "github.com/kubecost/cost-model/pkg/cmd/costmodel"
  8. "github.com/kubecost/cost-model/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 parameter to provide support for various cost-model implementations
  24. // (ie: open source, enterprise).
  25. func Execute(costModelCmd *cobra.Command) error {
  26. // use the open-source cost-model if a command is not provided
  27. if costModelCmd == nil {
  28. costModelCmd = newCostModelCommand()
  29. }
  30. // validate the command being passed
  31. if err := validate(costModelCmd); err != nil {
  32. return err
  33. }
  34. rootCmd := newRootCommand(costModelCmd)
  35. // in the event that no directive/command is passed, we want to default to using the cost-model command
  36. // cobra doesn't provide a way within the API to do this, so we'll prepend the command if it is omitted.
  37. if len(os.Args) > 1 {
  38. // try to find the sub-command from the arguments, if there's an error or the command _is_ the
  39. // root command, prepend the default command
  40. pCmd, _, err := rootCmd.Find(os.Args[1:])
  41. if err != nil || pCmd.Use == rootCmd.Use {
  42. rootCmd.SetArgs(append([]string{CommandCostModel}, os.Args[1:]...))
  43. }
  44. } else {
  45. rootCmd.SetArgs([]string{CommandCostModel})
  46. }
  47. return rootCmd.Execute()
  48. }
  49. // newRootCommand creates a new root command which will act as a sub-command router for the
  50. // cost-model application
  51. func newRootCommand(costModelCmd *cobra.Command) *cobra.Command {
  52. cmd := &cobra.Command{
  53. Use: commandRoot,
  54. SilenceUsage: true,
  55. }
  56. // Add our persistent flags, these are global and available anywhere
  57. cmd.PersistentFlags().String("log-level", "info", "Set the log level")
  58. cmd.PersistentFlags().String("log-format", "pretty", "Set the log format - Can be either 'JSON' or 'pretty'")
  59. viper.BindPFlag("log-level", cmd.PersistentFlags().Lookup("log-level"))
  60. viper.BindPFlag("log-format", cmd.PersistentFlags().Lookup("log-format"))
  61. // Setup viper to read from the env, this allows reading flags from the command line or the env
  62. // using the format 'LOG_LEVEL'
  63. viper.AutomaticEnv()
  64. viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
  65. // add the modes of operation
  66. cmd.AddCommand(
  67. costModelCmd,
  68. newAgentCommand(),
  69. )
  70. log.InitLogging()
  71. return cmd
  72. }
  73. // default open-source cost-model command
  74. func newCostModelCommand() *cobra.Command {
  75. opts := &costmodel.CostModelOpts{}
  76. cmCmd := &cobra.Command{
  77. Use: CommandCostModel,
  78. Short: "Cost-Model metric exporter and data aggregator.",
  79. RunE: func(cmd *cobra.Command, args []string) error {
  80. return costmodel.Execute(opts)
  81. },
  82. }
  83. // TODO: We could introduce a way of mapping input command-line parameters to a configuration
  84. // TODO: object, and pass that object to the agent Execute()
  85. // cmCmd.Flags().<Type>VarP(&opts.<Property>, "<flag>", "<short>", <default>, "<usage>")
  86. return cmCmd
  87. }
  88. func newAgentCommand() *cobra.Command {
  89. opts := &agent.AgentOpts{}
  90. agentCmd := &cobra.Command{
  91. Use: CommandAgent,
  92. Short: "Agent mode operates as a metric exporter only.",
  93. RunE: func(cmd *cobra.Command, args []string) error {
  94. return agent.Execute(opts)
  95. },
  96. }
  97. // TODO: We could introduce a way of mapping input command-line parameters to a configuration
  98. // TODO: object, and pass that object to the agent Execute()
  99. // agentCmd.Flags().<Type>VarP(&opts.<Property>, "<flag>", "<short>", <default>, "<usage>")
  100. return agentCmd
  101. }
  102. // validate will check to ensure that the cost model command passed in has a use equal to the
  103. // CommandCostModel to ensure that the default command matches.
  104. func validate(costModelCommand *cobra.Command) error {
  105. if costModelCommand.Use != CommandCostModel {
  106. return fmt.Errorf("Incompatible 'cost-model' command provided to run-time.")
  107. }
  108. return nil
  109. }