Files
upstream-centrifugo/internal/config/config.go
upstream-mirror b352a9a30d
Some checks failed
test / Lint (push) Failing after 3s
test / Test with Go 1.26.8 (push) Failing after 2s
snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:17:21 +05:00

430 lines
25 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package config contains Centrifugo Config and the code to load it.
package config
import (
"errors"
"fmt"
"os"
"reflect"
"regexp"
"slices"
"strings"
"github.com/centrifugal/centrifugo/v6/internal/config/envconfig"
"github.com/centrifugal/centrifugo/v6/internal/configtypes"
"github.com/go-viper/mapstructure/v2"
"github.com/hashicorp/go-envparse"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
// Config contains configuration options of Centrifugo.
type Config struct {
// HTTP is a configuration for Centrifugo HTTP server.
HTTP configtypes.HTTPServer `mapstructure:"http_server" json:"http_server" envconfig:"http_server" toml:"http_server" yaml:"http_server" doc:"Configures the main HTTP server: listen address, ports, and the internal endpoints port."`
// Log is a configuration for logging.
Log configtypes.Log `mapstructure:"log" json:"log" envconfig:"log" toml:"log" yaml:"log" doc:"Configures logging: level and optional log file."`
// Engine is a configuration for Centrifugo engine. It's a handy combination of Broker and PresenceManager.
// Currently only memory and redis engines are supported – both implement all the features. For more granular
// control use `broker` and `presence_manager` options.
Engine configtypes.Engine `mapstructure:"engine" json:"engine" envconfig:"engine" toml:"engine" yaml:"engine" doc:"Selects the engine - a combined broker and presence manager. Supports <<memory>> (default) and <<redis>>. For finer control use <<broker>> and <<presence_manager>> instead."`
// Broker allows to configure a message broker to use. Broker is responsible for PUB/SUB functionality
// and channel message history and idempotency cache.
// By default, memory Broker is used. Memory broker is superfast, but it's not distributed and all
// data stored in memory (thus lost after node restart). Redis Broker provides seamless horizontal
// scalability, fault-tolerance, and persistence over Centrifugo restarts. Centrifugo also supports
// Nats Broker which only implements at most once PUB/SUB semantics.
Broker configtypes.Broker `mapstructure:"broker" json:"broker" envconfig:"broker" toml:"broker" yaml:"broker" doc:"Configures the message broker (PUB/SUB, history, idempotency cache) when set separately from the engine. Supports <<memory>> (default, not distributed, lost on restart), <<redis>> (scalable and persistent), and <<nats>> (at-most-once PUB/SUB only)."`
// PresenceManager allows to configure a presence manager to use. Presence manager is responsible for
// presence information storage and retrieval. By default, memory PresenceManager is used. Memory
// PresenceManager is superfast, but it's not distributed. Redis PresenceManager provides a seamless
// horizontal scalability.
PresenceManager configtypes.PresenceManager `mapstructure:"presence_manager" json:"presence_manager" envconfig:"presence_manager" toml:"presence_manager" yaml:"presence_manager" doc:"Configures the presence manager when set separately from the engine. Supports <<memory>> (default, not distributed) and <<redis>> (scalable)."`
// MapBroker allows to configure a map broker for synchronized keyed state channels.
// When enabled, clients can use map subscription types for channels in namespaces
// that have a map subscription_type.
MapBroker configtypes.MapBroker `mapstructure:"map_broker" json:"map_broker" envconfig:"map_broker" toml:"map_broker" yaml:"map_broker" doc:"Configures the map broker for synchronized keyed-state channels. Used by namespaces whose subscription type is <<map>>."`
// Controller is a configuration for custom Centrifugo Controller used for
// cross-node communication in multi-node clusters.
Controller configtypes.Controller `mapstructure:"controller" json:"controller" envconfig:"controller" toml:"controller" yaml:"controller" doc:"Configures the controller used for cross-node communication in multi-node clusters."`
// SharedPoll contains configuration for shared poll subscriptions.
SharedPoll configtypes.SharedPoll `mapstructure:"shared_poll" json:"shared_poll" envconfig:"shared_poll" toml:"shared_poll" yaml:"shared_poll" doc:"Configures shared poll subscriptions."`
// Client contains real-time client connection related configuration.
Client configtypes.Client `mapstructure:"client" json:"client" envconfig:"client" toml:"client" yaml:"client" doc:"Configures real-time client connections: auth, tokens, allowed origins, limits, proxies, and the insecure mode."`
// Channel contains real-time channel related configuration.
Channel configtypes.Channel `mapstructure:"channel" json:"channel" envconfig:"channel" toml:"channel" yaml:"channel" doc:"Configures channels and namespaces: separators, default options, and per-namespace behavior."`
// RPC is a configuration for client RPC calls.
RPC configtypes.RPC `mapstructure:"rpc" json:"rpc" envconfig:"rpc" toml:"rpc" yaml:"rpc" doc:"Configures client RPC calls and RPC namespaces."`
// Proxies is an array of proxies with custom names for the more granular control of channel-related events
// in different channel namespaces.
Proxies configtypes.NamedProxies `mapstructure:"proxies" default:"[]" json:"proxies" envconfig:"proxies" yaml:"proxies" toml:"proxies" doc:"Defines named proxies that namespaces can reference to forward channel-related events to your backend."`
// HttpAPI is a configuration for HTTP server API. It's enabled by default.
HttpAPI configtypes.HttpAPI `mapstructure:"http_api" json:"http_api" envconfig:"http_api" toml:"http_api" yaml:"http_api" doc:"Configures the server HTTP API (enabled by default): API key, insecure mode, and whether it's exposed on the external port."`
// GrpcAPI is a configuration for gRPC server API. It's disabled by default.
GrpcAPI configtypes.GrpcAPI `mapstructure:"grpc_api" json:"grpc_api" envconfig:"grpc_api" toml:"grpc_api" yaml:"grpc_api" doc:"Configures the server gRPC API (disabled by default): enable flag, port, TLS, and auth."`
// Consumers is a configuration for message queue consumers. For example, Centrifugo can consume
// messages from PostgreSQL transactional outbox table, or from Kafka topics.
Consumers configtypes.Consumers `mapstructure:"consumers" default:"[]" json:"consumers" envconfig:"consumers" toml:"consumers" yaml:"consumers" doc:"Defines message queue consumers that ingest commands from external sources such as a PostgreSQL outbox table or Kafka topics."`
// WebSocket configuration. This transport is enabled by default.
WebSocket configtypes.WebSocket `mapstructure:"websocket" json:"websocket" envconfig:"websocket" toml:"websocket" yaml:"websocket" doc:"Configures the bidirectional WebSocket transport (enabled by default): message size limits, compression, ping/pong, and write timeouts."`
// SSE is a configuration for Server-Sent Events based bidirectional emulation transport.
SSE configtypes.SSE `mapstructure:"sse" json:"sse" envconfig:"sse" toml:"sse" yaml:"sse" doc:"Configures the bidirectional Server-Sent Events (EventSource) transport, which relies on the emulation endpoint. Disabled by default."`
// HTTPStream is a configuration for HTTP streaming based bidirectional emulation transport.
HTTPStream configtypes.HTTPStream `mapstructure:"http_stream" json:"http_stream" envconfig:"http_stream" toml:"http_stream" yaml:"http_stream" doc:"Configures the bidirectional HTTP-streaming transport, which relies on the emulation endpoint. Disabled by default."`
// WebTransport is a configuration for WebTransport transport. EXPERIMENTAL.
WebTransport configtypes.WebTransport `mapstructure:"webtransport" json:"webtransport" envconfig:"webtransport" toml:"webtransport" yaml:"webtransport" doc:"Configures the bidirectional WebTransport (HTTP/3) transport. Experimental and disabled by default."`
// UniSSE is a configuration for unidirectional Server-Sent Events transport.
UniSSE configtypes.UniSSE `mapstructure:"uni_sse" json:"uni_sse" envconfig:"uni_sse" toml:"uni_sse" yaml:"uni_sse" doc:"Configures the unidirectional Server-Sent Events (EventSource) transport. Disabled by default."`
// UniHTTPStream is a configuration for unidirectional HTTP streaming transport.
UniHTTPStream configtypes.UniHTTPStream `mapstructure:"uni_http_stream" json:"uni_http_stream" envconfig:"uni_http_stream" toml:"uni_http_stream" yaml:"uni_http_stream" doc:"Configures the unidirectional HTTP-streaming transport. Disabled by default."`
// UniWS is a configuration for unidirectional WebSocket transport.
UniWS configtypes.UniWebSocket `mapstructure:"uni_websocket" json:"uni_websocket" envconfig:"uni_websocket" toml:"uni_websocket" yaml:"uni_websocket" doc:"Configures the unidirectional WebSocket transport. Disabled by default."`
// UniGRPC is a configuration for unidirectional gRPC transport.
UniGRPC configtypes.UniGRPC `mapstructure:"uni_grpc" json:"uni_grpc" envconfig:"uni_grpc" toml:"uni_grpc" yaml:"uni_grpc" doc:"Configures the unidirectional gRPC transport: enable flag, port, and TLS. Disabled by default."`
// Emulation endpoint is enabled automatically when at least one bidirectional emulation transport
// is configured (SSE or HTTP Stream).
Emulation configtypes.Emulation `mapstructure:"emulation" json:"emulation" envconfig:"emulation" toml:"emulation" yaml:"emulation" doc:"Configures the emulation endpoint used by bidirectional SSE and HTTP-streaming transports. Enabled automatically when one of those transports is enabled."`
// Admin web UI configuration.
Admin configtypes.Admin `mapstructure:"admin" json:"admin" envconfig:"admin" toml:"admin" yaml:"admin" doc:"Configures the admin web UI and its API endpoints."`
// Prometheus metrics configuration.
Prometheus configtypes.Prometheus `mapstructure:"prometheus" json:"prometheus" envconfig:"prometheus" toml:"prometheus" yaml:"prometheus" doc:"Configures the Prometheus metrics endpoint."`
// Health check endpoint configuration.
Health configtypes.Health `mapstructure:"health" json:"health" envconfig:"health" toml:"health" yaml:"health" doc:"Configures the health check endpoint."`
// Swagger documentation (for server HTTP API) configuration.
Swagger configtypes.Swagger `mapstructure:"swagger" json:"swagger" envconfig:"swagger" toml:"swagger" yaml:"swagger" doc:"Configures the Swagger UI endpoint describing the server HTTP API."`
// Debug helps to enable Go profiling endpoints.
Debug configtypes.Debug `mapstructure:"debug" json:"debug" envconfig:"debug" toml:"debug" yaml:"debug" doc:"Configures Go pprof profiling endpoints. For debugging only - do not expose publicly."`
// Init is a configuration for connection initialization endpoint.
Init configtypes.ConnInit `mapstructure:"init" json:"init" envconfig:"init" toml:"init" yaml:"init" doc:"Configures the connection initialization endpoint."`
// OpenTelemetry is a configuration for OpenTelemetry tracing.
OpenTelemetry configtypes.OpenTelemetry `mapstructure:"opentelemetry" json:"opentelemetry" envconfig:"opentelemetry" toml:"opentelemetry" yaml:"opentelemetry" doc:"Configures OpenTelemetry tracing export."`
// Graphite is a configuration for export metrics to Graphite.
Graphite configtypes.Graphite `mapstructure:"graphite" json:"graphite" envconfig:"graphite" toml:"graphite" yaml:"graphite" doc:"Configures exporting metrics to Graphite."`
// UsageStats is a configuration for usage stats sending.
UsageStats configtypes.UsageStats `mapstructure:"usage_stats" json:"usage_stats" envconfig:"usage_stats" toml:"usage_stats" yaml:"usage_stats" doc:"Configures anonymous usage statistics reporting. Set <<enabled>> to <<false>> to opt out."`
// Node is a configuration for Centrifugo Node as part of cluster.
Node configtypes.Node `mapstructure:"node" json:"node" envconfig:"node" toml:"node" yaml:"node" doc:"Configures this node as part of a Centrifugo cluster: node name and inter-node info settings."`
// Shutdown is a configuration for graceful shutdown.
Shutdown configtypes.Shutdown `mapstructure:"shutdown" json:"shutdown" envconfig:"shutdown" toml:"shutdown" yaml:"shutdown" doc:"Configures graceful shutdown behavior, such as the shutdown termination timeout."`
// PidFile is a path to write a file with Centrifugo process PID.
PidFile string `mapstructure:"pid_file" json:"pid_file" envconfig:"pid_file" toml:"pid_file" yaml:"pid_file" expose:"full" doc:"Path of a file to write the Centrifugo process PID to. Leave empty to disable."`
// EnableUnreleasedFeatures enables unreleased features. These features are not stable and may be removed even
// in minor release update. Evaluate and share feedback if you find some feature useful and want it to be stabilized.
EnableUnreleasedFeatures bool `mapstructure:"enable_unreleased_features" json:"enable_unreleased_features" envconfig:"enable_unreleased_features" toml:"enable_unreleased_features" yaml:"enable_unreleased_features" doc:"Enables unstable, unreleased features. These may change or be removed even in a minor release - do not rely on them in production."`
}
type Meta struct {
FileNotFound bool
UnknownKeys []string
UnknownEnvs []string
KnownEnvVars map[string]envconfig.VarInfo
DeprecationWarnings []string
}
func DefineFlags(rootCmd *cobra.Command) {
rootCmd.Flags().StringP("pid_file", "", "", "optional path to create PID file")
rootCmd.Flags().StringP("http_server.address", "a", "", "interface address to listen on")
rootCmd.Flags().StringP("http_server.port", "p", "8000", "port to bind HTTP server to")
rootCmd.Flags().StringP("http_server.internal_address", "", "", "custom interface address to listen on for internal endpoints")
rootCmd.Flags().StringP("http_server.internal_port", "", "", "custom port for internal endpoints")
rootCmd.Flags().StringP("engine.type", "", "memory", "engine to use: memory or redis")
rootCmd.Flags().BoolP("broker.enabled", "", false, "enable broker")
rootCmd.Flags().StringP("broker.type", "", "memory", "broker to use: memory, redis or nats")
rootCmd.Flags().BoolP("presence_manager.enabled", "", false, "enable presence manager")
rootCmd.Flags().StringP("presence_manager.type", "", "memory", "presence manager to use: memory or redis")
rootCmd.Flags().StringP("map_broker.type", "", "memory", "map broker to use: memory, redis or postgres")
rootCmd.Flags().StringP("log.level", "", "info", "set the log level: trace, debug, info, warn, error, fatal or none")
rootCmd.Flags().StringP("log.file", "", "", "optional log file - if not specified logs go to STDOUT")
rootCmd.Flags().BoolP("debug.enabled", "", false, "enable debug endpoints")
rootCmd.Flags().BoolP("admin.enabled", "", false, "enable admin web interface")
rootCmd.Flags().BoolP("admin.external", "", false, "expose admin web interface on external port")
rootCmd.Flags().BoolP("prometheus.enabled", "", false, "enable Prometheus metrics endpoint")
rootCmd.Flags().BoolP("swagger.enabled", "", false, "enable Swagger UI endpoint describing server HTTP API")
rootCmd.Flags().BoolP("health.enabled", "", false, "enable health check endpoint")
rootCmd.Flags().BoolP("uni_websocket.enabled", "", false, "enable unidirectional websocket endpoint")
rootCmd.Flags().BoolP("uni_sse.enabled", "", false, "enable unidirectional SSE (EventSource) endpoint")
rootCmd.Flags().BoolP("uni_http_stream.enabled", "", false, "enable unidirectional HTTP-streaming endpoint")
rootCmd.Flags().BoolP("sse.enabled", "", false, "enable bidirectional SSE (EventSource) endpoint (with emulation layer)")
rootCmd.Flags().BoolP("http_stream.enabled", "", false, "enable bidirectional HTTP-streaming endpoint (with emulation layer)")
rootCmd.Flags().BoolP("init.enabled", "", false, "enable connection init endpoint")
rootCmd.Flags().BoolP("client.insecure", "", false, "start in insecure client mode")
rootCmd.Flags().BoolP("http_api.insecure", "", false, "use insecure API mode")
rootCmd.Flags().BoolP("http_api.external", "", false, "expose API handler on external port")
rootCmd.Flags().BoolP("admin.insecure", "", false, "use insecure admin mode – no auth required for admin socket")
rootCmd.Flags().BoolP("grpc_api.enabled", "", false, "enable GRPC API server")
rootCmd.Flags().IntP("grpc_api.port", "", 10000, "port to bind GRPC API server to")
rootCmd.Flags().BoolP("uni_grpc.enabled", "", false, "enable unidirectional GRPC endpoint")
rootCmd.Flags().IntP("uni_grpc.port", "", 11000, "port to bind unidirectional GRPC server to")
}
func GetConfig(cmd *cobra.Command, configFile string) (Config, Meta, error) {
v := viper.NewWithOptions(viper.WithDecodeHook(mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
configtypes.StringToDurationHookFunc(),
configtypes.StringToPEMDataHookFunc(),
configtypes.StringToMapStringStringHookFunc(),
configtypes.StringToStringKeyValuesHookFunc(),
)))
if cmd != nil {
bindPFlags := []string{
"pid_file", "http_server.port", "http_server.address", "http_server.internal_port",
"http_server.internal_address", "log.level", "log.file", "engine.type", "broker.enabled", "broker.type",
"presence_manager.enabled", "presence_manager.type", "map_broker.type",
"debug.enabled", "admin.enabled", "admin.external",
"admin.insecure", "client.insecure", "http_api.insecure", "http_api.external", "prometheus.enabled",
"health.enabled", "grpc_api.enabled", "grpc_api.port", "uni_grpc.enabled", "uni_grpc.port",
"uni_websocket.enabled", "uni_sse.enabled", "uni_http_stream.enabled", "sse.enabled", "http_stream.enabled",
"swagger.enabled", "init.enabled",
}
for _, flag := range bindPFlags {
_ = v.BindPFlag(flag, cmd.Flags().Lookup(flag))
}
}
meta := Meta{}
if configFile != "" {
v.SetConfigFile(configFile)
err := v.ReadInConfig()
if err != nil {
var configFileNotFoundError *os.PathError
if errors.As(err, &configFileNotFoundError) {
meta.FileNotFound = true
} else {
return Config{}, Meta{}, fmt.Errorf("error reading config file %s: %w", configFile, err)
}
}
}
conf := &Config{}
err := v.Unmarshal(conf)
if err != nil {
return Config{}, Meta{}, fmt.Errorf("error unmarshalling config: %w", err)
}
var flagsSet []string
if cmd != nil {
cmd.Flags().Visit(func(f *pflag.Flag) {
if !slices.Contains(flagsSet, f.Name) {
flagsSet = append(flagsSet, f.Name) // pid_file, http_server.port, etc.
}
})
}
knownEnvVars := map[string]envconfig.VarInfo{}
varInfo, err := envconfig.ProcessWithoutNestedKeys("CENTRIFUGO", conf, flagsSet)
if err != nil {
return Config{}, Meta{}, fmt.Errorf("error processing env: %w", err)
}
extendKnownEnvVars(knownEnvVars, varInfo)
for i, item := range conf.Channel.Namespaces {
varInfo, err = envconfig.Process("CENTRIFUGO_CHANNEL_NAMESPACES_"+configtypes.NameForEnv(item.Name), &item)
if err != nil {
return Config{}, Meta{}, fmt.Errorf("error processing env namespaces: %w", err)
}
conf.Channel.Namespaces[i] = item
extendKnownEnvVars(knownEnvVars, varInfo)
}
for i, item := range conf.RPC.Namespaces {
varInfo, err = envconfig.Process("CENTRIFUGO_RPC_NAMESPACES_"+configtypes.NameForEnv(item.Name), &item)
if err != nil {
return Config{}, Meta{}, fmt.Errorf("error processing env rpc namespaces: %w", err)
}
conf.RPC.Namespaces[i] = item
extendKnownEnvVars(knownEnvVars, varInfo)
}
for i, item := range conf.Proxies {
varInfo, err = envconfig.Process("CENTRIFUGO_PROXIES_"+configtypes.NameForEnv(item.Name), &item)
if err != nil {
return Config{}, Meta{}, fmt.Errorf("error processing env named proxies: %w", err)
}
conf.Proxies[i] = item
extendKnownEnvVars(knownEnvVars, varInfo)
}
for i, item := range conf.Consumers {
varInfo, err = envconfig.Process("CENTRIFUGO_CONSUMERS_"+configtypes.NameForEnv(item.Name), &item)
if err != nil {
return Config{}, Meta{}, fmt.Errorf("error processing env consumers: %w", err)
}
conf.Consumers[i] = item
extendKnownEnvVars(knownEnvVars, varInfo)
}
meta.UnknownKeys = findUnknownKeys(v.AllSettings(), conf, "")
meta.UnknownEnvs = checkEnvironmentVars(knownEnvVars)
meta.KnownEnvVars = knownEnvVars
cfg := *conf
cfg, deprecationWarnings := applyConfigMigrations(cfg)
meta.DeprecationWarnings = deprecationWarnings
return cfg, meta, nil
}
func extendKnownEnvVars(knownEnvVars map[string]envconfig.VarInfo, varInfo []envconfig.VarInfo) {
for _, info := range varInfo {
knownEnvVars[info.Key] = info
}
}
// findValidKeys recursively finds valid keys in a struct, including embedded structs
func findValidKeys(typ reflect.Type, validKeys map[string]reflect.StructField) {
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
tag := field.Tag.Get("mapstructure")
if tag != "" && tag != ",squash" {
// Normal field, add it to validKeys.
validKeys[tag] = field
} else if field.Anonymous && strings.Contains(tag, "squash") {
// Handle embedded fields with "squash".
embeddedType := field.Type
if embeddedType.Kind() == reflect.Pointer {
embeddedType = embeddedType.Elem()
}
if embeddedType.Kind() == reflect.Struct {
// Recursively process the embedded struct
findValidKeys(embeddedType, validKeys)
}
}
}
}
func findUnknownKeys(data map[string]interface{}, configStruct interface{}, parentKey string) []string {
var unknownKeys []string
val := reflect.ValueOf(configStruct)
if val.Kind() == reflect.Pointer && val.IsNil() {
// Create an instance if the struct pointer is nil to avoid panic.
val = reflect.New(val.Type().Elem())
}
if val.Kind() == reflect.Pointer {
val = val.Elem()
}
typ := val.Type()
// Build a set of valid keys from the struct's mapstructure tags, including embedded structs.
validKeys := make(map[string]reflect.StructField)
findValidKeys(typ, validKeys)
// Check each key in the map to see if it's in the valid keys set
for key, value := range data {
if field, exists := validKeys[key]; exists {
fieldValue := val.FieldByName(field.Name)
if (fieldValue.Kind() == reflect.Struct || (fieldValue.Kind() == reflect.Pointer && fieldValue.Type().Elem().Kind() == reflect.Struct)) && !field.Anonymous {
if nestedMap, ok := value.(map[string]interface{}); ok {
// Handle pointers to structs specifically
if fieldValue.Kind() == reflect.Pointer && fieldValue.IsNil() {
fieldValue.Set(reflect.New(fieldValue.Type().Elem())) // Create new struct if nil
}
nestedStruct := fieldValue.Interface()
if fieldValue.Kind() == reflect.Pointer {
nestedStruct = fieldValue.Elem().Interface()
}
unknownKeys = append(unknownKeys, findUnknownKeys(nestedMap, nestedStruct, appendKeyPath(parentKey, key))...)
}
} else if fieldValue.Kind() == reflect.Slice {
// Handle each element in the slice if it is a map
if slice, ok := value.([]interface{}); ok {
for i, elem := range slice {
if elemMap, ok := elem.(map[string]interface{}); ok {
elementType := fieldValue.Type().Elem()
if elementType.Kind() == reflect.Pointer {
elementType = elementType.Elem()
}
if elementType.Kind() == reflect.Struct {
nestedStruct := reflect.New(elementType).Interface()
unknownKeys = append(unknownKeys, findUnknownKeys(elemMap, nestedStruct, appendKeyPath(appendKeyPath(parentKey, key), fmt.Sprintf("[%d]", i)))...)
}
}
}
}
}
} else {
unknownKeys = append(unknownKeys, appendKeyPath(parentKey, key))
}
}
return unknownKeys
}
func appendKeyPath(parent, key string) string {
if parent == "" {
return key
}
return parent + "." + key
}
func checkEnvironmentVars(knownEnvVars map[string]envconfig.VarInfo) []string {
var unknownEnvs []string
envPrefix := "CENTRIFUGO_"
envVars := os.Environ()
for _, envVar := range envVars {
kv, err := envparse.Parse(strings.NewReader(envVar))
if err != nil {
continue
}
for envKey := range kv {
if !strings.HasPrefix(envKey, envPrefix) {
continue
}
// Kubernetes automatically adds some variables which are not used by Centrifugo
// itself. We skip warnings about them.
if isKubernetesEnvVar(envKey) {
continue
}
// Centrifugo can extrapolate ${CENTRIFUGO_VAR_XXX} from env inside MapStringString type.
if isCustomEnvVar(envKey) {
continue
}
if _, ok := knownEnvVars[envKey]; !ok {
unknownEnvs = append(unknownEnvs, envKey)
}
}
}
return unknownEnvs
}
// k8sEnvRegex is used to filter out Kubernetes-injected environment variables.
// Centrifugo automatically scans environment variables starting with CENTRIFUGO_ and converts them to configuration options.
// However, Kubernetes automatically injects environment variables, so this regular expression filters out Kubernetes-injected environment variables.
// See isKubernetesEnvVar function for usage.
var k8sEnvRegex = regexp.MustCompile(`^CENTRIFUGO(?:_[A-Z0-9_]+)?_(PORT|SERVICE_)`)
func isKubernetesEnvVar(envKey string) bool {
return k8sEnvRegex.MatchString(envKey)
}
func isCustomEnvVar(envKey string) bool {
// Custom environment variables should start with "CENTRIFUGO_VAR_" prefix.
// This function checks if the environment variable is a custom one.
return strings.HasPrefix(envKey, "CENTRIFUGO_VAR_")
}
// DefaultConfig is a helper to be used in tests.
func DefaultConfig() Config {
conf, _, err := GetConfig(nil, "")
if err != nil {
panic("error during getting default config: " + err.Error())
}
return conf
}