github.com/weaviate/weaviate@v1.24.6/usecases/config/environment.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package config 13 14 import ( 15 "fmt" 16 "math" 17 "os" 18 "strconv" 19 "strings" 20 "time" 21 22 "github.com/weaviate/weaviate/usecases/configbase" 23 24 "github.com/weaviate/weaviate/entities/schema" 25 "github.com/weaviate/weaviate/usecases/cluster" 26 ) 27 28 // FromEnv takes a *Config as it will respect initial config that has been 29 // provided by other means (e.g. a config file) and will only extend those that 30 // are set 31 func FromEnv(config *Config) error { 32 if configbase.Enabled(os.Getenv("PROMETHEUS_MONITORING_ENABLED")) { 33 config.Monitoring.Enabled = true 34 config.Monitoring.Tool = "prometheus" 35 config.Monitoring.Port = 2112 36 37 if configbase.Enabled(os.Getenv("PROMETHEUS_MONITORING_GROUP_CLASSES")) || 38 configbase.Enabled(os.Getenv("PROMETHEUS_MONITORING_GROUP")) { 39 // The variable was renamed with v1.20. Prior to v1.20 the recommended 40 // way to do MT was using classes. This lead to a lot of metrics which 41 // could be grouped with this variable. With v1.20 we introduced native 42 // multi-tenancy. Now all you need is a single class, but you would 43 // still get one set of metrics per shard. To prevent this, you still 44 // want to group. The new name reflects that it's just about grouping, 45 // not about classes or shards. 46 config.Monitoring.Group = true 47 } 48 } 49 50 if configbase.Enabled(os.Getenv("TRACK_VECTOR_DIMENSIONS")) { 51 config.TrackVectorDimensions = true 52 } 53 54 if configbase.Enabled(os.Getenv("REINDEX_VECTOR_DIMENSIONS_AT_STARTUP")) { 55 if config.TrackVectorDimensions { 56 config.ReindexVectorDimensionsAtStartup = true 57 } 58 } 59 60 if configbase.Enabled(os.Getenv("DISABLE_LAZY_LOAD_SHARDS")) { 61 config.DisableLazyLoadShards = true 62 } 63 64 // Recount all property lengths at startup to support accurate BM25 scoring 65 if configbase.Enabled(os.Getenv("RECOUNT_PROPERTIES_AT_STARTUP")) { 66 config.RecountPropertiesAtStartup = true 67 } 68 69 if configbase.Enabled(os.Getenv("REINDEX_SET_TO_ROARINGSET_AT_STARTUP")) { 70 config.ReindexSetToRoaringsetAtStartup = true 71 } 72 73 if configbase.Enabled(os.Getenv("INDEX_MISSING_TEXT_FILTERABLE_AT_STARTUP")) { 74 config.IndexMissingTextFilterableAtStartup = true 75 } 76 77 if v := os.Getenv("PROMETHEUS_MONITORING_PORT"); v != "" { 78 asInt, err := strconv.Atoi(v) 79 if err != nil { 80 return fmt.Errorf("parse PROMETHEUS_MONITORING_PORT as int: %w", err) 81 } 82 83 config.Monitoring.Port = asInt 84 } 85 86 if configbase.Enabled(os.Getenv("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED")) { 87 config.Authentication.AnonymousAccess.Enabled = true 88 } 89 90 if configbase.Enabled(os.Getenv("AUTHENTICATION_OIDC_ENABLED")) { 91 config.Authentication.OIDC.Enabled = true 92 93 if configbase.Enabled(os.Getenv("AUTHENTICATION_OIDC_SKIP_CLIENT_ID_CHECK")) { 94 config.Authentication.OIDC.SkipClientIDCheck = true 95 } 96 97 if v := os.Getenv("AUTHENTICATION_OIDC_ISSUER"); v != "" { 98 config.Authentication.OIDC.Issuer = v 99 } 100 101 if v := os.Getenv("AUTHENTICATION_OIDC_CLIENT_ID"); v != "" { 102 config.Authentication.OIDC.ClientID = v 103 } 104 105 if v := os.Getenv("AUTHENTICATION_OIDC_SCOPES"); v != "" { 106 config.Authentication.OIDC.Scopes = strings.Split(v, ",") 107 } 108 109 if v := os.Getenv("AUTHENTICATION_OIDC_USERNAME_CLAIM"); v != "" { 110 config.Authentication.OIDC.UsernameClaim = v 111 } 112 113 if v := os.Getenv("AUTHENTICATION_OIDC_GROUPS_CLAIM"); v != "" { 114 config.Authentication.OIDC.GroupsClaim = v 115 } 116 } 117 118 if configbase.Enabled(os.Getenv("AUTHENTICATION_APIKEY_ENABLED")) { 119 config.Authentication.APIKey.Enabled = true 120 121 if keysString, ok := os.LookupEnv("AUTHENTICATION_APIKEY_ALLOWED_KEYS"); ok { 122 keys := strings.Split(keysString, ",") 123 config.Authentication.APIKey.AllowedKeys = keys 124 } 125 126 if keysString, ok := os.LookupEnv("AUTHENTICATION_APIKEY_USERS"); ok { 127 keys := strings.Split(keysString, ",") 128 config.Authentication.APIKey.Users = keys 129 } 130 } 131 132 if configbase.Enabled(os.Getenv("AUTHORIZATION_ADMINLIST_ENABLED")) { 133 config.Authorization.AdminList.Enabled = true 134 135 usersString, ok := os.LookupEnv("AUTHORIZATION_ADMINLIST_USERS") 136 if ok { 137 config.Authorization.AdminList.Users = strings.Split(usersString, ",") 138 } 139 140 roUsersString, ok := os.LookupEnv("AUTHORIZATION_ADMINLIST_READONLY_USERS") 141 if ok { 142 config.Authorization.AdminList.ReadOnlyUsers = strings.Split(roUsersString, ",") 143 } 144 145 groupsString, ok := os.LookupEnv("AUTHORIZATION_ADMINLIST_GROUPS") 146 if ok { 147 config.Authorization.AdminList.Groups = strings.Split(groupsString, ",") 148 } 149 150 roGroupsString, ok := os.LookupEnv("AUTHORIZATION_ADMINLIST_READONLY_GROUPS") 151 if ok { 152 config.Authorization.AdminList.ReadOnlyGroups = strings.Split(roGroupsString, ",") 153 } 154 } 155 156 if !config.Authentication.AnyAuthMethodSelected() { 157 config.Authentication = DefaultAuthentication 158 } 159 160 if os.Getenv("PERSISTENCE_LSM_ACCESS_STRATEGY") == "pread" { 161 config.AvoidMmap = true 162 } 163 164 clusterCfg, err := parseClusterConfig() 165 if err != nil { 166 return err 167 } 168 config.Cluster = clusterCfg 169 170 if v := os.Getenv("PERSISTENCE_DATA_PATH"); v != "" { 171 config.Persistence.DataPath = v 172 } else { 173 if config.Persistence.DataPath == "" { 174 config.Persistence.DataPath = DefaultPersistenceDataPath 175 } 176 } 177 178 if err := config.parseMemtableConfig(); err != nil { 179 return err 180 } 181 182 if err := config.parseCORSConfig(); err != nil { 183 return err 184 } 185 186 if v := os.Getenv("ORIGIN"); v != "" { 187 config.Origin = v 188 } 189 190 if v := os.Getenv("CONTEXTIONARY_URL"); v != "" { 191 config.Contextionary.URL = v 192 } 193 194 if v := os.Getenv("QUERY_DEFAULTS_LIMIT"); v != "" { 195 asInt, err := strconv.Atoi(v) 196 if err != nil { 197 return fmt.Errorf("parse QUERY_DEFAULTS_LIMIT as int: %w", err) 198 } 199 200 config.QueryDefaults.Limit = int64(asInt) 201 } else { 202 if config.QueryDefaults.Limit == 0 { 203 config.QueryDefaults.Limit = DefaultQueryDefaultsLimit 204 } 205 } 206 207 if v := os.Getenv("QUERY_MAXIMUM_RESULTS"); v != "" { 208 asInt, err := strconv.Atoi(v) 209 if err != nil { 210 return fmt.Errorf("parse QUERY_MAXIMUM_RESULTS as int: %w", err) 211 } 212 213 config.QueryMaximumResults = int64(asInt) 214 } else { 215 config.QueryMaximumResults = DefaultQueryMaximumResults 216 } 217 218 if v := os.Getenv("QUERY_NESTED_CROSS_REFERENCE_LIMIT"); v != "" { 219 limit, err := strconv.ParseInt(v, 10, 64) 220 if err != nil { 221 return fmt.Errorf("parse QUERY_NESTED_CROSS_REFERENCE_LIMIT as int: %w", err) 222 } else if limit <= 0 { 223 limit = math.MaxInt 224 } 225 config.QueryNestedCrossReferenceLimit = limit 226 } else { 227 config.QueryNestedCrossReferenceLimit = DefaultQueryNestedCrossReferenceLimit 228 } 229 230 if v := os.Getenv("MAX_IMPORT_GOROUTINES_FACTOR"); v != "" { 231 asFloat, err := strconv.ParseFloat(v, 64) 232 if err != nil { 233 return fmt.Errorf("parse MAX_IMPORT_GOROUTINES_FACTOR as float: %w", err) 234 } else if asFloat <= 0 { 235 return fmt.Errorf("negative MAX_IMPORT_GOROUTINES_FACTOR factor") 236 } 237 238 config.MaxImportGoroutinesFactor = asFloat 239 } else { 240 config.MaxImportGoroutinesFactor = DefaultMaxImportGoroutinesFactor 241 } 242 243 if v := os.Getenv("DEFAULT_VECTORIZER_MODULE"); v != "" { 244 config.DefaultVectorizerModule = v 245 } else { 246 // env not set, this could either mean, we already have a value from a file 247 // or we explicitly want to set the value to "none" 248 if config.DefaultVectorizerModule == "" { 249 config.DefaultVectorizerModule = VectorizerModuleNone 250 } 251 } 252 253 if v := os.Getenv("MODULES_CLIENT_TIMEOUT"); v != "" { 254 timeout, err := time.ParseDuration(v) 255 if err != nil { 256 return fmt.Errorf("parse MODULES_CLIENT_TIMEOUT as time.Duration: %w", err) 257 } 258 config.ModuleHttpClientTimeout = timeout 259 } else { 260 config.ModuleHttpClientTimeout = 50 * time.Second 261 } 262 263 if v := os.Getenv("DEFAULT_VECTOR_DISTANCE_METRIC"); v != "" { 264 config.DefaultVectorDistanceMetric = v 265 } 266 267 if v := os.Getenv("ENABLE_MODULES"); v != "" { 268 config.EnableModules = v 269 } 270 271 config.AutoSchema.Enabled = true 272 if v := os.Getenv("AUTOSCHEMA_ENABLED"); v != "" { 273 config.AutoSchema.Enabled = !(strings.ToLower(v) == "false") 274 } 275 config.AutoSchema.DefaultString = schema.DataTypeText.String() 276 if v := os.Getenv("AUTOSCHEMA_DEFAULT_STRING"); v != "" { 277 config.AutoSchema.DefaultString = v 278 } 279 config.AutoSchema.DefaultNumber = "number" 280 if v := os.Getenv("AUTOSCHEMA_DEFAULT_NUMBER"); v != "" { 281 config.AutoSchema.DefaultNumber = v 282 } 283 config.AutoSchema.DefaultDate = "date" 284 if v := os.Getenv("AUTOSCHEMA_DEFAULT_DATE"); v != "" { 285 config.AutoSchema.DefaultDate = v 286 } 287 288 ru, err := parseResourceUsageEnvVars() 289 if err != nil { 290 return err 291 } 292 config.ResourceUsage = ru 293 294 if v := os.Getenv("GO_BLOCK_PROFILE_RATE"); v != "" { 295 asInt, err := strconv.Atoi(v) 296 if err != nil { 297 return fmt.Errorf("parse GO_BLOCK_PROFILE_RATE as int: %w", err) 298 } 299 300 config.Profiling.BlockProfileRate = asInt 301 } 302 303 if v := os.Getenv("GO_MUTEX_PROFILE_FRACTION"); v != "" { 304 asInt, err := strconv.Atoi(v) 305 if err != nil { 306 return fmt.Errorf("parse GO_MUTEX_PROFILE_FRACTION as int: %w", err) 307 } 308 309 config.Profiling.MutexProfileFraction = asInt 310 } 311 312 if v := os.Getenv("MAXIMUM_CONCURRENT_GET_REQUESTS"); v != "" { 313 asInt, err := strconv.ParseInt(v, 10, 64) 314 if err != nil { 315 return fmt.Errorf("parse MAXIMUM_CONCURRENT_GET_REQUESTS as int: %w", err) 316 } 317 config.MaximumConcurrentGetRequests = int(asInt) 318 } else { 319 config.MaximumConcurrentGetRequests = DefaultMaxConcurrentGetRequests 320 } 321 322 if err := parsePositiveInt( 323 "GRPC_PORT", 324 func(val int) { config.GRPC.Port = val }, 325 DefaultGRPCPort, 326 ); err != nil { 327 return err 328 } 329 config.GRPC.CertFile = "" 330 if v := os.Getenv("GRPC_CERT_FILE"); v != "" { 331 config.GRPC.CertFile = v 332 } 333 config.GRPC.KeyFile = "" 334 if v := os.Getenv("GRPC_KEY_FILE"); v != "" { 335 config.GRPC.KeyFile = v 336 } 337 338 config.DisableGraphQL = configbase.Enabled(os.Getenv("DISABLE_GRAPHQL")) 339 340 if err := parsePositiveInt( 341 "REPLICATION_MINIMUM_FACTOR", 342 func(val int) { config.Replication.MinimumFactor = val }, 343 DefaultMinimumReplicationFactor, 344 ); err != nil { 345 return err 346 } 347 348 config.DisableTelemetry = false 349 if configbase.Enabled(os.Getenv("DISABLE_TELEMETRY")) { 350 config.DisableTelemetry = true 351 } 352 353 return nil 354 } 355 356 func (c *Config) parseCORSConfig() error { 357 if v := os.Getenv("CORS_ALLOW_ORIGIN"); v != "" { 358 c.CORS.AllowOrigin = v 359 } else { 360 c.CORS.AllowOrigin = DefaultCORSAllowOrigin 361 } 362 363 if v := os.Getenv("CORS_ALLOW_METHODS"); v != "" { 364 c.CORS.AllowMethods = v 365 } else { 366 c.CORS.AllowMethods = DefaultCORSAllowMethods 367 } 368 369 if v := os.Getenv("CORS_ALLOW_HEADERS"); v != "" { 370 c.CORS.AllowHeaders = v 371 } else { 372 c.CORS.AllowHeaders = DefaultCORSAllowHeaders 373 } 374 375 return nil 376 } 377 378 func (c *Config) parseMemtableConfig() error { 379 // first parse old idle name for flush value 380 if err := parsePositiveInt( 381 "PERSISTENCE_FLUSH_IDLE_MEMTABLES_AFTER", 382 func(val int) { c.Persistence.MemtablesFlushDirtyAfter = val }, 383 DefaultPersistenceMemtablesFlushDirtyAfter, 384 ); err != nil { 385 return err 386 } 387 // then parse with new idle name and use previous value in case it's not set 388 if err := parsePositiveInt( 389 "PERSISTENCE_MEMTABLES_FLUSH_IDLE_AFTER_SECONDS", 390 func(val int) { c.Persistence.MemtablesFlushDirtyAfter = val }, 391 c.Persistence.MemtablesFlushDirtyAfter, 392 ); err != nil { 393 return err 394 } 395 // then parse with dirty name and use idle value as fallback 396 if err := parsePositiveInt( 397 "PERSISTENCE_MEMTABLES_FLUSH_DIRTY_AFTER_SECONDS", 398 func(val int) { c.Persistence.MemtablesFlushDirtyAfter = val }, 399 c.Persistence.MemtablesFlushDirtyAfter, 400 ); err != nil { 401 return err 402 } 403 404 if err := parsePositiveInt( 405 "PERSISTENCE_MEMTABLES_MAX_SIZE_MB", 406 func(val int) { c.Persistence.MemtablesMaxSizeMB = val }, 407 DefaultPersistenceMemtablesMaxSize, 408 ); err != nil { 409 return err 410 } 411 412 if err := parsePositiveInt( 413 "PERSISTENCE_MEMTABLES_MIN_ACTIVE_DURATION_SECONDS", 414 func(val int) { c.Persistence.MemtablesMinActiveDurationSeconds = val }, 415 DefaultPersistenceMemtablesMinDuration, 416 ); err != nil { 417 return err 418 } 419 420 if err := parsePositiveInt( 421 "PERSISTENCE_MEMTABLES_MAX_ACTIVE_DURATION_SECONDS", 422 func(val int) { c.Persistence.MemtablesMaxActiveDurationSeconds = val }, 423 DefaultPersistenceMemtablesMaxDuration, 424 ); err != nil { 425 return err 426 } 427 428 return nil 429 } 430 431 func parsePositiveInt(varName string, cb func(val int), defaultValue int) error { 432 if v := os.Getenv(varName); v != "" { 433 asInt, err := strconv.Atoi(v) 434 if err != nil { 435 return fmt.Errorf("parse %s as int: %w", varName, err) 436 } else if asInt <= 0 { 437 return fmt.Errorf("%s must be a positive value larger 0", varName) 438 } 439 440 cb(asInt) 441 } else { 442 cb(defaultValue) 443 } 444 445 return nil 446 } 447 448 const ( 449 DefaultQueryMaximumResults = int64(10000) 450 DefaultQueryNestedCrossReferenceLimit = int64(100000) 451 ) 452 453 const ( 454 DefaultPersistenceMemtablesFlushDirtyAfter = 60 455 DefaultPersistenceMemtablesMaxSize = 200 456 DefaultPersistenceMemtablesMinDuration = 15 457 DefaultPersistenceMemtablesMaxDuration = 45 458 DefaultMaxConcurrentGetRequests = 0 459 DefaultGRPCPort = 50051 460 DefaultMinimumReplicationFactor = 1 461 ) 462 463 const VectorizerModuleNone = "none" 464 465 // DefaultGossipBindPort uses the hashicorp/memberlist default 466 // port value assigned with the use of DefaultLocalConfig 467 const DefaultGossipBindPort = 7946 468 469 // TODO: This should be retrieved dynamically from all installed modules 470 const VectorizerModuleText2VecContextionary = "text2vec-contextionary" 471 472 func parseResourceUsageEnvVars() (ResourceUsage, error) { 473 ru := ResourceUsage{} 474 475 if v := os.Getenv("DISK_USE_WARNING_PERCENTAGE"); v != "" { 476 asUint, err := strconv.ParseUint(v, 10, 64) 477 if err != nil { 478 return ru, fmt.Errorf("parse DISK_USE_WARNING_PERCENTAGE as uint: %w", err) 479 } 480 ru.DiskUse.WarningPercentage = asUint 481 } else { 482 ru.DiskUse.WarningPercentage = DefaultDiskUseWarningPercentage 483 } 484 485 if v := os.Getenv("DISK_USE_READONLY_PERCENTAGE"); v != "" { 486 asUint, err := strconv.ParseUint(v, 10, 64) 487 if err != nil { 488 return ru, fmt.Errorf("parse DISK_USE_READONLY_PERCENTAGE as uint: %w", err) 489 } 490 ru.DiskUse.ReadOnlyPercentage = asUint 491 } else { 492 ru.DiskUse.ReadOnlyPercentage = DefaultDiskUseReadonlyPercentage 493 } 494 495 if v := os.Getenv("MEMORY_WARNING_PERCENTAGE"); v != "" { 496 asUint, err := strconv.ParseUint(v, 10, 64) 497 if err != nil { 498 return ru, fmt.Errorf("parse MEMORY_WARNING_PERCENTAGE as uint: %w", err) 499 } 500 ru.MemUse.WarningPercentage = asUint 501 } else { 502 ru.MemUse.WarningPercentage = DefaultMemUseWarningPercentage 503 } 504 505 if v := os.Getenv("MEMORY_READONLY_PERCENTAGE"); v != "" { 506 asUint, err := strconv.ParseUint(v, 10, 64) 507 if err != nil { 508 return ru, fmt.Errorf("parse MEMORY_READONLY_PERCENTAGE as uint: %w", err) 509 } 510 ru.MemUse.ReadOnlyPercentage = asUint 511 } else { 512 ru.MemUse.ReadOnlyPercentage = DefaultMemUseReadonlyPercentage 513 } 514 515 return ru, nil 516 } 517 518 func parseClusterConfig() (cluster.Config, error) { 519 cfg := cluster.Config{} 520 521 cfg.Hostname = os.Getenv("CLUSTER_HOSTNAME") 522 cfg.Join = os.Getenv("CLUSTER_JOIN") 523 524 advertiseAddr, advertiseAddrSet := os.LookupEnv("CLUSTER_ADVERTISE_ADDR") 525 advertisePort, advertisePortSet := os.LookupEnv("CLUSTER_ADVERTISE_PORT") 526 527 gossipBind, gossipBindSet := os.LookupEnv("CLUSTER_GOSSIP_BIND_PORT") 528 dataBind, dataBindSet := os.LookupEnv("CLUSTER_DATA_BIND_PORT") 529 530 if advertiseAddrSet { 531 cfg.AdvertiseAddr = advertiseAddr 532 } 533 534 if advertisePortSet { 535 asInt, err := strconv.Atoi(advertisePort) 536 if err != nil { 537 return cfg, fmt.Errorf("parse CLUSTER_ADVERTISE_PORT as int: %w", err) 538 } 539 cfg.AdvertisePort = asInt 540 } 541 542 if gossipBindSet { 543 asInt, err := strconv.Atoi(gossipBind) 544 if err != nil { 545 return cfg, fmt.Errorf("parse CLUSTER_GOSSIP_BIND_PORT as int: %w", err) 546 } 547 cfg.GossipBindPort = asInt 548 } else { 549 cfg.GossipBindPort = DefaultGossipBindPort 550 } 551 552 if dataBindSet { 553 asInt, err := strconv.Atoi(dataBind) 554 if err != nil { 555 return cfg, fmt.Errorf("parse CLUSTER_DATA_BIND_PORT as int: %w", err) 556 } 557 cfg.DataBindPort = asInt 558 } else { 559 // it is convention in this server that the data bind point is 560 // equal to the data bind port + 1 561 cfg.DataBindPort = cfg.GossipBindPort + 1 562 } 563 564 if cfg.DataBindPort != cfg.GossipBindPort+1 { 565 return cfg, fmt.Errorf("CLUSTER_DATA_BIND_PORT must be one port " + 566 "number greater than CLUSTER_GOSSIP_BIND_PORT") 567 } 568 569 cfg.IgnoreStartupSchemaSync = configbase.Enabled( 570 os.Getenv("CLUSTER_IGNORE_SCHEMA_SYNC")) 571 cfg.SkipSchemaSyncRepair = configbase.Enabled( 572 os.Getenv("CLUSTER_SKIP_SCHEMA_REPAIR")) 573 574 basicAuthUsername := os.Getenv("CLUSTER_BASIC_AUTH_USERNAME") 575 basicAuthPassword := os.Getenv("CLUSTER_BASIC_AUTH_PASSWORD") 576 577 cfg.AuthConfig = cluster.AuthConfig{ 578 BasicAuth: cluster.BasicAuth{ 579 Username: basicAuthUsername, 580 Password: basicAuthPassword, 581 }, 582 } 583 584 return cfg, nil 585 }