// nolint:revive package util import ( "net/url" "os" "slices" "strconv" "strings" "sync" "time" "github.com/rs/zerolog/log" "golang.org/x/text/language" ) const ( mgmtSecretLen = 16 ) var ( mgmtSecret string mgmtSecretOnce sync.Once ) func GetEnv(key string, defaultVal string) string { if val, ok := os.LookupEnv(key); ok { return val } return defaultVal } func GetEnvEnum(key string, defaultVal string, allowedValues []string) string { if !slices.Contains(allowedValues, defaultVal) { log.Panic().Str("key", key).Str("value", defaultVal).Msg("Default value is not in the allowed values list.") } val, ok := os.LookupEnv(key) if !ok { return defaultVal } if !slices.Contains(allowedValues, val) { log.Error().Str("key", key).Str("value", val).Msg("Value is not allowed. Fallback to default value.") return defaultVal } return val } func GetEnvAsInt(key string, defaultVal int) int { strVal := GetEnv(key, "") if val, err := strconv.Atoi(strVal); err == nil { return val } return defaultVal } func GetEnvAsUint32(key string, defaultVal uint32) uint32 { strVal := GetEnv(key, "") if val, err := strconv.ParseUint(strVal, 10, 32); err == nil { return uint32(val) } return defaultVal } func GetEnvAsUint8(key string, defaultVal uint8) uint8 { strVal := GetEnv(key, "") if val, err := strconv.ParseUint(strVal, 10, 8); err == nil { return uint8(val) } return defaultVal } func GetEnvAsBool(key string, defaultVal bool) bool { strVal := GetEnv(key, "") if val, err := strconv.ParseBool(strVal); err == nil { return val } return defaultVal } // GetEnvAsStringArr reads ENV and returns the values split by separator. func GetEnvAsStringArr(key string, defaultVal []string, separator ...string) []string { strVal := GetEnv(key, "") if len(strVal) == 0 { return defaultVal } sep := "," if len(separator) >= 1 { sep = separator[0] } return strings.Split(strVal, sep) } // GetEnvAsStringArrTrimmed reads ENV and returns the whitespace trimmed values split by separator. func GetEnvAsStringArrTrimmed(key string, defaultVal []string, separator ...string) []string { slc := GetEnvAsStringArr(key, defaultVal, separator...) for i := range slc { slc[i] = strings.TrimSpace(slc[i]) } return slc } func GetEnvAsURL(key string, defaultVal string) *url.URL { strVal := GetEnv(key, "") if len(strVal) == 0 { u, err := url.Parse(defaultVal) if err != nil { log.Panic().Str("key", key).Str("defaultVal", defaultVal).Err(err).Msg("Failed to parse default value for env variable as URL") } return u } u, err := url.Parse(strVal) if err != nil { log.Panic().Str("key", key).Str("strVal", strVal).Err(err).Msg("Failed to parse env variable as URL") } return u } func GetEnvAsLanguageTag(key string, defaultVal language.Tag) language.Tag { strVal := GetEnv(key, "") if len(strVal) == 0 { return defaultVal } tag, err := language.Parse(strVal) if err != nil { log.Panic().Str("key", key).Str("strVal", strVal).Err(err).Msg("Failed to parse env variable as language.Tag") } return tag } // GetEnvAsLanguageTagArr reads ENV and returns the parsed values as []language.Tag split by separator. func GetEnvAsLanguageTagArr(key string, defaultVal []language.Tag, separator ...string) []language.Tag { strVal := GetEnv(key, "") if len(strVal) == 0 { return defaultVal } sep := "," if len(separator) >= 1 { sep = separator[0] } splitString := strings.Split(strVal, sep) res := []language.Tag{} for _, s := range splitString { tag, err := language.Parse(s) if err != nil { log.Panic().Str("key", key).Str("itemVal", s).Err(err).Msg("Failed to parse item value from env variable as language.Tag") } res = append(res, tag) } return res } // GetMgmtSecret returns the management secret for the app server, mainly used by health check and readiness endpoints. // It first attempts to retrieve a value from the given environment variable and generates a cryptographically secure random string // should no env var have been set. // Failure to generate a random string will cause a panic as secret security cannot be guaranteed otherwise. // Subsequent calls to GetMgmtSecret during the server's runtime will always return the same randomly generated secret for consistency. func GetMgmtSecret(envKey string) string { val := GetEnv(envKey, "") if len(val) > 0 { return val } mgmtSecretOnce.Do(func() { var err error mgmtSecret, err = GenerateRandomHexString(mgmtSecretLen) if err != nil { log.Panic().Err(err).Msg("Failed to generate random management secret") } log.Warn().Str("envKey", envKey).Str("mgmtSecret", mgmtSecret).Msg("Could not retrieve management secret from env key, using randomly generated one") }) return mgmtSecret } func GetEnvAsLocation(key string, defaultVal string) *time.Location { strVal := GetEnv(key, "") if len(strVal) == 0 { l, err := time.LoadLocation(defaultVal) if err != nil { log.Panic().Str("key", key).Str("defaultVal", defaultVal).Err(err).Msg("Failed to parse default value for env variable as location") } return l } l, err := time.LoadLocation(strVal) if err != nil { log.Panic().Str("key", key).Str("strVal", strVal).Err(err).Msg("Failed to parse env variable as location") } return l }