From 26fe910e88b0db2f8787cae8ea2632640ac33159 Mon Sep 17 00:00:00 2001 From: Mathew Kamkar <578302+matkam@users.noreply.github.com> Date: Tue, 25 Oct 2022 22:24:08 -0700 Subject: [PATCH] Generated CLI Flags (#1112) --- cmd/server/main.go | 158 +++++++++++++------------ cmd/server/main_test.go | 2 +- go.mod | 5 +- go.sum | 12 +- pkg/config/config.go | 170 ++++++++++++++++++++++++++- pkg/config/config_test.go | 32 ++++- pkg/rtc/participant_internal_test.go | 2 +- pkg/service/roomallocator_test.go | 6 +- test/integration_helpers.go | 4 +- test/webhook_test.go | 2 +- 10 files changed, 297 insertions(+), 96 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 81036dab1..c7ccc53f2 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -23,89 +23,96 @@ import ( "github.com/livekit/livekit-server/version" ) +var baseFlags = []cli.Flag{ + &cli.StringSliceFlag{ + Name: "bind", + Usage: "IP address to listen on, use flag multiple times to specify multiple addresses", + }, + &cli.StringFlag{ + Name: "config", + Usage: "path to LiveKit config file", + }, + &cli.StringFlag{ + Name: "config-body", + Usage: "LiveKit config in YAML, typically passed in as an environment var in a container", + EnvVars: []string{"LIVEKIT_CONFIG"}, + }, + &cli.StringFlag{ + Name: "key-file", + Usage: "path to file that contains API keys/secrets", + }, + &cli.StringFlag{ + Name: "keys", + Usage: "api keys (key: secret\\n)", + EnvVars: []string{"LIVEKIT_KEYS"}, + }, + &cli.StringFlag{ + Name: "region", + Usage: "region of the current node. Used by regionaware node selector", + EnvVars: []string{"LIVEKIT_REGION"}, + }, + &cli.StringFlag{ + Name: "node-ip", + Usage: "IP address of the current node, used to advertise to clients. Automatically determined by default", + EnvVars: []string{"NODE_IP"}, + }, + &cli.IntFlag{ + Name: "udp-port", + Usage: "Single UDP port to use for WebRTC traffic", + EnvVars: []string{"UDP_PORT"}, + }, + &cli.StringFlag{ + Name: "redis-host", + Usage: "host (incl. port) to redis server", + EnvVars: []string{"REDIS_HOST"}, + }, + &cli.StringFlag{ + Name: "redis-password", + Usage: "password to redis", + EnvVars: []string{"REDIS_PASSWORD"}, + }, + &cli.StringFlag{ + Name: "turn-cert", + Usage: "tls cert file for TURN server", + EnvVars: []string{"LIVEKIT_TURN_CERT"}, + }, + &cli.StringFlag{ + Name: "turn-key", + Usage: "tls key file for TURN server", + EnvVars: []string{"LIVEKIT_TURN_KEY"}, + }, + // debugging flags + &cli.StringFlag{ + Name: "memprofile", + Usage: "write memory profile to `file`", + }, + &cli.BoolFlag{ + Name: "dev", + Usage: "sets log-level to debug, console formatter, and /debug/pprof. insecure for production", + }, + &cli.BoolFlag{ + Name: "disable-strict-config", + Usage: "disables strict config parsing", + Hidden: true, + }, +} + func init() { rand.Seed(time.Now().Unix()) } func main() { + generatedFlags, err := config.GenerateCLIFlags(baseFlags) + if err != nil { + fmt.Println(err) + } + app := &cli.App{ Name: "livekit-server", Usage: "High performance WebRTC server", Description: "run without subcommands to start the server", - Flags: []cli.Flag{ - &cli.StringSliceFlag{ - Name: "bind", - Usage: "IP address to listen on, use flag multiple times to specify multiple addresses", - }, - &cli.StringFlag{ - Name: "config", - Usage: "path to LiveKit config file", - }, - &cli.StringFlag{ - Name: "config-body", - Usage: "LiveKit config in YAML, typically passed in as an environment var in a container", - EnvVars: []string{"LIVEKIT_CONFIG"}, - }, - &cli.StringFlag{ - Name: "key-file", - Usage: "path to file that contains API keys/secrets", - }, - &cli.StringFlag{ - Name: "keys", - Usage: "api keys (key: secret\\n)", - EnvVars: []string{"LIVEKIT_KEYS"}, - }, - &cli.StringFlag{ - Name: "region", - Usage: "region of the current node. Used by regionaware node selector", - EnvVars: []string{"LIVEKIT_REGION"}, - }, - &cli.StringFlag{ - Name: "node-ip", - Usage: "IP address of the current node, used to advertise to clients. Automatically determined by default", - EnvVars: []string{"NODE_IP"}, - }, - &cli.IntFlag{ - Name: "udp-port", - Usage: "Single UDP port to use for WebRTC traffic", - EnvVars: []string{"UDP_PORT"}, - }, - &cli.StringFlag{ - Name: "redis-host", - Usage: "host (incl. port) to redis server", - EnvVars: []string{"REDIS_HOST"}, - }, - &cli.StringFlag{ - Name: "redis-password", - Usage: "password to redis", - EnvVars: []string{"REDIS_PASSWORD"}, - }, - &cli.StringFlag{ - Name: "turn-cert", - Usage: "tls cert file for TURN server", - EnvVars: []string{"LIVEKIT_TURN_CERT"}, - }, - &cli.StringFlag{ - Name: "turn-key", - Usage: "tls key file for TURN server", - EnvVars: []string{"LIVEKIT_TURN_KEY"}, - }, - // debugging flags - &cli.StringFlag{ - Name: "memprofile", - Usage: "write memory profile to `file`", - }, - &cli.BoolFlag{ - Name: "dev", - Usage: "sets log-level to debug, console formatter, and /debug/pprof. insecure for production", - }, - &cli.BoolFlag{ - Name: "disable-strict-config", - Usage: "disables strict config parsing", - Hidden: true, - }, - }, - Action: startServer, + Flags: append(baseFlags, generatedFlags...), + Action: startServer, Commands: []*cli.Command{ { Name: "generate-keys", @@ -165,7 +172,8 @@ func getConfig(c *cli.Context) (*config.Config, error) { if c.Bool("disable-strict-config") { strictMode = false } - conf, err := config.NewConfig(confString, strictMode, c) + + conf, err := config.NewConfig(confString, strictMode, c, baseFlags) if err != nil { return nil, err } diff --git a/cmd/server/main_test.go b/cmd/server/main_test.go index 97a4b8cf0..931692a64 100644 --- a/cmd/server/main_test.go +++ b/cmd/server/main_test.go @@ -43,7 +43,7 @@ func TestShouldReturnErrorIfConfigFileDoesNotExist(t *testing.T) { func writeConfigFile(test testStruct, t *testing.T) { if test.configFileName != "" { d1 := []byte(test.expectedConfigBody) - err := os.WriteFile(test.configFileName, d1, 0644) + err := os.WriteFile(test.configFileName, d1, 0o644) require.NoError(t, err) } } diff --git a/go.mod b/go.mod index d2fc88914..61f35a0a7 100644 --- a/go.mod +++ b/go.mod @@ -88,12 +88,11 @@ require ( github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 // indirect - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.10 // indirect - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect + golang.org/x/tools v0.1.12 // indirect google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect google.golang.org/grpc v1.50.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect diff --git a/go.sum b/go.sum index c26b9b5d1..daf75cb3b 100644 --- a/go.sum +++ b/go.sum @@ -425,6 +425,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -485,8 +486,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -540,6 +542,7 @@ golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= @@ -562,6 +565,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -632,6 +636,7 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -692,14 +697,13 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= diff --git a/pkg/config/config.go b/pkg/config/config.go index 4e11fcac5..5fd6bad58 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -3,6 +3,7 @@ package config import ( "fmt" "os" + "reflect" "strings" "time" @@ -23,6 +24,9 @@ var DefaultStunServers = []string{ type CongestionControlProbeMode string const ( + generatedCLIFlagsHidden = true + generatedCLIFlagUsage = "generated" + CongestionControlProbeModePadding CongestionControlProbeMode = "padding" CongestionControlProbeModeMedia CongestionControlProbeMode = "media" @@ -206,7 +210,7 @@ type IngressConfig struct { RTMPBaseURL string `yaml:"rtmp_base_url"` } -func NewConfig(confString string, strictMode bool, c *cli.Context) (*Config, error) { +func NewConfig(confString string, strictMode bool, c *cli.Context, baseFlags []cli.Flag) (*Config, error) { // start with defaults conf := &Config{ Port: 7880, @@ -265,6 +269,7 @@ func NewConfig(confString string, strictMode bool, c *cli.Context) (*Config, err }, Keys: map[string]string{}, } + if confString != "" { decoder := yaml.NewDecoder(strings.NewReader(confString)) decoder.KnownFields(strictMode) @@ -274,7 +279,7 @@ func NewConfig(confString string, strictMode bool, c *cli.Context) (*Config, err } if c != nil { - if err := conf.updateFromCLI(c); err != nil { + if err := conf.updateFromCLI(c, baseFlags); err != nil { return nil, err } } @@ -347,7 +352,166 @@ func (conf *Config) IsTURNSEnabled() bool { return false } -func (conf *Config) updateFromCLI(c *cli.Context) error { +type configNode struct { + TypeNode reflect.Value + TagPrefix string +} + +func (conf *Config) ToCLIFlagNames(existingFlags []cli.Flag) map[string]reflect.Value { + existingFlagNames := map[string]bool{} + for _, flag := range existingFlags { + for _, flagName := range flag.Names() { + existingFlagNames[flagName] = true + } + } + + flagNames := map[string]reflect.Value{} + var currNode configNode + nodes := []configNode{{reflect.ValueOf(conf).Elem(), ""}} + for len(nodes) > 0 { + currNode, nodes = nodes[0], nodes[1:] + for i := 0; i < currNode.TypeNode.NumField(); i++ { + // inspect yaml tag from struct field to get path + field := currNode.TypeNode.Type().Field(i) + yamlTag := strings.SplitN(field.Tag.Get("yaml"), ",", 2)[0] + if yamlTag == "" || yamlTag == "-" { + continue + } + yamlPath := yamlTag + if currNode.TagPrefix != "" { + yamlPath = fmt.Sprintf("%s.%s", currNode.TagPrefix, yamlTag) + } + if existingFlagNames[yamlPath] { + continue + } + + // map flag name to value + value := currNode.TypeNode.Field(i) + if value.Kind() == reflect.Struct { + nodes = append(nodes, configNode{value, yamlPath}) + } else { + flagNames[yamlPath] = value + } + } + } + + return flagNames +} + +func GenerateCLIFlags(existingFlags []cli.Flag) ([]cli.Flag, error) { + blankConfig := &Config{} + flags := []cli.Flag{} + for name, value := range blankConfig.ToCLIFlagNames(existingFlags) { + kind := value.Kind() + if kind == reflect.Ptr { + kind = value.Type().Elem().Kind() + } + + var flag cli.Flag + envVar := fmt.Sprintf("LIVEKIT_%s", strings.ToUpper(strings.Replace(name, ".", "_", -1))) + + switch kind { + case reflect.Bool: + flag = &cli.BoolFlag{ + Name: name, + Usage: generatedCLIFlagUsage, + Hidden: generatedCLIFlagsHidden, + } + case reflect.String: + flag = &cli.StringFlag{ + Name: name, + EnvVars: []string{envVar}, + Usage: generatedCLIFlagUsage, + Hidden: generatedCLIFlagsHidden, + } + case reflect.Int, reflect.Int32: + flag = &cli.IntFlag{ + Name: name, + EnvVars: []string{envVar}, + Usage: generatedCLIFlagUsage, + Hidden: generatedCLIFlagsHidden, + } + case reflect.Int64: + flag = &cli.Int64Flag{ + Name: name, + EnvVars: []string{envVar}, + Usage: generatedCLIFlagUsage, + Hidden: generatedCLIFlagsHidden, + } + case reflect.Uint8, reflect.Uint16, reflect.Uint32: + flag = &cli.UintFlag{ + Name: name, + EnvVars: []string{envVar}, + Usage: generatedCLIFlagUsage, + Hidden: generatedCLIFlagsHidden, + } + case reflect.Float32: + flag = &cli.Float64Flag{ + Name: name, + EnvVars: []string{envVar}, + Usage: generatedCLIFlagUsage, + Hidden: generatedCLIFlagsHidden, + } + case reflect.Slice: + // TODO + continue + case reflect.Map: + // TODO + continue + default: + return flags, fmt.Errorf("cli flag generation unsupported for config type: %s is a %s", name, kind.String()) + } + + flags = append(flags, flag) + } + + return flags, nil +} + +func (conf *Config) updateFromCLI(c *cli.Context, baseFlags []cli.Flag) error { + generatedFlagNames := conf.ToCLIFlagNames(baseFlags) + for _, flag := range c.App.Flags { + flagName := flag.Names()[0] + + // the `len(baseFlags) > 0` check is needed because `c.IsSet(...)` is always false in unit tests + if !c.IsSet(flagName) && len(baseFlags) > 0 { + continue + } + + configValue, ok := generatedFlagNames[flagName] + if !ok { + continue + } + + kind := configValue.Kind() + if kind == reflect.Ptr { + // instantiate value to be set + configValue.Set(reflect.New(configValue.Type().Elem())) + + kind = configValue.Type().Elem().Kind() + configValue = configValue.Elem() + } + + switch kind { + case reflect.Bool: + configValue.SetBool(c.Bool(flagName)) + case reflect.String: + configValue.SetString(c.String(flagName)) + case reflect.Int, reflect.Int32, reflect.Int64: + configValue.SetInt(c.Int64(flagName)) + case reflect.Uint8, reflect.Uint16, reflect.Uint32: + configValue.SetUint(c.Uint64(flagName)) + case reflect.Float32: + configValue.SetFloat(c.Float64(flagName)) + // case reflect.Slice: + // // TODO + // case reflect.Map: + // // TODO + default: + return fmt.Errorf("unsupported generated cli flag type for config: %s is a %s", flagName, kind.String()) + } + } + if c.IsSet("dev") { conf.Development = c.Bool("dev") } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index b6ebbe5d4..aa461425b 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1,13 +1,15 @@ package config import ( + "flag" "testing" "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" ) func TestConfig_UnmarshalKeys(t *testing.T) { - conf, err := NewConfig("", true, nil) + conf, err := NewConfig("", true, nil, nil) require.NoError(t, err) require.NoError(t, conf.unmarshalKeys("key1: secret1")) @@ -17,7 +19,7 @@ func TestConfig_UnmarshalKeys(t *testing.T) { func TestConfig_DefaultsKept(t *testing.T) { const content = `room: empty_timeout: 10` - conf, err := NewConfig(content, true, nil) + conf, err := NewConfig(content, true, nil, nil) require.NoError(t, err) require.Equal(t, true, conf.Room.AutoCreate) require.Equal(t, uint32(10), conf.Room.EmptyTimeout) @@ -27,6 +29,30 @@ func TestConfig_UnknownKeys(t *testing.T) { const content = `unknown: 10 room: empty_timeout: 10` - _, err := NewConfig(content, true, nil) + _, err := NewConfig(content, true, nil, nil) require.Error(t, err) } + +func TestGeneratedFlags(t *testing.T) { + generatedFlags, err := GenerateCLIFlags(nil) + require.NoError(t, err) + + app := cli.NewApp() + app.Flags = append(app.Flags, generatedFlags...) + + set := flag.NewFlagSet("test", 0) + set.Bool("rtc.use_ice_lite", true, "") // bool + set.String("redis.address", "localhost:6379", "") // string + set.Uint("prometheus_port", 9999, "") // uint32 + set.Bool("rtc.allow_tcp_fallback", true, "") // pointer + + c := cli.NewContext(app, set, nil) + conf, err := NewConfig("", true, c, nil) + require.NoError(t, err) + + require.True(t, conf.RTC.UseICELite) + require.Equal(t, "localhost:6379", conf.Redis.Address) + require.Equal(t, uint32(9999), conf.PrometheusPort) + require.NotNil(t, conf.RTC.AllowTCPFallback) + require.True(t, *conf.RTC.AllowTCPFallback) +} diff --git a/pkg/rtc/participant_internal_test.go b/pkg/rtc/participant_internal_test.go index d20b0afce..32d02a3cb 100644 --- a/pkg/rtc/participant_internal_test.go +++ b/pkg/rtc/participant_internal_test.go @@ -443,7 +443,7 @@ func newParticipantForTestWithOpts(identity livekit.ParticipantIdentity, opts *p if opts.protocolVersion == 0 { opts.protocolVersion = 6 } - conf, _ := config.NewConfig("", true, nil) + conf, _ := config.NewConfig("", true, nil, nil) // disable mux, it doesn't play too well with unit test conf.RTC.UDPPort = 0 conf.RTC.TCPPort = 0 diff --git a/pkg/service/roomallocator_test.go b/pkg/service/roomallocator_test.go index a18c50845..ddf305d66 100644 --- a/pkg/service/roomallocator_test.go +++ b/pkg/service/roomallocator_test.go @@ -17,7 +17,7 @@ import ( func TestCreateRoom(t *testing.T) { t.Run("ensure default room settings are applied", func(t *testing.T) { - conf, err := config.NewConfig("", true, nil) + conf, err := config.NewConfig("", true, nil, nil) require.NoError(t, err) node, err := routing.NewLocalNode(conf) @@ -32,7 +32,7 @@ func TestCreateRoom(t *testing.T) { }) t.Run("reject new participants when track limit has been reached", func(t *testing.T) { - conf, err := config.NewConfig("", true, nil) + conf, err := config.NewConfig("", true, nil, nil) require.NoError(t, err) conf.Limit.NumTracks = 10 @@ -48,7 +48,7 @@ func TestCreateRoom(t *testing.T) { }) t.Run("reject new participants when bandwidth limit has been reached", func(t *testing.T) { - conf, err := config.NewConfig("", true, nil) + conf, err := config.NewConfig("", true, nil, nil) require.NoError(t, err) conf.Limit.BytesPerSec = 100 diff --git a/test/integration_helpers.go b/test/integration_helpers.go index 0409e95bc..aff574b0d 100644 --- a/test/integration_helpers.go +++ b/test/integration_helpers.go @@ -137,7 +137,7 @@ func waitUntilConnected(t *testing.T, clients ...*testclient.RTCClient) { func createSingleNodeServer(configUpdater func(*config.Config)) *service.LivekitServer { var err error - conf, err := config.NewConfig("", true, nil) + conf, err := config.NewConfig("", true, nil, nil) if err != nil { panic(fmt.Sprintf("could not create config: %v", err)) } @@ -163,7 +163,7 @@ func createSingleNodeServer(configUpdater func(*config.Config)) *service.Livekit func createMultiNodeServer(nodeID string, port uint32) *service.LivekitServer { var err error - conf, err := config.NewConfig("", true, nil) + conf, err := config.NewConfig("", true, nil, nil) if err != nil { panic(fmt.Sprintf("could not create config: %v", err)) } diff --git a/test/webhook_test.go b/test/webhook_test.go index 68431dc6f..22e901e88 100644 --- a/test/webhook_test.go +++ b/test/webhook_test.go @@ -105,7 +105,7 @@ func TestWebhooks(t *testing.T) { } func setupServerWithWebhook() (server *service.LivekitServer, testServer *webhookTestServer, finishFunc func(), err error) { - conf, err := config.NewConfig("", true, nil) + conf, err := config.NewConfig("", true, nil, nil) if err != nil { panic(fmt.Sprintf("could not create config: %v", err)) }