storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/config/config.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2019 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17  
    18  package config
    19  
    20  import (
    21  	"bufio"
    22  	"fmt"
    23  	"io"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"github.com/minio/minio-go/v7/pkg/set"
    28  
    29  	"storj.io/minio/pkg/auth"
    30  	"storj.io/minio/pkg/env"
    31  	"storj.io/minio/pkg/madmin"
    32  )
    33  
    34  // Error config error type
    35  type Error struct {
    36  	Err string
    37  }
    38  
    39  // Errorf - formats according to a format specifier and returns
    40  // the string as a value that satisfies error of type config.Error
    41  func Errorf(format string, a ...interface{}) error {
    42  	return Error{Err: fmt.Sprintf(format, a...)}
    43  }
    44  
    45  func (e Error) Error() string {
    46  	return e.Err
    47  }
    48  
    49  // Default keys
    50  const (
    51  	Default = madmin.Default
    52  	Enable  = madmin.EnableKey
    53  	Comment = madmin.CommentKey
    54  
    55  	// Enable values
    56  	EnableOn  = madmin.EnableOn
    57  	EnableOff = madmin.EnableOff
    58  
    59  	RegionName = "name"
    60  	AccessKey  = "access_key"
    61  	SecretKey  = "secret_key"
    62  )
    63  
    64  // Top level config constants.
    65  const (
    66  	CredentialsSubSys    = "credentials"
    67  	PolicyOPASubSys      = "policy_opa"
    68  	IdentityOpenIDSubSys = "identity_openid"
    69  	IdentityLDAPSubSys   = "identity_ldap"
    70  	CacheSubSys          = "cache"
    71  	RegionSubSys         = "region"
    72  	EtcdSubSys           = "etcd"
    73  	StorageClassSubSys   = "storage_class"
    74  	APISubSys            = "api"
    75  	CompressionSubSys    = "compression"
    76  	KmsVaultSubSys       = "kms_vault"
    77  	KmsKesSubSys         = "kms_kes"
    78  	LoggerWebhookSubSys  = "logger_webhook"
    79  	AuditWebhookSubSys   = "audit_webhook"
    80  	HealSubSys           = "heal"
    81  	ScannerSubSys        = "scanner"
    82  	CrawlerSubSys        = "crawler"
    83  
    84  	// Add new constants here if you add new fields to config.
    85  )
    86  
    87  // Notification config constants.
    88  const (
    89  	NotifyKafkaSubSys    = "notify_kafka"
    90  	NotifyMQTTSubSys     = "notify_mqtt"
    91  	NotifyMySQLSubSys    = "notify_mysql"
    92  	NotifyNATSSubSys     = "notify_nats"
    93  	NotifyNSQSubSys      = "notify_nsq"
    94  	NotifyESSubSys       = "notify_elasticsearch"
    95  	NotifyAMQPSubSys     = "notify_amqp"
    96  	NotifyPostgresSubSys = "notify_postgres"
    97  	NotifyRedisSubSys    = "notify_redis"
    98  	NotifyWebhookSubSys  = "notify_webhook"
    99  
   100  	// Add new constants here if you add new fields to config.
   101  )
   102  
   103  // SubSystems - all supported sub-systems
   104  var SubSystems = set.CreateStringSet(
   105  	CredentialsSubSys,
   106  	RegionSubSys,
   107  	EtcdSubSys,
   108  	CacheSubSys,
   109  	APISubSys,
   110  	StorageClassSubSys,
   111  	CompressionSubSys,
   112  	KmsVaultSubSys,
   113  	KmsKesSubSys,
   114  	LoggerWebhookSubSys,
   115  	AuditWebhookSubSys,
   116  	PolicyOPASubSys,
   117  	IdentityLDAPSubSys,
   118  	IdentityOpenIDSubSys,
   119  	ScannerSubSys,
   120  	HealSubSys,
   121  	NotifyAMQPSubSys,
   122  	NotifyESSubSys,
   123  	NotifyKafkaSubSys,
   124  	NotifyMQTTSubSys,
   125  	NotifyMySQLSubSys,
   126  	NotifyNATSSubSys,
   127  	NotifyNSQSubSys,
   128  	NotifyPostgresSubSys,
   129  	NotifyRedisSubSys,
   130  	NotifyWebhookSubSys,
   131  )
   132  
   133  // SubSystemsDynamic - all sub-systems that have dynamic config.
   134  var SubSystemsDynamic = set.CreateStringSet(
   135  	APISubSys,
   136  	CompressionSubSys,
   137  	ScannerSubSys,
   138  	HealSubSys,
   139  )
   140  
   141  // SubSystemsSingleTargets - subsystems which only support single target.
   142  var SubSystemsSingleTargets = set.CreateStringSet([]string{
   143  	CredentialsSubSys,
   144  	RegionSubSys,
   145  	EtcdSubSys,
   146  	CacheSubSys,
   147  	APISubSys,
   148  	StorageClassSubSys,
   149  	CompressionSubSys,
   150  	KmsVaultSubSys,
   151  	KmsKesSubSys,
   152  	PolicyOPASubSys,
   153  	IdentityLDAPSubSys,
   154  	IdentityOpenIDSubSys,
   155  	HealSubSys,
   156  	ScannerSubSys,
   157  }...)
   158  
   159  // Constant separators
   160  const (
   161  	SubSystemSeparator = madmin.SubSystemSeparator
   162  	KvSeparator        = madmin.KvSeparator
   163  	KvSpaceSeparator   = madmin.KvSpaceSeparator
   164  	KvComment          = madmin.KvComment
   165  	KvNewline          = madmin.KvNewline
   166  	KvDoubleQuote      = madmin.KvDoubleQuote
   167  	KvSingleQuote      = madmin.KvSingleQuote
   168  
   169  	// Env prefix used for all envs in MinIO
   170  	EnvPrefix        = "MINIO_"
   171  	EnvWordDelimiter = `_`
   172  )
   173  
   174  // DefaultKVS - default kvs for all sub-systems
   175  var DefaultKVS map[string]KVS
   176  
   177  // RegisterDefaultKVS - this function saves input kvsMap
   178  // globally, this should be called only once preferably
   179  // during `init()`.
   180  func RegisterDefaultKVS(kvsMap map[string]KVS) {
   181  	DefaultKVS = map[string]KVS{}
   182  	for subSys, kvs := range kvsMap {
   183  		DefaultKVS[subSys] = kvs
   184  	}
   185  }
   186  
   187  // HelpSubSysMap - help for all individual KVS for each sub-systems
   188  // also carries a special empty sub-system which dumps
   189  // help for each sub-system key.
   190  var HelpSubSysMap map[string]HelpKVS
   191  
   192  // RegisterHelpSubSys - this function saves
   193  // input help KVS for each sub-system globally,
   194  // this function should be called only once
   195  // preferably in during `init()`.
   196  func RegisterHelpSubSys(helpKVSMap map[string]HelpKVS) {
   197  	HelpSubSysMap = map[string]HelpKVS{}
   198  	for subSys, hkvs := range helpKVSMap {
   199  		HelpSubSysMap[subSys] = hkvs
   200  	}
   201  }
   202  
   203  // KV - is a shorthand of each key value.
   204  type KV struct {
   205  	Key   string `json:"key"`
   206  	Value string `json:"value"`
   207  }
   208  
   209  // KVS - is a shorthand for some wrapper functions
   210  // to operate on list of key values.
   211  type KVS []KV
   212  
   213  // Empty - return if kv is empty
   214  func (kvs KVS) Empty() bool {
   215  	return len(kvs) == 0
   216  }
   217  
   218  // Keys returns the list of keys for the current KVS
   219  func (kvs KVS) Keys() []string {
   220  	var keys = make([]string, len(kvs))
   221  	var foundComment bool
   222  	for i := range kvs {
   223  		if kvs[i].Key == madmin.CommentKey {
   224  			foundComment = true
   225  		}
   226  		keys[i] = kvs[i].Key
   227  	}
   228  	// Comment KV not found, add it explicitly.
   229  	if !foundComment {
   230  		keys = append(keys, madmin.CommentKey)
   231  	}
   232  	return keys
   233  }
   234  
   235  func (kvs KVS) String() string {
   236  	var s strings.Builder
   237  	for _, kv := range kvs {
   238  		// Do not need to print if state is on
   239  		if kv.Key == Enable && kv.Value == EnableOn {
   240  			continue
   241  		}
   242  		s.WriteString(kv.Key)
   243  		s.WriteString(KvSeparator)
   244  		spc := madmin.HasSpace(kv.Value)
   245  		if spc {
   246  			s.WriteString(KvDoubleQuote)
   247  		}
   248  		s.WriteString(kv.Value)
   249  		if spc {
   250  			s.WriteString(KvDoubleQuote)
   251  		}
   252  		s.WriteString(KvSpaceSeparator)
   253  	}
   254  	return s.String()
   255  }
   256  
   257  // Set sets a value, if not sets a default value.
   258  func (kvs *KVS) Set(key, value string) {
   259  	for i, kv := range *kvs {
   260  		if kv.Key == key {
   261  			(*kvs)[i] = KV{
   262  				Key:   key,
   263  				Value: value,
   264  			}
   265  			return
   266  		}
   267  	}
   268  	*kvs = append(*kvs, KV{
   269  		Key:   key,
   270  		Value: value,
   271  	})
   272  }
   273  
   274  // Get - returns the value of a key, if not found returns empty.
   275  func (kvs KVS) Get(key string) string {
   276  	v, ok := kvs.Lookup(key)
   277  	if ok {
   278  		return v
   279  	}
   280  	return ""
   281  }
   282  
   283  // Delete - deletes the key if present from the KV list.
   284  func (kvs *KVS) Delete(key string) {
   285  	for i, kv := range *kvs {
   286  		if kv.Key == key {
   287  			*kvs = append((*kvs)[:i], (*kvs)[i+1:]...)
   288  			return
   289  		}
   290  	}
   291  }
   292  
   293  // Lookup - lookup a key in a list of KVS
   294  func (kvs KVS) Lookup(key string) (string, bool) {
   295  	for _, kv := range kvs {
   296  		if kv.Key == key {
   297  			return kv.Value, true
   298  		}
   299  	}
   300  	return "", false
   301  }
   302  
   303  // Config - MinIO server config structure.
   304  type Config map[string]map[string]KVS
   305  
   306  // DelFrom - deletes all keys in the input reader.
   307  func (c Config) DelFrom(r io.Reader) error {
   308  	scanner := bufio.NewScanner(r)
   309  	for scanner.Scan() {
   310  		// Skip any empty lines, or comment like characters
   311  		text := scanner.Text()
   312  		if text == "" || strings.HasPrefix(text, KvComment) {
   313  			continue
   314  		}
   315  		if err := c.DelKVS(text); err != nil {
   316  			return err
   317  		}
   318  	}
   319  	if err := scanner.Err(); err != nil {
   320  		return err
   321  	}
   322  	return nil
   323  }
   324  
   325  // ReadConfig - read content from input and write into c.
   326  // Returns whether all parameters were dynamic.
   327  func (c Config) ReadConfig(r io.Reader) (dynOnly bool, err error) {
   328  	var n int
   329  	scanner := bufio.NewScanner(r)
   330  	dynOnly = true
   331  	for scanner.Scan() {
   332  		// Skip any empty lines, or comment like characters
   333  		text := scanner.Text()
   334  		if text == "" || strings.HasPrefix(text, KvComment) {
   335  			continue
   336  		}
   337  		dynamic, err := c.SetKVS(text, DefaultKVS)
   338  		if err != nil {
   339  			return false, err
   340  		}
   341  		dynOnly = dynOnly && dynamic
   342  		n += len(text)
   343  	}
   344  	if err := scanner.Err(); err != nil {
   345  		return false, err
   346  	}
   347  	return dynOnly, nil
   348  }
   349  
   350  type configWriteTo struct {
   351  	Config
   352  	filterByKey string
   353  }
   354  
   355  // NewConfigWriteTo - returns a struct which
   356  // allows for serializing the config/kv struct
   357  // to a io.WriterTo
   358  func NewConfigWriteTo(cfg Config, key string) io.WriterTo {
   359  	return &configWriteTo{Config: cfg, filterByKey: key}
   360  }
   361  
   362  // WriteTo - implements io.WriterTo interface implementation for config.
   363  func (c *configWriteTo) WriteTo(w io.Writer) (int64, error) {
   364  	kvsTargets, err := c.GetKVS(c.filterByKey, DefaultKVS)
   365  	if err != nil {
   366  		return 0, err
   367  	}
   368  	var n int
   369  	for _, target := range kvsTargets {
   370  		m1, _ := w.Write([]byte(target.SubSystem))
   371  		m2, _ := w.Write([]byte(KvSpaceSeparator))
   372  		m3, _ := w.Write([]byte(target.KVS.String()))
   373  		if len(kvsTargets) > 1 {
   374  			m4, _ := w.Write([]byte(KvNewline))
   375  			n += m1 + m2 + m3 + m4
   376  		} else {
   377  			n += m1 + m2 + m3
   378  		}
   379  	}
   380  	return int64(n), nil
   381  }
   382  
   383  // Default KV configs for worm and region
   384  var (
   385  	DefaultCredentialKVS = KVS{
   386  		KV{
   387  			Key:   AccessKey,
   388  			Value: auth.DefaultAccessKey,
   389  		},
   390  		KV{
   391  			Key:   SecretKey,
   392  			Value: auth.DefaultSecretKey,
   393  		},
   394  	}
   395  
   396  	DefaultRegionKVS = KVS{
   397  		KV{
   398  			Key:   RegionName,
   399  			Value: "",
   400  		},
   401  	}
   402  )
   403  
   404  // LookupCreds - lookup credentials from config.
   405  func LookupCreds(kv KVS) (auth.Credentials, error) {
   406  	if err := CheckValidKeys(CredentialsSubSys, kv, DefaultCredentialKVS); err != nil {
   407  		return auth.Credentials{}, err
   408  	}
   409  	accessKey := kv.Get(AccessKey)
   410  	secretKey := kv.Get(SecretKey)
   411  	if accessKey == "" || secretKey == "" {
   412  		accessKey = auth.DefaultAccessKey
   413  		secretKey = auth.DefaultSecretKey
   414  	}
   415  	return auth.CreateCredentials(accessKey, secretKey)
   416  }
   417  
   418  var validRegionRegex = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-_-]+$")
   419  
   420  // LookupRegion - get current region.
   421  func LookupRegion(kv KVS) (string, error) {
   422  	if err := CheckValidKeys(RegionSubSys, kv, DefaultRegionKVS); err != nil {
   423  		return "", err
   424  	}
   425  	region := env.Get(EnvRegion, "")
   426  	if region == "" {
   427  		region = env.Get(EnvRegionName, kv.Get(RegionName))
   428  	}
   429  	if region != "" {
   430  		if validRegionRegex.MatchString(region) {
   431  			return region, nil
   432  		}
   433  		return "", Errorf(
   434  			"region '%s' is invalid, expected simple characters such as [us-east-1, myregion...]",
   435  			region)
   436  	}
   437  	return "", nil
   438  }
   439  
   440  // CheckValidKeys - checks if inputs KVS has the necessary keys,
   441  // returns error if it find extra or superflous keys.
   442  func CheckValidKeys(subSys string, kv KVS, validKVS KVS) error {
   443  	nkv := KVS{}
   444  	for _, kv := range kv {
   445  		// Comment is a valid key, its also fully optional
   446  		// ignore it since it is a valid key for all
   447  		// sub-systems.
   448  		if kv.Key == Comment {
   449  			continue
   450  		}
   451  		if _, ok := validKVS.Lookup(kv.Key); !ok {
   452  			nkv = append(nkv, kv)
   453  		}
   454  	}
   455  	if len(nkv) > 0 {
   456  		return Errorf(
   457  			"found invalid keys (%s) for '%s' sub-system, use 'mc admin config reset myminio %s' to fix invalid keys", nkv.String(), subSys, subSys)
   458  	}
   459  	return nil
   460  }
   461  
   462  // LookupWorm - check if worm is enabled
   463  func LookupWorm() (bool, error) {
   464  	return ParseBool(env.Get(EnvWorm, EnableOff))
   465  }
   466  
   467  // Carries all the renamed sub-systems from their
   468  // previously known names
   469  var renamedSubsys = map[string]string{
   470  	CrawlerSubSys: ScannerSubSys,
   471  	// Add future sub-system renames
   472  }
   473  
   474  // Merge - merges a new config with all the
   475  // missing values for default configs,
   476  // returns a config.
   477  func (c Config) Merge() Config {
   478  	cp := New()
   479  	for subSys, tgtKV := range c {
   480  		for tgt := range tgtKV {
   481  			ckvs := c[subSys][tgt]
   482  			for _, kv := range cp[subSys][Default] {
   483  				_, ok := c[subSys][tgt].Lookup(kv.Key)
   484  				if !ok {
   485  					ckvs.Set(kv.Key, kv.Value)
   486  				}
   487  			}
   488  			if _, ok := cp[subSys]; !ok {
   489  				rnSubSys, ok := renamedSubsys[subSys]
   490  				if !ok {
   491  					// A config subsystem was removed or server was downgraded.
   492  					Logger.Info("config: ignoring unknown subsystem config %q\n", subSys)
   493  					continue
   494  				}
   495  				// Copy over settings from previous sub-system
   496  				// to newly renamed sub-system
   497  				for _, kv := range cp[rnSubSys][Default] {
   498  					_, ok := c[subSys][tgt].Lookup(kv.Key)
   499  					if !ok {
   500  						ckvs.Set(kv.Key, kv.Value)
   501  					}
   502  				}
   503  				subSys = rnSubSys
   504  			}
   505  			cp[subSys][tgt] = ckvs
   506  		}
   507  	}
   508  	return cp
   509  }
   510  
   511  // New - initialize a new server config.
   512  func New() Config {
   513  	srvCfg := make(Config)
   514  	for _, k := range SubSystems.ToSlice() {
   515  		srvCfg[k] = map[string]KVS{}
   516  		srvCfg[k][Default] = DefaultKVS[k]
   517  	}
   518  	return srvCfg
   519  }
   520  
   521  // Target signifies an individual target
   522  type Target struct {
   523  	SubSystem string
   524  	KVS       KVS
   525  }
   526  
   527  // Targets sub-system targets
   528  type Targets []Target
   529  
   530  // GetKVS - get kvs from specific subsystem.
   531  func (c Config) GetKVS(s string, defaultKVS map[string]KVS) (Targets, error) {
   532  	if len(s) == 0 {
   533  		return nil, Errorf("input cannot be empty")
   534  	}
   535  	inputs := strings.Fields(s)
   536  	if len(inputs) > 1 {
   537  		return nil, Errorf("invalid number of arguments %s", s)
   538  	}
   539  	subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
   540  	if len(subSystemValue) == 0 {
   541  		return nil, Errorf("invalid number of arguments %s", s)
   542  	}
   543  	found := SubSystems.Contains(subSystemValue[0])
   544  	if !found {
   545  		// Check for sub-prefix only if the input value is only a
   546  		// single value, this rejects invalid inputs if any.
   547  		found = !SubSystems.FuncMatch(strings.HasPrefix, subSystemValue[0]).IsEmpty() && len(subSystemValue) == 1
   548  	}
   549  	if !found {
   550  		return nil, Errorf("unknown sub-system %s", s)
   551  	}
   552  
   553  	targets := Targets{}
   554  	subSysPrefix := subSystemValue[0]
   555  	if len(subSystemValue) == 2 {
   556  		if len(subSystemValue[1]) == 0 {
   557  			return nil, Errorf("sub-system target '%s' cannot be empty", s)
   558  		}
   559  		kvs, ok := c[subSysPrefix][subSystemValue[1]]
   560  		if !ok {
   561  			return nil, Errorf("sub-system target '%s' doesn't exist", s)
   562  		}
   563  		for _, kv := range defaultKVS[subSysPrefix] {
   564  			_, ok = kvs.Lookup(kv.Key)
   565  			if !ok {
   566  				kvs.Set(kv.Key, kv.Value)
   567  			}
   568  		}
   569  		targets = append(targets, Target{
   570  			SubSystem: inputs[0],
   571  			KVS:       kvs,
   572  		})
   573  	} else {
   574  		hkvs := HelpSubSysMap[""]
   575  		// Use help for sub-system to preserve the order.
   576  		for _, hkv := range hkvs {
   577  			if !strings.HasPrefix(hkv.Key, subSysPrefix) {
   578  				continue
   579  			}
   580  			if c[hkv.Key][Default].Empty() {
   581  				targets = append(targets, Target{
   582  					SubSystem: hkv.Key,
   583  					KVS:       defaultKVS[hkv.Key],
   584  				})
   585  			}
   586  			for k, kvs := range c[hkv.Key] {
   587  				for _, dkv := range defaultKVS[hkv.Key] {
   588  					_, ok := kvs.Lookup(dkv.Key)
   589  					if !ok {
   590  						kvs.Set(dkv.Key, dkv.Value)
   591  					}
   592  				}
   593  				if k != Default {
   594  					targets = append(targets, Target{
   595  						SubSystem: hkv.Key + SubSystemSeparator + k,
   596  						KVS:       kvs,
   597  					})
   598  				} else {
   599  					targets = append(targets, Target{
   600  						SubSystem: hkv.Key,
   601  						KVS:       kvs,
   602  					})
   603  				}
   604  			}
   605  		}
   606  	}
   607  	return targets, nil
   608  }
   609  
   610  // DelKVS - delete a specific key.
   611  func (c Config) DelKVS(s string) error {
   612  	if len(s) == 0 {
   613  		return Errorf("input arguments cannot be empty")
   614  	}
   615  	inputs := strings.Fields(s)
   616  	if len(inputs) > 1 {
   617  		return Errorf("invalid number of arguments %s", s)
   618  	}
   619  	subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
   620  	if len(subSystemValue) == 0 {
   621  		return Errorf("invalid number of arguments %s", s)
   622  	}
   623  	if !SubSystems.Contains(subSystemValue[0]) {
   624  		// Unknown sub-system found try to remove it anyways.
   625  		delete(c, subSystemValue[0])
   626  		return nil
   627  	}
   628  	tgt := Default
   629  	subSys := subSystemValue[0]
   630  	if len(subSystemValue) == 2 {
   631  		if len(subSystemValue[1]) == 0 {
   632  			return Errorf("sub-system target '%s' cannot be empty", s)
   633  		}
   634  		tgt = subSystemValue[1]
   635  	}
   636  	_, ok := c[subSys][tgt]
   637  	if !ok {
   638  		return Errorf("sub-system %s already deleted", s)
   639  	}
   640  	delete(c[subSys], tgt)
   641  	return nil
   642  }
   643  
   644  // Clone - clones a config map entirely.
   645  func (c Config) Clone() Config {
   646  	cp := New()
   647  	for subSys, tgtKV := range c {
   648  		cp[subSys] = make(map[string]KVS)
   649  		for tgt, kv := range tgtKV {
   650  			cp[subSys][tgt] = append(cp[subSys][tgt], kv...)
   651  		}
   652  	}
   653  	return cp
   654  }
   655  
   656  // SetKVS - set specific key values per sub-system.
   657  func (c Config) SetKVS(s string, defaultKVS map[string]KVS) (dynamic bool, err error) {
   658  	if len(s) == 0 {
   659  		return false, Errorf("input arguments cannot be empty")
   660  	}
   661  	inputs := strings.SplitN(s, KvSpaceSeparator, 2)
   662  	if len(inputs) <= 1 {
   663  		return false, Errorf("invalid number of arguments '%s'", s)
   664  	}
   665  	subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
   666  	if len(subSystemValue) == 0 {
   667  		return false, Errorf("invalid number of arguments %s", s)
   668  	}
   669  
   670  	if !SubSystems.Contains(subSystemValue[0]) {
   671  		return false, Errorf("unknown sub-system %s", s)
   672  	}
   673  
   674  	if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
   675  		return false, Errorf("sub-system '%s' only supports single target", subSystemValue[0])
   676  	}
   677  	dynamic = SubSystemsDynamic.Contains(subSystemValue[0])
   678  
   679  	tgt := Default
   680  	subSys := subSystemValue[0]
   681  	if len(subSystemValue) == 2 {
   682  		tgt = subSystemValue[1]
   683  	}
   684  
   685  	fields := madmin.KvFields(inputs[1], defaultKVS[subSys].Keys())
   686  	if len(fields) == 0 {
   687  		return false, Errorf("sub-system '%s' cannot have empty keys", subSys)
   688  	}
   689  
   690  	var kvs = KVS{}
   691  	var prevK string
   692  	for _, v := range fields {
   693  		kv := strings.SplitN(v, KvSeparator, 2)
   694  		if len(kv) == 0 {
   695  			continue
   696  		}
   697  		if len(kv) == 1 && prevK != "" {
   698  			value := strings.Join([]string{
   699  				kvs.Get(prevK),
   700  				madmin.SanitizeValue(kv[0]),
   701  			}, KvSpaceSeparator)
   702  			kvs.Set(prevK, value)
   703  			continue
   704  		}
   705  		if len(kv) == 2 {
   706  			prevK = kv[0]
   707  			kvs.Set(prevK, madmin.SanitizeValue(kv[1]))
   708  			continue
   709  		}
   710  		return false, Errorf("key '%s', cannot have empty value", kv[0])
   711  	}
   712  
   713  	_, ok := kvs.Lookup(Enable)
   714  	// Check if state is required
   715  	_, enableRequired := defaultKVS[subSys].Lookup(Enable)
   716  	if !ok && enableRequired {
   717  		// implicit state "on" if not specified.
   718  		kvs.Set(Enable, EnableOn)
   719  	}
   720  
   721  	currKVS, ok := c[subSys][tgt]
   722  	if !ok {
   723  		currKVS = defaultKVS[subSys]
   724  	} else {
   725  		for _, kv := range defaultKVS[subSys] {
   726  			if _, ok = currKVS.Lookup(kv.Key); !ok {
   727  				currKVS.Set(kv.Key, kv.Value)
   728  			}
   729  		}
   730  	}
   731  
   732  	for _, kv := range kvs {
   733  		if kv.Key == Comment {
   734  			// Skip comment and add it later.
   735  			continue
   736  		}
   737  		currKVS.Set(kv.Key, kv.Value)
   738  	}
   739  
   740  	v, ok := kvs.Lookup(Comment)
   741  	if ok {
   742  		currKVS.Set(Comment, v)
   743  	}
   744  
   745  	hkvs := HelpSubSysMap[subSys]
   746  	for _, hkv := range hkvs {
   747  		var enabled bool
   748  		if enableRequired {
   749  			enabled = currKVS.Get(Enable) == EnableOn
   750  		} else {
   751  			// when enable arg is not required
   752  			// then it is implicit on for the sub-system.
   753  			enabled = true
   754  		}
   755  		v, _ := currKVS.Lookup(hkv.Key)
   756  		if v == "" && !hkv.Optional && enabled {
   757  			// Return error only if the
   758  			// key is enabled, for state=off
   759  			// let it be empty.
   760  			return false, Errorf(
   761  				"'%s' is not optional for '%s' sub-system, please check '%s' documentation",
   762  				hkv.Key, subSys, subSys)
   763  		}
   764  	}
   765  	c[subSys][tgt] = currKVS
   766  	return dynamic, nil
   767  }