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  }