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 })