Merge commit '6bd7fac875d9e9009915053d8a590abb372c5679' into feature/livekit-upgrade-1.13.1
This commit is contained in:
+242
-104
@@ -18,25 +18,30 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/metric"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/bwe/remotebwe"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/bwe/sendsidebwe"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/mime"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/streamallocator"
|
||||
"github.com/livekit/mediatransportutil/pkg/rtcconfig"
|
||||
"github.com/livekit/protocol/codecs/mime"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
redisLiveKit "github.com/livekit/protocol/redis"
|
||||
"github.com/livekit/protocol/rpc"
|
||||
"github.com/livekit/protocol/webhook"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/agent"
|
||||
"github.com/livekit/livekit-server/pkg/metric"
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/bwe/remotebwe"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/bwe/sendsidebwe"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/pacer"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/streamallocator"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -44,8 +49,9 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyFileIncorrectPermission = errors.New("key file others permissions must be set to 0")
|
||||
ErrKeysNotSet = errors.New("one of key-file or keys must be provided")
|
||||
ErrKeyFileIncorrectPermission = errors.New("key file others permissions must be set to 0")
|
||||
ErrTURNSecretFileIncorrectPermission = errors.New("turn secret file others permissions must be set to 0")
|
||||
ErrKeysNotSet = errors.New("one of key-file or keys must be provided")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -62,7 +68,7 @@ type Config struct {
|
||||
TURN TURNConfig `yaml:"turn,omitempty"`
|
||||
Ingress IngressConfig `yaml:"ingress,omitempty"`
|
||||
SIP SIPConfig `yaml:"sip,omitempty"`
|
||||
WebHook WebHookConfig `yaml:"webhook,omitempty"`
|
||||
WebHook webhook.WebHookConfig `yaml:"webhook,omitempty"`
|
||||
NodeSelector NodeSelectorConfig `yaml:"node_selector,omitempty"`
|
||||
KeyFile string `yaml:"key_file,omitempty"`
|
||||
Keys map[string]string `yaml:"keys,omitempty"`
|
||||
@@ -73,10 +79,18 @@ type Config struct {
|
||||
LogLevel string `yaml:"log_level,omitempty"`
|
||||
Logging LoggingConfig `yaml:"logging,omitempty"`
|
||||
Limit LimitConfig `yaml:"limit,omitempty"`
|
||||
Agents agent.Config `yaml:"agents,omitempty"`
|
||||
|
||||
Development bool `yaml:"development,omitempty"`
|
||||
|
||||
Metric metric.MetricConfig `yaml:"metric,omitempty"`
|
||||
Trace TracingConfig `yaml:"trace,omitempty"`
|
||||
|
||||
NodeStats NodeStatsConfig `yaml:"node_stats,omitempty"`
|
||||
|
||||
EnableDataTracks bool `yaml:"enable_data_tracks,omitempty"`
|
||||
|
||||
API APIConfig `yaml:"api,omitempty"`
|
||||
}
|
||||
|
||||
type RTCConfig struct {
|
||||
@@ -102,6 +116,18 @@ type RTCConfig struct {
|
||||
// allow TCP and TURN/TLS fallback
|
||||
AllowTCPFallback *bool `yaml:"allow_tcp_fallback,omitempty"`
|
||||
|
||||
// Signaling RTT threshold (in milliseconds) governing ICE/TCP fallback. On a UDP
|
||||
// failure, ICE/TCP is attempted only while the measured signaling RTT is below this
|
||||
// value; at or above it, supporting clients fall back directly to TURN/TLS. When 0
|
||||
// (the default), the RTT check is disabled and ICE/TCP is always attempted (for
|
||||
// clients that support it). A positive value also gates allow_udp_unstable_fallback.
|
||||
TCPFallbackRTTThreshold int `yaml:"tcp_fallback_rtt_threshold,omitempty"`
|
||||
|
||||
// When enabled, an established UDP connection reporting sustained high packet loss is
|
||||
// migrated to ICE/TCP or TURN/TLS. Requires tcp_fallback_rtt_threshold to be set
|
||||
// (> 0). Disabled by default.
|
||||
AllowUDPUnstableFallback bool `yaml:"allow_udp_unstable_fallback,omitempty"`
|
||||
|
||||
// force a reconnect on a publication error
|
||||
ReconnectOnPublicationError *bool `yaml:"reconnect_on_publication_error,omitempty"`
|
||||
|
||||
@@ -118,7 +144,13 @@ type RTCConfig struct {
|
||||
// be dropped for a slow data channel to avoid blocking the room.
|
||||
DatachannelSlowThreshold int `yaml:"datachannel_slow_threshold,omitempty"`
|
||||
|
||||
// Target latency for lossy data channels, used to drop packets to reduce latency.
|
||||
DatachannelLossyTargetLatency time.Duration `yaml:"datachannel_lossy_target_latency,omitempty"`
|
||||
|
||||
ForwardStats ForwardStatsConfig `yaml:"forward_stats,omitempty"`
|
||||
|
||||
// enable rtp stream restart detection for published tracks
|
||||
EnableRTPStreamRestartDetection bool `yaml:"enable_rtp_stream_restart_detection,omitempty"`
|
||||
}
|
||||
|
||||
type TURNServer struct {
|
||||
@@ -127,6 +159,14 @@ type TURNServer struct {
|
||||
Protocol string `yaml:"protocol,omitempty"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
Credential string `yaml:"credential,omitempty"`
|
||||
// Secret is used for TURN static auth secrets mechanism. When provided,
|
||||
// dynamic credentials are generated using HMAC-SHA1 instead of static Username/Credential
|
||||
Secret string `yaml:"secret,omitempty"`
|
||||
// File containing the secret
|
||||
SecretFile string `yaml:"secret_file,omitempty"`
|
||||
// TTL is the time-to-live in seconds for generated credentials when using Secret.
|
||||
// Defaults to 14400 seconds (4 hours) if not specified
|
||||
TTL int `yaml:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type CongestionControlConfig struct {
|
||||
@@ -139,8 +179,9 @@ type CongestionControlConfig struct {
|
||||
|
||||
UseSendSideBWEInterceptor bool `yaml:"use_send_side_bwe_interceptor,omitempty"`
|
||||
|
||||
UseSendSideBWE bool `yaml:"use_send_side_bwe,omitempty"`
|
||||
SendSideBWE sendsidebwe.SendSideBWEConfig `yaml:"send_side_bwe,omitempty"`
|
||||
UseSendSideBWE bool `yaml:"use_send_side_bwe,omitempty"`
|
||||
SendSideBWEPacer string `yaml:"send_side_bwe_pacer,omitempty"`
|
||||
SendSideBWE sendsidebwe.SendSideBWEConfig `yaml:"send_side_bwe,omitempty"`
|
||||
}
|
||||
|
||||
type PlayoutDelayConfig struct {
|
||||
@@ -152,6 +193,8 @@ type PlayoutDelayConfig struct {
|
||||
type VideoConfig struct {
|
||||
DynacastPauseDelay time.Duration `yaml:"dynacast_pause_delay,omitempty"`
|
||||
StreamTrackerManager sfu.StreamTrackerManagerConfig `yaml:"stream_tracker_manager,omitempty"`
|
||||
|
||||
CodecRegressionThreshold int `yaml:"codec_regression_threshold,omitempty"`
|
||||
}
|
||||
|
||||
type RoomConfig struct {
|
||||
@@ -167,6 +210,8 @@ type RoomConfig struct {
|
||||
CreateRoomEnabled bool `yaml:"create_room_enabled,omitempty"`
|
||||
CreateRoomTimeout time.Duration `yaml:"create_room_timeout,omitempty"`
|
||||
CreateRoomAttempts int `yaml:"create_room_attempts,omitempty"`
|
||||
// target room participant update batch chunk size in bytes
|
||||
UpdateBatchTargetSize int `yaml:"update_batch_target_size,omitempty"`
|
||||
// deprecated, moved to limits
|
||||
MaxMetadataSize uint32 `yaml:"max_metadata_size,omitempty"`
|
||||
// deprecated, moved to limits
|
||||
@@ -187,26 +232,33 @@ type LoggingConfig struct {
|
||||
}
|
||||
|
||||
type TURNConfig struct {
|
||||
Enabled bool `yaml:"enabled,omitempty"`
|
||||
Domain string `yaml:"domain,omitempty"`
|
||||
CertFile string `yaml:"cert_file,omitempty"`
|
||||
KeyFile string `yaml:"key_file,omitempty"`
|
||||
TLSPort int `yaml:"tls_port,omitempty"`
|
||||
UDPPort int `yaml:"udp_port,omitempty"`
|
||||
RelayPortRangeStart uint16 `yaml:"relay_range_start,omitempty"`
|
||||
RelayPortRangeEnd uint16 `yaml:"relay_range_end,omitempty"`
|
||||
ExternalTLS bool `yaml:"external_tls,omitempty"`
|
||||
}
|
||||
|
||||
type WebHookConfig struct {
|
||||
URLs []string `yaml:"urls,omitempty"`
|
||||
// key to use for webhook
|
||||
APIKey string `yaml:"api_key,omitempty"`
|
||||
Enabled bool `yaml:"enabled,omitempty"`
|
||||
Domain string `yaml:"domain,omitempty"`
|
||||
CertFile string `yaml:"cert_file,omitempty"`
|
||||
KeyFile string `yaml:"key_file,omitempty"`
|
||||
TLSPort int `yaml:"tls_port,omitempty"`
|
||||
UDPPort int `yaml:"udp_port,omitempty"`
|
||||
RelayPortRangeStart uint16 `yaml:"relay_range_start,omitempty"`
|
||||
RelayPortRangeEnd uint16 `yaml:"relay_range_end,omitempty"`
|
||||
ExternalTLS bool `yaml:"external_tls,omitempty"`
|
||||
BindAddresses []string `yaml:"bind_addresses,omitempty"`
|
||||
// TTL of the TURN credentials in seconds - defaults to 300
|
||||
TTLSeconds int `yaml:"ttl_seconds,omitempty"`
|
||||
// list of restricted peer CIDRs (loopback, link-local (unicast, multicast), multicast, private, unspecified) to allow access to.
|
||||
// By default (i. e. empty list), all restricted peer CIDRs are denied access.
|
||||
// When not empty, only the specified CIDRs are allowed access.
|
||||
// Note that this check is applied to restricted peer CIDRs only.
|
||||
AllowRestrictedPeerCIDRs []string `yaml:"allow_restricted_peer_cidrs,omitempty"`
|
||||
// list of peer CIDRs to deny access to
|
||||
// This applies to all peer CIDRs, including restricted ones.
|
||||
// Deny list takes precedence over allow list.
|
||||
DenyPeerCIDRs []string `yaml:"deny_peer_cidrs,omitempty"`
|
||||
}
|
||||
|
||||
type NodeSelectorConfig struct {
|
||||
Kind string `yaml:"kind,omitempty"`
|
||||
SortBy string `yaml:"sort_by,omitempty"`
|
||||
Algorithm string `yaml:"algorithm,omitempty"`
|
||||
CPULoadLimit float32 `yaml:"cpu_load_limit,omitempty"`
|
||||
SysloadLimit float32 `yaml:"sysload_limit,omitempty"`
|
||||
Regions []RegionConfig `yaml:"regions,omitempty"`
|
||||
@@ -245,6 +297,10 @@ func (l LimitConfig) CheckRoomNameLength(name string) bool {
|
||||
return l.MaxRoomNameLength == 0 || len(name) <= l.MaxRoomNameLength
|
||||
}
|
||||
|
||||
func (l LimitConfig) CheckParticipantIdentityLength(identity string) bool {
|
||||
return l.MaxParticipantIdentityLength == 0 || len(identity) <= l.MaxParticipantIdentityLength
|
||||
}
|
||||
|
||||
func (l LimitConfig) CheckParticipantNameLength(name string) bool {
|
||||
return l.MaxParticipantNameLength == 0 || len(name) <= l.MaxParticipantNameLength
|
||||
}
|
||||
@@ -281,6 +337,9 @@ type APIConfig struct {
|
||||
|
||||
// max amount of time to wait before checking for operation complete
|
||||
MaxCheckInterval time.Duration `yaml:"max_check_interval,omitempty"`
|
||||
|
||||
// Backwards compatibility for room service api calls, will enable by default and remove in a future release
|
||||
EnablePsrpcForGetListParticpants bool `yaml:"enable_psrpc_for_get_list_participants,omitempty"`
|
||||
}
|
||||
|
||||
type PrometheusConfig struct {
|
||||
@@ -295,6 +354,13 @@ type ForwardStatsConfig struct {
|
||||
ReportWindow time.Duration `yaml:"report_window,omitempty"`
|
||||
}
|
||||
|
||||
type TracingConfig struct {
|
||||
// JaegerURL configures Jaeger as a global tracer.
|
||||
//
|
||||
// The following formats are supported: <hostname>, <host>:<port>, http(s)://<host>/<path>
|
||||
JaegerURL string `yaml:"jaeger_url,omitempty"`
|
||||
}
|
||||
|
||||
func DefaultAPIConfig() APIConfig {
|
||||
return APIConfig{
|
||||
ExecutionTimeout: 2 * time.Second,
|
||||
@@ -303,6 +369,18 @@ func DefaultAPIConfig() APIConfig {
|
||||
}
|
||||
}
|
||||
|
||||
type NodeStatsConfig struct {
|
||||
StatsUpdateInterval time.Duration `yaml:"stats_update_interval,omitempty"`
|
||||
StatsRateMeasurementIntervals []time.Duration `yaml:"stats_rate_measurement_intervals,omitempty"`
|
||||
StatsMaxDelay time.Duration `yaml:"stats_max_delay,omitempty"`
|
||||
}
|
||||
|
||||
var DefaultNodeStatsConfig = NodeStatsConfig{
|
||||
StatsUpdateInterval: 2 * time.Second,
|
||||
StatsRateMeasurementIntervals: []time.Duration{10 * time.Second},
|
||||
StatsMaxDelay: 30 * time.Second,
|
||||
}
|
||||
|
||||
var DefaultConfig = Config{
|
||||
Port: 7880,
|
||||
RTC: RTCConfig{
|
||||
@@ -324,31 +402,37 @@ var DefaultConfig = Config{
|
||||
RemoteBWE: remotebwe.DefaultRemoteBWEConfig,
|
||||
UseSendSideBWEInterceptor: false,
|
||||
UseSendSideBWE: false,
|
||||
SendSideBWEPacer: string(pacer.PacerBehaviorNoQueue),
|
||||
SendSideBWE: sendsidebwe.DefaultSendSideBWEConfig,
|
||||
},
|
||||
},
|
||||
Audio: sfu.DefaultAudioConfig,
|
||||
Video: VideoConfig{
|
||||
DynacastPauseDelay: 5 * time.Second,
|
||||
StreamTrackerManager: sfu.DefaultStreamTrackerManagerConfig,
|
||||
DynacastPauseDelay: 5 * time.Second,
|
||||
StreamTrackerManager: sfu.DefaultStreamTrackerManagerConfig,
|
||||
CodecRegressionThreshold: 5,
|
||||
},
|
||||
Redis: redisLiveKit.RedisConfig{},
|
||||
Room: RoomConfig{
|
||||
AutoCreate: true,
|
||||
EnabledCodecs: []CodecSpec{
|
||||
{Mime: mime.MimeTypePCMU.String()},
|
||||
{Mime: mime.MimeTypePCMA.String()},
|
||||
{Mime: mime.MimeTypeOpus.String()},
|
||||
{Mime: mime.MimeTypeRED.String()},
|
||||
{Mime: mime.MimeTypeVP8.String()},
|
||||
{Mime: mime.MimeTypeH264.String()},
|
||||
{Mime: mime.MimeTypeVP9.String()},
|
||||
{Mime: mime.MimeTypeAV1.String()},
|
||||
{Mime: mime.MimeTypeH265.String()},
|
||||
{Mime: mime.MimeTypeRTX.String()},
|
||||
},
|
||||
EmptyTimeout: 5 * 60,
|
||||
DepartureTimeout: 20,
|
||||
CreateRoomEnabled: true,
|
||||
CreateRoomTimeout: 10 * time.Second,
|
||||
CreateRoomAttempts: 3,
|
||||
EmptyTimeout: 5 * 60,
|
||||
DepartureTimeout: 20,
|
||||
CreateRoomEnabled: true,
|
||||
CreateRoomTimeout: 10 * time.Second,
|
||||
CreateRoomAttempts: 3,
|
||||
UpdateBatchTargetSize: 128 * 1024,
|
||||
},
|
||||
Limit: LimitConfig{
|
||||
MaxMetadataSize: 64000,
|
||||
@@ -361,13 +445,16 @@ var DefaultConfig = Config{
|
||||
PionLevel: "error",
|
||||
},
|
||||
TURN: TURNConfig{
|
||||
Enabled: false,
|
||||
Enabled: false,
|
||||
BindAddresses: []string{"0.0.0.0"},
|
||||
TTLSeconds: 300,
|
||||
},
|
||||
NodeSelector: NodeSelectorConfig{
|
||||
Kind: "any",
|
||||
SortBy: "random",
|
||||
SysloadLimit: 0.9,
|
||||
CPULoadLimit: 0.9,
|
||||
Algorithm: "lowest",
|
||||
},
|
||||
SignalRelay: SignalRelayConfig{
|
||||
RetryTimeout: 7500 * time.Millisecond,
|
||||
@@ -376,12 +463,19 @@ var DefaultConfig = Config{
|
||||
StreamBufferSize: 1000,
|
||||
ConnectAttempts: 3,
|
||||
},
|
||||
PSRPC: rpc.DefaultPSRPCConfig,
|
||||
Keys: map[string]string{},
|
||||
Metric: metric.DefaultMetricConfig,
|
||||
Agents: agent.Config{
|
||||
TargetLoad: agent.DefaultTargetLoad,
|
||||
},
|
||||
PSRPC: rpc.DefaultPSRPCConfig,
|
||||
Keys: map[string]string{},
|
||||
Metric: metric.DefaultMetricConfig,
|
||||
WebHook: webhook.DefaultWebHookConfig,
|
||||
NodeStats: DefaultNodeStatsConfig,
|
||||
API: DefaultAPIConfig(),
|
||||
EnableDataTracks: true,
|
||||
}
|
||||
|
||||
func NewConfig(confString string, strictMode bool, c *cli.Context, baseFlags []cli.Flag) (*Config, error) {
|
||||
func NewConfig(confString string, strictMode bool, c *cli.Command, baseFlags []cli.Flag) (*Config, error) {
|
||||
// start with defaults
|
||||
marshalled, err := yaml.Marshal(&DefaultConfig)
|
||||
if err != nil {
|
||||
@@ -563,74 +657,110 @@ func (conf *Config) ValidateKeys() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conf *Config) LoadTURNSecrets() error {
|
||||
var otherFilter os.FileMode = 0o007
|
||||
for i, s := range conf.RTC.TURNServers {
|
||||
if s.SecretFile == "" {
|
||||
continue
|
||||
}
|
||||
if s.Secret != "" {
|
||||
logger.Warnw("both secret and secret_file are set for TURN server, the hardcoded secret will be used", nil,
|
||||
"host", s.Host, "port", s.Port)
|
||||
continue
|
||||
}
|
||||
st, err := os.Stat(s.SecretFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if st.Mode().Perm()&otherFilter != 0o000 {
|
||||
return ErrTURNSecretFileIncorrectPermission
|
||||
}
|
||||
data, err := os.ReadFile(s.SecretFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading turn secret file %q: %w", s.SecretFile, err)
|
||||
}
|
||||
conf.RTC.TURNServers[i].Secret = strings.TrimSpace(string(data))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateCLIFlags(existingFlags []cli.Flag, hidden bool) ([]cli.Flag, error) {
|
||||
blankConfig := &Config{}
|
||||
defaultConfig := &DefaultConfig
|
||||
flags := make([]cli.Flag, 0)
|
||||
for name, value := range blankConfig.ToCLIFlagNames(existingFlags) {
|
||||
for name, value := range (defaultConfig).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)))
|
||||
envVar := fmt.Sprintf("LIVEKIT_%s", strings.ToUpper(strings.ReplaceAll(name, ".", "_")))
|
||||
defaultText := cliDefaultText(value)
|
||||
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
flag = &cli.BoolFlag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
Name: name,
|
||||
Sources: cli.EnvVars(envVar),
|
||||
Usage: generatedCLIFlagUsage,
|
||||
DefaultText: defaultText,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.String:
|
||||
flag = &cli.StringFlag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
Name: name,
|
||||
Sources: cli.EnvVars(envVar),
|
||||
Usage: generatedCLIFlagUsage,
|
||||
DefaultText: defaultText,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Int, reflect.Int32:
|
||||
flag = &cli.IntFlag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
Name: name,
|
||||
Sources: cli.EnvVars(envVar),
|
||||
Usage: generatedCLIFlagUsage,
|
||||
DefaultText: defaultText,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Int64:
|
||||
flag = &cli.Int64Flag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
Name: name,
|
||||
Sources: cli.EnvVars(envVar),
|
||||
Usage: generatedCLIFlagUsage,
|
||||
DefaultText: defaultText,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
flag = &cli.UintFlag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
Name: name,
|
||||
Sources: cli.EnvVars(envVar),
|
||||
Usage: generatedCLIFlagUsage,
|
||||
DefaultText: defaultText,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Uint64:
|
||||
flag = &cli.Uint64Flag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
Name: name,
|
||||
Sources: cli.EnvVars(envVar),
|
||||
Usage: generatedCLIFlagUsage,
|
||||
DefaultText: defaultText,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Float32:
|
||||
flag = &cli.Float64Flag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
Name: name,
|
||||
Sources: cli.EnvVars(envVar),
|
||||
Usage: generatedCLIFlagUsage,
|
||||
DefaultText: defaultText,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Float64:
|
||||
flag = &cli.Float64Flag{
|
||||
Name: name,
|
||||
EnvVars: []string{envVar},
|
||||
Usage: generatedCLIFlagUsage,
|
||||
Hidden: hidden,
|
||||
Name: name,
|
||||
Sources: cli.EnvVars(envVar),
|
||||
Usage: generatedCLIFlagUsage,
|
||||
DefaultText: defaultText,
|
||||
Hidden: hidden,
|
||||
}
|
||||
case reflect.Slice:
|
||||
// TODO
|
||||
@@ -651,13 +781,39 @@ func GenerateCLIFlags(existingFlags []cli.Flag, hidden bool) ([]cli.Flag, error)
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
func (conf *Config) updateFromCLI(c *cli.Context, baseFlags []cli.Flag) error {
|
||||
func cliDefaultText(value reflect.Value) string {
|
||||
if value.Kind() == reflect.Ptr {
|
||||
if value.IsNil() {
|
||||
return ""
|
||||
}
|
||||
value = value.Elem()
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Bool:
|
||||
return strconv.FormatBool(value.Bool())
|
||||
case reflect.String:
|
||||
return value.String()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if value.Type() == reflect.TypeOf(time.Duration(0)) {
|
||||
return value.Interface().(time.Duration).String()
|
||||
}
|
||||
return strconv.FormatInt(value.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return strconv.FormatUint(value.Uint(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return strconv.FormatFloat(value.Float(), 'f', -1, 64)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *Config) updateFromCLI(c *cli.Command, baseFlags []cli.Flag) error {
|
||||
generatedFlagNames := conf.ToCLIFlagNames(baseFlags)
|
||||
for _, flag := range c.App.Flags {
|
||||
for _, flag := range c.Flags {
|
||||
flagName := flag.Names()[0]
|
||||
|
||||
// the `c.App.Name != "test"` check is needed because `c.IsSet(...)` is always false in unit tests
|
||||
if !c.IsSet(flagName) && c.App.Name != "test" {
|
||||
if !c.IsSet(flagName) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -666,34 +822,16 @@ func (conf *Config) updateFromCLI(c *cli.Context, baseFlags []cli.Flag) error {
|
||||
continue
|
||||
}
|
||||
|
||||
kind := configValue.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
// instantiate value to be set
|
||||
if configValue.Kind() == reflect.Ptr {
|
||||
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, reflect.Uint64:
|
||||
configValue.SetUint(c.Uint64(flagName))
|
||||
case reflect.Float32:
|
||||
configValue.SetFloat(c.Float64(flagName))
|
||||
case reflect.Float64:
|
||||
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())
|
||||
value := reflect.ValueOf(c.Value(flagName))
|
||||
if value.CanConvert(configValue.Type()) {
|
||||
configValue.Set(value.Convert(configValue.Type()))
|
||||
} else {
|
||||
return fmt.Errorf("unsupported generated cli flag type for config: %s (expected %s, got %s)", flagName, configValue.Type(), value.Type())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,7 +862,7 @@ func (conf *Config) updateFromCLI(c *cli.Context, baseFlags []cli.Flag) error {
|
||||
conf.TURN.KeyFile = c.String("turn-key")
|
||||
}
|
||||
if c.IsSet("node-ip") {
|
||||
conf.RTC.NodeIP = c.String("node-ip")
|
||||
conf.RTC.NodeIP.UnmarshalString(c.String("node-ip"))
|
||||
}
|
||||
if c.IsSet("udp-port") {
|
||||
conf.RTC.UDPPort.UnmarshalString(c.String("udp-port"))
|
||||
@@ -736,7 +874,7 @@ func (conf *Config) updateFromCLI(c *cli.Context, baseFlags []cli.Flag) error {
|
||||
}
|
||||
|
||||
func (conf *Config) unmarshalKeys(keys string) error {
|
||||
temp := make(map[string]interface{})
|
||||
temp := make(map[string]any)
|
||||
if err := yaml.Unmarshal([]byte(keys), temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config/configtest"
|
||||
)
|
||||
@@ -53,19 +52,17 @@ func TestGeneratedFlags(t *testing.T) {
|
||||
generatedFlags, err := GenerateCLIFlags(nil, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "test"
|
||||
app.Flags = append(app.Flags, generatedFlags...)
|
||||
c := &cli.Command{}
|
||||
c.Name = "test"
|
||||
c.Flags = append(c.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
|
||||
set.Bool("rtc.reconnect_on_publication_error", true, "") // pointer
|
||||
set.Bool("rtc.reconnect_on_subscription_error", false, "") // pointer
|
||||
c.Set("rtc.use_ice_lite", "true")
|
||||
c.Set("redis.address", "localhost:6379")
|
||||
c.Set("prometheus.port", "9999")
|
||||
c.Set("rtc.allow_tcp_fallback", "true")
|
||||
c.Set("rtc.reconnect_on_publication_error", "true")
|
||||
c.Set("rtc.reconnect_on_subscription_error", "false")
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
conf, err := NewConfig("", true, c, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user