github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/cli/flags_test.go (about)

     1  package cli
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"time"
     9  
    10  	. "github.com/onsi/ginkgo/v2"
    11  	. "github.com/onsi/gomega"
    12  	"github.com/spf13/cobra"
    13  	"github.com/spf13/viper"
    14  	"golang.org/x/crypto/bcrypt"
    15  
    16  	"github.com/pyroscope-io/pyroscope/pkg/config"
    17  	scrape "github.com/pyroscope-io/pyroscope/pkg/scrape/config"
    18  	"github.com/pyroscope-io/pyroscope/pkg/scrape/discovery"
    19  	sm "github.com/pyroscope-io/pyroscope/pkg/scrape/model"
    20  	"github.com/pyroscope-io/pyroscope/pkg/util/bytesize"
    21  )
    22  
    23  type FlagsStruct struct {
    24  	Config   string            `mapstructure:"config"`
    25  	Foo      string            `mapstructure:"foo"`
    26  	Foos     []string          `mapstructure:"foos"`
    27  	Bar      int               `mapstructure:"bar"`
    28  	Baz      time.Duration     `mapstructure:"baz"`
    29  	FooBar   string            `mapstructure:"foo-bar"`
    30  	FooFoo   float64           `mapstructure:"foo-foo"`
    31  	FooBytes bytesize.ByteSize `mapstructure:"foo-bytes"`
    32  	FooDur   time.Duration     `mapstructure:"foo-dur"`
    33  }
    34  
    35  var _ = Describe("flags", func() {
    36  	Context("PopulateFlagSet", func() {
    37  		Context("without config file", func() {
    38  			It("correctly sets all types of arguments", func() {
    39  				vpr := viper.New()
    40  				exampleCommand := &cobra.Command{
    41  					RunE: func(cmd *cobra.Command, args []string) error {
    42  						return nil
    43  					},
    44  				}
    45  
    46  				cfg := FlagsStruct{}
    47  				PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr)
    48  
    49  				b := bytes.NewBufferString("")
    50  				exampleCommand.SetOut(b)
    51  				exampleCommand.SetArgs([]string{
    52  					fmt.Sprintf("--foo=%s", "test-val-1"),
    53  					fmt.Sprintf("--foos=%s", "test-val-2"),
    54  					fmt.Sprintf("--foos=%s", "test-val-3"),
    55  					fmt.Sprintf("--bar=%s", "123"),
    56  					fmt.Sprintf("--baz=%s", "10h"),
    57  					fmt.Sprintf("--foo-bar=%s", "test-val-4"),
    58  					fmt.Sprintf("--foo-foo=%s", "10.23"),
    59  					fmt.Sprintf("--foo-bytes=%s", "100MB"),
    60  				})
    61  
    62  				err := exampleCommand.Execute()
    63  				Expect(err).ToNot(HaveOccurred())
    64  				Expect(cfg.Foo).To(Equal("test-val-1"))
    65  				Expect(cfg.Foos).To(Equal([]string{"test-val-2", "test-val-3"}))
    66  				Expect(cfg.Bar).To(Equal(123))
    67  				Expect(cfg.Baz).To(Equal(10 * time.Hour))
    68  				Expect(cfg.FooBar).To(Equal("test-val-4"))
    69  				Expect(cfg.FooFoo).To(Equal(10.23))
    70  				Expect(cfg.FooBytes).To(Equal(100 * bytesize.MB))
    71  			})
    72  		})
    73  
    74  		Context("with config file", func() {
    75  			It("correctly sets all types of arguments", func() {
    76  				cfg := FlagsStruct{}
    77  				vpr := viper.New()
    78  				exampleCommand := &cobra.Command{
    79  					RunE: CreateCmdRunFn(&cfg, vpr, func(cmd *cobra.Command, args []string) error {
    80  						return nil
    81  					}),
    82  				}
    83  
    84  				PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr)
    85  				vpr.BindPFlags(exampleCommand.Flags())
    86  
    87  				b := bytes.NewBufferString("")
    88  				exampleCommand.SetOut(b)
    89  				exampleCommand.SetArgs([]string{fmt.Sprintf("--config=%s", "testdata/example.yml")})
    90  
    91  				err := exampleCommand.Execute()
    92  				Expect(err).ToNot(HaveOccurred())
    93  				Expect(cfg.Foo).To(Equal("test-val-1"))
    94  				Expect(cfg.Foos).To(Equal([]string{"test-val-2", "test-val-3"}))
    95  				Expect(cfg.Bar).To(Equal(123))
    96  				Expect(cfg.Baz).To(Equal(10 * time.Hour))
    97  				Expect(cfg.FooBar).To(Equal("test-val-4"))
    98  				Expect(cfg.FooFoo).To(Equal(10.23))
    99  				Expect(cfg.FooBytes).To(Equal(100 * bytesize.MB))
   100  				Expect(cfg.FooDur).To(Equal(5*time.Minute + 23*time.Second))
   101  			})
   102  
   103  			It("correctly works with substitutions", func() {
   104  				os.Setenv("VALUE1", "test-val-1")
   105  				os.Setenv("VALUE2", "test-val-2")
   106  				// os.Setenv("VALUE3", "test-val-3")
   107  				os.Setenv("VALUE4", "123")
   108  				os.Setenv("VALUE5", "10h")
   109  				os.Setenv("VALUE6", "test-val-4")
   110  				os.Setenv("VALUE7", "10.23")
   111  				os.Setenv("VALUE8", "100mb")
   112  				os.Setenv("VALUE9", "5m23s")
   113  				cfg := FlagsStruct{}
   114  				vpr := viper.New()
   115  
   116  				exampleCommand := &cobra.Command{
   117  					RunE: CreateCmdRunFn(&cfg, vpr, func(cmd *cobra.Command, args []string) error {
   118  						return nil
   119  					}),
   120  				}
   121  
   122  				PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr)
   123  				vpr.BindPFlags(exampleCommand.Flags())
   124  
   125  				b := bytes.NewBufferString("")
   126  				exampleCommand.SetOut(b)
   127  				exampleCommand.SetArgs([]string{fmt.Sprintf("--config=%s", "testdata/substitutions.yml")})
   128  
   129  				err := exampleCommand.Execute()
   130  				Expect(err).ToNot(HaveOccurred())
   131  				Expect(cfg.Foo).To(Equal("test-val-1"))
   132  				Expect(cfg.Foos).To(Equal([]string{"test-val-2", ""}))
   133  				Expect(cfg.Bar).To(Equal(123))
   134  				Expect(cfg.Baz).To(Equal(10 * time.Hour))
   135  				Expect(cfg.FooBar).To(Equal("test-val-4"))
   136  				Expect(cfg.FooFoo).To(Equal(10.23))
   137  				Expect(cfg.FooBytes).To(Equal(100 * bytesize.MB))
   138  				Expect(cfg.FooDur).To(Equal(5*time.Minute + 23*time.Second))
   139  			})
   140  
   141  			It("arguments take precedence", func() {
   142  				cfg := FlagsStruct{}
   143  				vpr := viper.New()
   144  				exampleCommand := &cobra.Command{
   145  					RunE: func(cmd *cobra.Command, args []string) error {
   146  						if cfg.Config != "" {
   147  							// Use config file from the flag.
   148  							vpr.SetConfigFile(cfg.Config)
   149  
   150  							// If a config file is found, read it in.
   151  							if err := vpr.ReadInConfig(); err == nil {
   152  								fmt.Fprintln(os.Stderr, "Using config file:", vpr.ConfigFileUsed())
   153  							}
   154  
   155  							if err := Unmarshal(vpr, &cfg); err != nil {
   156  								fmt.Fprintln(os.Stderr, "Unable to unmarshal:", err)
   157  							}
   158  
   159  							fmt.Printf("configuration is %+v \n", cfg)
   160  						}
   161  
   162  						return nil
   163  					},
   164  				}
   165  
   166  				PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr)
   167  				vpr.BindPFlags(exampleCommand.Flags())
   168  
   169  				b := bytes.NewBufferString("")
   170  				exampleCommand.SetOut(b)
   171  				exampleCommand.SetArgs([]string{
   172  					fmt.Sprintf("--config=%s", "testdata/example.yml"),
   173  					fmt.Sprintf("--foo=%s", "test-val-4"),
   174  					fmt.Sprintf("--foo-dur=%s", "3h"),
   175  				})
   176  
   177  				err := exampleCommand.Execute()
   178  				Expect(err).ToNot(HaveOccurred())
   179  				Expect(cfg.Foo).To(Equal("test-val-4"))
   180  				Expect(cfg.FooDur).To(Equal(3 * time.Hour))
   181  			})
   182  			It("server configuration", func() {
   183  				var cfg config.Server
   184  				vpr := viper.New()
   185  				exampleCommand := &cobra.Command{
   186  					RunE: CreateCmdRunFn(&cfg, vpr, func(cmd *cobra.Command, args []string) error {
   187  						Expect(loadScrapeConfigsFromFile(&cfg)).ToNot(HaveOccurred())
   188  						fmt.Printf("configuration is %+v \n", cfg)
   189  						return nil
   190  					}),
   191  				}
   192  
   193  				PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr, WithSkip("scrape-configs"))
   194  				vpr.BindPFlags(exampleCommand.Flags())
   195  
   196  				b := bytes.NewBufferString("")
   197  				exampleCommand.SetOut(b)
   198  				exampleCommand.SetArgs([]string{
   199  					"--config=testdata/server.yml",
   200  					"--log-level=debug",
   201  					"--adhoc-data-path=", // Override as it's platform dependent.
   202  					"--auth.signup-default-role=admin",
   203  				})
   204  
   205  				err := exampleCommand.Execute()
   206  				Expect(err).ToNot(HaveOccurred())
   207  				Expect(cfg).To(Equal(config.Server{
   208  					AnalyticsOptOut:         false,
   209  					Config:                  "testdata/server.yml",
   210  					LogLevel:                "debug",
   211  					BadgerLogLevel:          "error",
   212  					StoragePath:             "/var/lib/pyroscope",
   213  					APIBindAddr:             ":4040",
   214  					BaseURL:                 "",
   215  					CacheEvictThreshold:     0.25,
   216  					CacheEvictVolume:        0.33,
   217  					MinFreeSpacePercentage:  5,
   218  					BadgerNoTruncate:        false,
   219  					DisablePprofEndpoint:    false,
   220  					EnableExperimentalAdmin: true,
   221  					NoAdhocUI:               false,
   222  					AdhocMaxFileSize:        52428800,
   223  					MaxNodesSerialization:   2048,
   224  					MaxNodesRender:          8192,
   225  					HideApplications:        []string{},
   226  					Retention:               0,
   227  					Database: config.Database{
   228  						Type: "sqlite3",
   229  						URL:  "",
   230  					},
   231  					RetentionLevels: config.RetentionLevels{
   232  						Zero: 100 * time.Second,
   233  						One:  1000 * time.Second,
   234  					},
   235  					SampleRate:          0,
   236  					OutOfSpaceThreshold: 0,
   237  					CacheDimensionSize:  0,
   238  					CacheDictionarySize: 0,
   239  					CacheSegmentSize:    0,
   240  					CacheTreeSize:       0,
   241  					CORS: config.CORSConfig{
   242  						AllowedOrigins: []string{},
   243  						AllowedHeaders: []string{},
   244  						AllowedMethods: []string{},
   245  						MaxAge:         0,
   246  					},
   247  					Auth: config.Auth{
   248  						Internal: config.InternalAuth{
   249  							Enabled:       false,
   250  							SignupEnabled: false,
   251  							AdminUser: config.AdminUser{
   252  								Create:   true,
   253  								Name:     "admin",
   254  								Email:    "admin@localhost.local",
   255  								Password: "admin",
   256  							},
   257  						},
   258  						Google: config.GoogleOauth{
   259  							Enabled:        false,
   260  							ClientID:       "",
   261  							ClientSecret:   "",
   262  							RedirectURL:    "",
   263  							AuthURL:        "https://accounts.google.com/o/oauth2/auth",
   264  							TokenURL:       "https://accounts.google.com/o/oauth2/token",
   265  							AllowedDomains: []string{},
   266  						},
   267  						Gitlab: config.GitlabOauth{
   268  							Enabled:       false,
   269  							ClientID:      "",
   270  							ClientSecret:  "",
   271  							RedirectURL:   "",
   272  							AuthURL:       "https://gitlab.com/oauth/authorize",
   273  							TokenURL:      "https://gitlab.com/oauth/token",
   274  							APIURL:        "https://gitlab.com/api/v4",
   275  							AllowedGroups: []string{},
   276  						},
   277  						Github: config.GithubOauth{
   278  							Enabled:              false,
   279  							ClientID:             "",
   280  							ClientSecret:         "",
   281  							RedirectURL:          "",
   282  							AuthURL:              "https://github.com/login/oauth/authorize",
   283  							TokenURL:             "https://github.com/login/oauth/access_token",
   284  							AllowedOrganizations: []string{},
   285  						},
   286  						Ingestion: config.IngestionAuth{
   287  							Enabled:   false,
   288  							CacheTTL:  time.Minute,
   289  							CacheSize: 1024,
   290  						},
   291  						JWTSecret:                "",
   292  						LoginMaximumLifetimeDays: 0,
   293  						SignupDefaultRole:        "admin",
   294  						CookieSameSite:           http.SameSiteStrictMode,
   295  						APIKeyBcryptCost:         bcrypt.MinCost,
   296  					},
   297  
   298  					MetricsExportRules: config.MetricsExportRules{
   299  						"my_metric_name": {
   300  							Expr:    `app.name{foo=~"bar"}`,
   301  							Node:    "a;b;c",
   302  							GroupBy: []string{"foo"},
   303  						},
   304  					},
   305  					AdminSocketPath: "/tmp/pyroscope.sock",
   306  
   307  					RemoteWrite: config.RemoteWrite{
   308  						Enabled: true,
   309  					},
   310  
   311  					ScrapeConfigs: []*scrape.Config{
   312  						{
   313  							JobName:          "testing",
   314  							EnabledProfiles:  []string{"cpu", "mem"},
   315  							Profiles:         scrape.DefaultConfig().Profiles,
   316  							ScrapeInterval:   10 * time.Second,
   317  							ScrapeTimeout:    15 * time.Second,
   318  							Scheme:           "http",
   319  							HTTPClientConfig: scrape.DefaultHTTPClientConfig,
   320  							ServiceDiscoveryConfigs: []discovery.Config{
   321  								discovery.StaticConfig{
   322  									{
   323  										Targets: []sm.LabelSet{
   324  											{"__address__": "localhost:6060", "__name__": "app", "__spy_name__": ""},
   325  										},
   326  										Labels: sm.LabelSet{"foo": "bar"},
   327  										Source: "0",
   328  									},
   329  								},
   330  							},
   331  						},
   332  					},
   333  				}))
   334  			})
   335  
   336  			It("agent configuration", func() {
   337  				var cfg config.Agent
   338  				vpr := viper.New()
   339  				exampleCommand := &cobra.Command{
   340  					Run: func(cmd *cobra.Command, args []string) {
   341  						Expect(vpr.BindPFlags(cmd.Flags())).ToNot(HaveOccurred())
   342  						vpr.SetConfigFile(cfg.Config)
   343  						Expect(vpr.ReadInConfig()).ToNot(HaveOccurred())
   344  						Expect(Unmarshal(vpr, &cfg)).ToNot(HaveOccurred())
   345  						Expect(loadAgentConfig(&cfg)).ToNot(HaveOccurred())
   346  					},
   347  				}
   348  
   349  				PopulateFlagSet(&cfg, exampleCommand.Flags(), vpr)
   350  				exampleCommand.SetArgs([]string{
   351  					"--config=testdata/agent.yml",
   352  					"--log-level=debug",
   353  					"--tag=foo=xxx",
   354  				})
   355  
   356  				Expect(exampleCommand.Execute()).ToNot(HaveOccurred())
   357  				Expect(cfg).To(Equal(config.Agent{
   358  					Config:                 "testdata/agent.yml",
   359  					LogLevel:               "debug",
   360  					NoLogging:              false,
   361  					ServerAddress:          "http://localhost:4040",
   362  					AuthToken:              "",
   363  					UpstreamThreads:        4,
   364  					UpstreamRequestTimeout: 10 * time.Second,
   365  					Targets: []config.Target{
   366  						{
   367  							ServiceName:        "foo",
   368  							SpyName:            "debugspy",
   369  							ApplicationName:    "foo.app",
   370  							SampleRate:         0,
   371  							DetectSubprocesses: false,
   372  							Tags: map[string]string{
   373  								"foo": "xxx",
   374  								"baz": "qux",
   375  							},
   376  						},
   377  					},
   378  					Tags: map[string]string{
   379  						"foo": "xxx",
   380  						"baz": "qux",
   381  					},
   382  				}))
   383  			})
   384  		})
   385  	})
   386  })