github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/config/config.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package config
    19  
    20  import (
    21  	"bufio"
    22  	"fmt"
    23  	"io"
    24  	"regexp"
    25  	"sort"
    26  	"strings"
    27  
    28  	"github.com/minio/madmin-go/v3"
    29  	"github.com/minio/minio-go/v7/pkg/set"
    30  	"github.com/minio/minio/internal/auth"
    31  	"github.com/minio/pkg/v2/env"
    32  )
    33  
    34  // ErrorConfig holds the config error types
    35  type ErrorConfig interface {
    36  	ErrConfigGeneric | ErrConfigNotFound
    37  }
    38  
    39  // ErrConfigGeneric is a generic config type
    40  type ErrConfigGeneric struct {
    41  	msg string
    42  }
    43  
    44  func (ge *ErrConfigGeneric) setMsg(msg string) {
    45  	ge.msg = msg
    46  }
    47  
    48  func (ge ErrConfigGeneric) Error() string {
    49  	return ge.msg
    50  }
    51  
    52  // ErrConfigNotFound is an error to indicate
    53  // that a config parameter is not found
    54  type ErrConfigNotFound struct {
    55  	ErrConfigGeneric
    56  }
    57  
    58  // Error creates an error message and wraps
    59  // it with the error type specified in the type parameter
    60  func Error[T ErrorConfig, PT interface {
    61  	*T
    62  	setMsg(string)
    63  }](format string, vals ...interface{},
    64  ) T {
    65  	pt := PT(new(T))
    66  	pt.setMsg(fmt.Sprintf(format, vals...))
    67  	return *pt
    68  }
    69  
    70  // Errorf formats an error and returns it as a generic config error
    71  func Errorf(format string, vals ...interface{}) ErrConfigGeneric {
    72  	return Error[ErrConfigGeneric](format, vals...)
    73  }
    74  
    75  // Default keys
    76  const (
    77  	Default = madmin.Default
    78  	Enable  = madmin.EnableKey
    79  	Comment = madmin.CommentKey
    80  
    81  	EnvSeparator = "="
    82  
    83  	// Enable values
    84  	EnableOn  = madmin.EnableOn
    85  	EnableOff = madmin.EnableOff
    86  
    87  	RegionKey  = "region"
    88  	NameKey    = "name"
    89  	RegionName = "name"
    90  	AccessKey  = "access_key"
    91  	SecretKey  = "secret_key"
    92  	License    = "license" // Deprecated Dec 2021
    93  	APIKey     = "api_key"
    94  	Proxy      = "proxy"
    95  )
    96  
    97  // Top level config constants.
    98  const (
    99  	PolicyOPASubSys      = madmin.PolicyOPASubSys
   100  	PolicyPluginSubSys   = madmin.PolicyPluginSubSys
   101  	IdentityOpenIDSubSys = madmin.IdentityOpenIDSubSys
   102  	IdentityLDAPSubSys   = madmin.IdentityLDAPSubSys
   103  	IdentityTLSSubSys    = madmin.IdentityTLSSubSys
   104  	IdentityPluginSubSys = madmin.IdentityPluginSubSys
   105  	CacheSubSys          = madmin.CacheSubSys
   106  	SiteSubSys           = madmin.SiteSubSys
   107  	RegionSubSys         = madmin.RegionSubSys
   108  	EtcdSubSys           = madmin.EtcdSubSys
   109  	StorageClassSubSys   = madmin.StorageClassSubSys
   110  	APISubSys            = madmin.APISubSys
   111  	CompressionSubSys    = madmin.CompressionSubSys
   112  	LoggerWebhookSubSys  = madmin.LoggerWebhookSubSys
   113  	AuditWebhookSubSys   = madmin.AuditWebhookSubSys
   114  	AuditKafkaSubSys     = madmin.AuditKafkaSubSys
   115  	HealSubSys           = madmin.HealSubSys
   116  	ScannerSubSys        = madmin.ScannerSubSys
   117  	CrawlerSubSys        = madmin.CrawlerSubSys
   118  	SubnetSubSys         = madmin.SubnetSubSys
   119  	CallhomeSubSys       = madmin.CallhomeSubSys
   120  	DriveSubSys          = madmin.DriveSubSys
   121  	BatchSubSys          = madmin.BatchSubSys
   122  	BrowserSubSys        = madmin.BrowserSubSys
   123  	ILMSubSys            = madmin.ILMSubsys
   124  
   125  	// Add new constants here (similar to above) if you add new fields to config.
   126  )
   127  
   128  // Notification config constants.
   129  const (
   130  	NotifyKafkaSubSys    = madmin.NotifyKafkaSubSys
   131  	NotifyMQTTSubSys     = madmin.NotifyMQTTSubSys
   132  	NotifyMySQLSubSys    = madmin.NotifyMySQLSubSys
   133  	NotifyNATSSubSys     = madmin.NotifyNATSSubSys
   134  	NotifyNSQSubSys      = madmin.NotifyNSQSubSys
   135  	NotifyESSubSys       = madmin.NotifyESSubSys
   136  	NotifyAMQPSubSys     = madmin.NotifyAMQPSubSys
   137  	NotifyPostgresSubSys = madmin.NotifyPostgresSubSys
   138  	NotifyRedisSubSys    = madmin.NotifyRedisSubSys
   139  	NotifyWebhookSubSys  = madmin.NotifyWebhookSubSys
   140  
   141  	// Add new constants here (similar to above) if you add new fields to config.
   142  )
   143  
   144  // Lambda config constants.
   145  const (
   146  	LambdaWebhookSubSys = madmin.LambdaWebhookSubSys
   147  )
   148  
   149  // NotifySubSystems - all notification sub-systems
   150  var NotifySubSystems = set.CreateStringSet(
   151  	NotifyKafkaSubSys,
   152  	NotifyMQTTSubSys,
   153  	NotifyMySQLSubSys,
   154  	NotifyNATSSubSys,
   155  	NotifyNSQSubSys,
   156  	NotifyESSubSys,
   157  	NotifyAMQPSubSys,
   158  	NotifyPostgresSubSys,
   159  	NotifyRedisSubSys,
   160  	NotifyWebhookSubSys,
   161  )
   162  
   163  // LambdaSubSystems - all lambda sub-systems
   164  var LambdaSubSystems = set.CreateStringSet(
   165  	LambdaWebhookSubSys,
   166  )
   167  
   168  // LoggerSubSystems - all sub-systems related to logger
   169  var LoggerSubSystems = set.CreateStringSet(
   170  	LoggerWebhookSubSys,
   171  	AuditWebhookSubSys,
   172  	AuditKafkaSubSys,
   173  )
   174  
   175  // SubSystems - all supported sub-systems
   176  var SubSystems = madmin.SubSystems
   177  
   178  // SubSystemsDynamic - all sub-systems that have dynamic config.
   179  var SubSystemsDynamic = set.CreateStringSet(
   180  	APISubSys,
   181  	CompressionSubSys,
   182  	ScannerSubSys,
   183  	HealSubSys,
   184  	SubnetSubSys,
   185  	CallhomeSubSys,
   186  	DriveSubSys,
   187  	LoggerWebhookSubSys,
   188  	AuditWebhookSubSys,
   189  	AuditKafkaSubSys,
   190  	StorageClassSubSys,
   191  	CacheSubSys,
   192  	ILMSubSys,
   193  	BatchSubSys,
   194  	BrowserSubSys,
   195  )
   196  
   197  // SubSystemsSingleTargets - subsystems which only support single target.
   198  var SubSystemsSingleTargets = set.CreateStringSet(
   199  	SiteSubSys,
   200  	RegionSubSys,
   201  	EtcdSubSys,
   202  	CacheSubSys,
   203  	APISubSys,
   204  	StorageClassSubSys,
   205  	CompressionSubSys,
   206  	PolicyOPASubSys,
   207  	PolicyPluginSubSys,
   208  	IdentityLDAPSubSys,
   209  	IdentityTLSSubSys,
   210  	IdentityPluginSubSys,
   211  	HealSubSys,
   212  	ScannerSubSys,
   213  	SubnetSubSys,
   214  	CallhomeSubSys,
   215  	DriveSubSys,
   216  	ILMSubSys,
   217  	BatchSubSys,
   218  	BrowserSubSys,
   219  )
   220  
   221  // Constant separators
   222  const (
   223  	SubSystemSeparator = madmin.SubSystemSeparator
   224  	KvSeparator        = madmin.KvSeparator
   225  	KvSpaceSeparator   = madmin.KvSpaceSeparator
   226  	KvComment          = madmin.KvComment
   227  	KvNewline          = madmin.KvNewline
   228  	KvDoubleQuote      = madmin.KvDoubleQuote
   229  	KvSingleQuote      = madmin.KvSingleQuote
   230  
   231  	// Env prefix used for all envs in MinIO
   232  	EnvPrefix        = madmin.EnvPrefix
   233  	EnvWordDelimiter = madmin.EnvWordDelimiter
   234  )
   235  
   236  // DefaultKVS - default kvs for all sub-systems
   237  var DefaultKVS = map[string]KVS{}
   238  
   239  // RegisterDefaultKVS - this function saves input kvsMap
   240  // globally, this should be called only once preferably
   241  // during `init()`.
   242  func RegisterDefaultKVS(kvsMap map[string]KVS) {
   243  	for subSys, kvs := range kvsMap {
   244  		DefaultKVS[subSys] = kvs
   245  	}
   246  }
   247  
   248  // HelpSubSysMap - help for all individual KVS for each sub-systems
   249  // also carries a special empty sub-system which dumps
   250  // help for each sub-system key.
   251  var HelpSubSysMap = map[string]HelpKVS{}
   252  
   253  // RegisterHelpSubSys - this function saves
   254  // input help KVS for each sub-system globally,
   255  // this function should be called only once
   256  // preferably in during `init()`.
   257  func RegisterHelpSubSys(helpKVSMap map[string]HelpKVS) {
   258  	for subSys, hkvs := range helpKVSMap {
   259  		HelpSubSysMap[subSys] = hkvs
   260  	}
   261  }
   262  
   263  // HelpDeprecatedSubSysMap - help for all deprecated sub-systems, that may be
   264  // removed in the future.
   265  var HelpDeprecatedSubSysMap = map[string]HelpKV{}
   266  
   267  // RegisterHelpDeprecatedSubSys - saves input help KVS for deprecated
   268  // sub-systems globally. Should be called only once at init.
   269  func RegisterHelpDeprecatedSubSys(helpDeprecatedKVMap map[string]HelpKV) {
   270  	for k, v := range helpDeprecatedKVMap {
   271  		HelpDeprecatedSubSysMap[k] = v
   272  	}
   273  }
   274  
   275  // KV - is a shorthand of each key value.
   276  type KV struct {
   277  	Key   string `json:"key"`
   278  	Value string `json:"value"`
   279  
   280  	HiddenIfEmpty bool `json:"-"`
   281  }
   282  
   283  func (kv KV) String() string {
   284  	var s strings.Builder
   285  	s.WriteString(kv.Key)
   286  	s.WriteString(KvSeparator)
   287  	spc := madmin.HasSpace(kv.Value)
   288  	if spc {
   289  		s.WriteString(KvDoubleQuote)
   290  	}
   291  	s.WriteString(kv.Value)
   292  	if spc {
   293  		s.WriteString(KvDoubleQuote)
   294  	}
   295  	return s.String()
   296  }
   297  
   298  // KVS - is a shorthand for some wrapper functions
   299  // to operate on list of key values.
   300  type KVS []KV
   301  
   302  // Empty - return if kv is empty
   303  func (kvs KVS) Empty() bool {
   304  	return len(kvs) == 0
   305  }
   306  
   307  // Clone - returns a copy of the KVS
   308  func (kvs KVS) Clone() KVS {
   309  	return append(make(KVS, 0, len(kvs)), kvs...)
   310  }
   311  
   312  // GetWithDefault - returns default value if key not set
   313  func (kvs KVS) GetWithDefault(key string, defaultKVS KVS) string {
   314  	v := kvs.Get(key)
   315  	if len(v) == 0 {
   316  		return defaultKVS.Get(key)
   317  	}
   318  	return v
   319  }
   320  
   321  // Keys returns the list of keys for the current KVS
   322  func (kvs KVS) Keys() []string {
   323  	keys := make([]string, len(kvs))
   324  	var foundComment bool
   325  	for i := range kvs {
   326  		if kvs[i].Key == madmin.CommentKey {
   327  			foundComment = true
   328  		}
   329  		keys[i] = kvs[i].Key
   330  	}
   331  	// Comment KV not found, add it explicitly.
   332  	if !foundComment {
   333  		keys = append(keys, madmin.CommentKey)
   334  	}
   335  	return keys
   336  }
   337  
   338  func (kvs KVS) String() string {
   339  	var s strings.Builder
   340  	for _, kv := range kvs {
   341  		s.WriteString(kv.String())
   342  		s.WriteString(KvSpaceSeparator)
   343  	}
   344  	return s.String()
   345  }
   346  
   347  // Merge environment values with on disk KVS, environment values overrides
   348  // anything on the disk.
   349  func Merge(cfgKVS map[string]KVS, envname string, defaultKVS KVS) map[string]KVS {
   350  	newCfgKVS := make(map[string]KVS)
   351  	for _, e := range env.List(envname) {
   352  		tgt := strings.TrimPrefix(e, envname+Default)
   353  		if tgt == envname {
   354  			tgt = Default
   355  		}
   356  		newCfgKVS[tgt] = defaultKVS
   357  	}
   358  	for tgt, kv := range cfgKVS {
   359  		newCfgKVS[tgt] = kv
   360  	}
   361  	return newCfgKVS
   362  }
   363  
   364  // Set sets a value, if not sets a default value.
   365  func (kvs *KVS) Set(key, value string) {
   366  	for i, kv := range *kvs {
   367  		if kv.Key == key {
   368  			(*kvs)[i] = KV{
   369  				Key:   key,
   370  				Value: value,
   371  			}
   372  			return
   373  		}
   374  	}
   375  	*kvs = append(*kvs, KV{
   376  		Key:   key,
   377  		Value: value,
   378  	})
   379  }
   380  
   381  // Get - returns the value of a key, if not found returns empty.
   382  func (kvs KVS) Get(key string) string {
   383  	v, ok := kvs.Lookup(key)
   384  	if ok {
   385  		return v
   386  	}
   387  	return ""
   388  }
   389  
   390  // Delete - deletes the key if present from the KV list.
   391  func (kvs *KVS) Delete(key string) {
   392  	for i, kv := range *kvs {
   393  		if kv.Key == key {
   394  			*kvs = append((*kvs)[:i], (*kvs)[i+1:]...)
   395  			return
   396  		}
   397  	}
   398  }
   399  
   400  // LookupKV returns the KV by its key
   401  func (kvs KVS) LookupKV(key string) (KV, bool) {
   402  	for _, kv := range kvs {
   403  		if kv.Key == key {
   404  			return kv, true
   405  		}
   406  	}
   407  	return KV{}, false
   408  }
   409  
   410  // Lookup - lookup a key in a list of KVS
   411  func (kvs KVS) Lookup(key string) (string, bool) {
   412  	for _, kv := range kvs {
   413  		if kv.Key == key {
   414  			return kv.Value, true
   415  		}
   416  	}
   417  	return "", false
   418  }
   419  
   420  // Config - MinIO server config structure.
   421  type Config map[string]map[string]KVS
   422  
   423  // DelFrom - deletes all keys in the input reader.
   424  func (c Config) DelFrom(r io.Reader) error {
   425  	scanner := bufio.NewScanner(r)
   426  	for scanner.Scan() {
   427  		// Skip any empty lines, or comment like characters
   428  		text := scanner.Text()
   429  		if text == "" || strings.HasPrefix(text, KvComment) {
   430  			continue
   431  		}
   432  		if err := c.DelKVS(text); err != nil {
   433  			return err
   434  		}
   435  	}
   436  	return scanner.Err()
   437  }
   438  
   439  // ContextKeyString is type(string) for contextKey
   440  type ContextKeyString string
   441  
   442  // ContextKeyForTargetFromConfig - key for context for target from config
   443  const ContextKeyForTargetFromConfig = ContextKeyString("ContextKeyForTargetFromConfig")
   444  
   445  // ParseConfigTargetID - read all targetIDs from reader
   446  func ParseConfigTargetID(r io.Reader) (ids map[string]bool, err error) {
   447  	ids = make(map[string]bool)
   448  	scanner := bufio.NewScanner(r)
   449  	for scanner.Scan() {
   450  		// Skip any empty lines, or comment like characters
   451  		text := scanner.Text()
   452  		if text == "" || strings.HasPrefix(text, KvComment) {
   453  			continue
   454  		}
   455  		_, _, tgt, err := GetSubSys(text)
   456  		if err != nil {
   457  			return nil, err
   458  		}
   459  		ids[tgt] = true
   460  	}
   461  	if err := scanner.Err(); err != nil {
   462  		return nil, err
   463  	}
   464  	return
   465  }
   466  
   467  // ReadConfig - read content from input and write into c.
   468  // Returns whether all parameters were dynamic.
   469  func (c Config) ReadConfig(r io.Reader) (dynOnly bool, err error) {
   470  	var n int
   471  	scanner := bufio.NewScanner(r)
   472  	dynOnly = true
   473  	for scanner.Scan() {
   474  		// Skip any empty lines, or comment like characters
   475  		text := scanner.Text()
   476  		if text == "" || strings.HasPrefix(text, KvComment) {
   477  			continue
   478  		}
   479  		dynamic, err := c.SetKVS(text, DefaultKVS)
   480  		if err != nil {
   481  			return false, err
   482  		}
   483  		dynOnly = dynOnly && dynamic
   484  		n += len(text)
   485  	}
   486  	if err := scanner.Err(); err != nil {
   487  		return false, err
   488  	}
   489  	return dynOnly, nil
   490  }
   491  
   492  // RedactSensitiveInfo - removes sensitive information
   493  // like urls and credentials from the configuration
   494  func (c Config) RedactSensitiveInfo() Config {
   495  	nc := c.Clone()
   496  
   497  	for configName, configVals := range nc {
   498  		for _, helpKV := range HelpSubSysMap[configName] {
   499  			if helpKV.Sensitive {
   500  				for name, kvs := range configVals {
   501  					for i := range kvs {
   502  						if kvs[i].Key == helpKV.Key && len(kvs[i].Value) > 0 {
   503  							kvs[i].Value = "*redacted*"
   504  						}
   505  					}
   506  					configVals[name] = kvs
   507  				}
   508  			}
   509  		}
   510  	}
   511  
   512  	return nc
   513  }
   514  
   515  // Default KV configs for worm and region
   516  var (
   517  	DefaultCredentialKVS = KVS{
   518  		KV{
   519  			Key:   AccessKey,
   520  			Value: auth.DefaultAccessKey,
   521  		},
   522  		KV{
   523  			Key:   SecretKey,
   524  			Value: auth.DefaultSecretKey,
   525  		},
   526  	}
   527  
   528  	DefaultSiteKVS = KVS{
   529  		KV{
   530  			Key:   NameKey,
   531  			Value: "",
   532  		},
   533  		KV{
   534  			Key:   RegionKey,
   535  			Value: "",
   536  		},
   537  	}
   538  
   539  	DefaultRegionKVS = KVS{
   540  		KV{
   541  			Key:   RegionName,
   542  			Value: "",
   543  		},
   544  	}
   545  )
   546  
   547  // Site - holds site info - name and region.
   548  type Site struct {
   549  	Name   string
   550  	Region string
   551  }
   552  
   553  var validRegionRegex = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-_-]+$")
   554  
   555  // validSiteNameRegex - allows lowercase letters, digits and '-', starts with
   556  // letter. At least 2 characters long.
   557  var validSiteNameRegex = regexp.MustCompile("^[a-z][a-z0-9-]+$")
   558  
   559  // LookupSite - get site related configuration. Loads configuration from legacy
   560  // region sub-system as well.
   561  func LookupSite(siteKV KVS, regionKV KVS) (s Site, err error) {
   562  	if err = CheckValidKeys(SiteSubSys, siteKV, DefaultSiteKVS); err != nil {
   563  		return
   564  	}
   565  	region := env.Get(EnvRegion, "")
   566  	if region == "" {
   567  		env.Get(EnvRegionName, "")
   568  	}
   569  	if region == "" {
   570  		region = env.Get(EnvSiteRegion, siteKV.Get(RegionKey))
   571  	}
   572  	if region == "" {
   573  		// No region config found in the site-subsystem. So lookup the legacy
   574  		// region sub-system.
   575  		if err = CheckValidKeys(RegionSubSys, regionKV, DefaultRegionKVS); err != nil {
   576  			// An invalid key was found in the region sub-system.
   577  			// Since the region sub-system cannot be (re)set as it
   578  			// is legacy, we return an error to tell the user to
   579  			// reset the region via the new command.
   580  			err = Errorf("could not load region from legacy configuration as it was invalid - use 'mc admin config set myminio site region=myregion name=myname' to set a region and name (%v)", err)
   581  			return
   582  		}
   583  
   584  		region = regionKV.Get(RegionName)
   585  	}
   586  	if region != "" {
   587  		if !validRegionRegex.MatchString(region) {
   588  			err = Errorf(
   589  				"region '%s' is invalid, expected simple characters such as [us-east-1, myregion...]",
   590  				region)
   591  			return
   592  		}
   593  		s.Region = region
   594  	}
   595  
   596  	name := env.Get(EnvSiteName, siteKV.Get(NameKey))
   597  	if name != "" {
   598  		if !validSiteNameRegex.MatchString(name) {
   599  			err = Errorf(
   600  				"site name '%s' is invalid, expected simple characters such as [cal-rack0, myname...]",
   601  				name)
   602  			return
   603  		}
   604  		s.Name = name
   605  	}
   606  	return
   607  }
   608  
   609  // CheckValidKeys - checks if inputs KVS has the necessary keys,
   610  // returns error if it find extra or superfluous keys.
   611  func CheckValidKeys(subSys string, kv KVS, validKVS KVS, deprecatedKeys ...string) error {
   612  	nkv := KVS{}
   613  	for _, kv := range kv {
   614  		// Comment is a valid key, its also fully optional
   615  		// ignore it since it is a valid key for all
   616  		// sub-systems.
   617  		if kv.Key == Comment {
   618  			continue
   619  		}
   620  		var skip bool
   621  		for _, deprecatedKey := range deprecatedKeys {
   622  			if kv.Key == deprecatedKey {
   623  				skip = true
   624  				break
   625  			}
   626  		}
   627  		if skip {
   628  			continue
   629  		}
   630  		if _, ok := validKVS.Lookup(kv.Key); !ok {
   631  			nkv = append(nkv, kv)
   632  		}
   633  	}
   634  	if len(nkv) > 0 {
   635  		return Errorf(
   636  			"found invalid keys (%s) for '%s' sub-system, use 'mc admin config reset myminio %s' to fix invalid keys", nkv.String(), subSys, subSys)
   637  	}
   638  	return nil
   639  }
   640  
   641  // LookupWorm - check if worm is enabled
   642  func LookupWorm() (bool, error) {
   643  	return ParseBool(env.Get(EnvWorm, EnableOff))
   644  }
   645  
   646  // Carries all the renamed sub-systems from their
   647  // previously known names
   648  var renamedSubsys = map[string]string{
   649  	CrawlerSubSys: ScannerSubSys,
   650  	// Add future sub-system renames
   651  }
   652  
   653  const ( // deprecated keys
   654  	apiReplicationWorkers       = "replication_workers"
   655  	apiReplicationFailedWorkers = "replication_failed_workers"
   656  )
   657  
   658  // map of subsystem to deleted keys
   659  var deletedSubSysKeys = map[string][]string{
   660  	APISubSys: {apiReplicationWorkers, apiReplicationFailedWorkers},
   661  	// Add future sub-system deleted keys
   662  }
   663  
   664  // Merge - merges a new config with all the
   665  // missing values for default configs,
   666  // returns a config.
   667  func (c Config) Merge() Config {
   668  	cp := New()
   669  	for subSys, tgtKV := range c {
   670  		for tgt := range tgtKV {
   671  			ckvs := c[subSys][tgt]
   672  			for _, kv := range cp[subSys][Default] {
   673  				_, ok := c[subSys][tgt].Lookup(kv.Key)
   674  				if !ok {
   675  					ckvs.Set(kv.Key, kv.Value)
   676  				}
   677  			}
   678  			if _, ok := cp[subSys]; !ok {
   679  				rnSubSys, ok := renamedSubsys[subSys]
   680  				if !ok {
   681  					// A config subsystem was removed or server was downgraded.
   682  					continue
   683  				}
   684  				// Copy over settings from previous sub-system
   685  				// to newly renamed sub-system
   686  				for _, kv := range cp[rnSubSys][Default] {
   687  					_, ok := c[subSys][tgt].Lookup(kv.Key)
   688  					if !ok {
   689  						ckvs.Set(kv.Key, kv.Value)
   690  					}
   691  				}
   692  				subSys = rnSubSys
   693  			}
   694  			// Delete deprecated keys for subsystem if any
   695  			if keys, ok := deletedSubSysKeys[subSys]; ok {
   696  				for _, key := range keys {
   697  					ckvs.Delete(key)
   698  				}
   699  			}
   700  			cp[subSys][tgt] = ckvs
   701  		}
   702  	}
   703  
   704  	return cp
   705  }
   706  
   707  // New - initialize a new server config.
   708  func New() Config {
   709  	srvCfg := make(Config)
   710  	for _, k := range SubSystems.ToSlice() {
   711  		srvCfg[k] = map[string]KVS{}
   712  		srvCfg[k][Default] = DefaultKVS[k]
   713  	}
   714  	return srvCfg
   715  }
   716  
   717  // Target signifies an individual target
   718  type Target struct {
   719  	SubSystem string
   720  	KVS       KVS
   721  }
   722  
   723  // Targets sub-system targets
   724  type Targets []Target
   725  
   726  // GetKVS - get kvs from specific subsystem.
   727  func (c Config) GetKVS(s string, defaultKVS map[string]KVS) (Targets, error) {
   728  	if len(s) == 0 {
   729  		return nil, Errorf("input cannot be empty")
   730  	}
   731  	inputs := strings.Fields(s)
   732  	if len(inputs) > 1 {
   733  		return nil, Errorf("invalid number of arguments %s", s)
   734  	}
   735  	subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
   736  	if len(subSystemValue) == 0 {
   737  		return nil, Errorf("invalid number of arguments %s", s)
   738  	}
   739  	found := SubSystems.Contains(subSystemValue[0])
   740  	if !found {
   741  		// Check for sub-prefix only if the input value is only a
   742  		// single value, this rejects invalid inputs if any.
   743  		found = !SubSystems.FuncMatch(strings.HasPrefix, subSystemValue[0]).IsEmpty() && len(subSystemValue) == 1
   744  	}
   745  	if !found {
   746  		return nil, Errorf("unknown sub-system %s", s)
   747  	}
   748  
   749  	targets := Targets{}
   750  	subSysPrefix := subSystemValue[0]
   751  	if len(subSystemValue) == 2 {
   752  		if len(subSystemValue[1]) == 0 {
   753  			return nil, Errorf("sub-system target '%s' cannot be empty", s)
   754  		}
   755  		kvs, ok := c[subSysPrefix][subSystemValue[1]]
   756  		if !ok {
   757  			return nil, Errorf("sub-system target '%s' doesn't exist", s)
   758  		}
   759  		for _, kv := range defaultKVS[subSysPrefix] {
   760  			_, ok = kvs.Lookup(kv.Key)
   761  			if !ok {
   762  				kvs.Set(kv.Key, kv.Value)
   763  			}
   764  		}
   765  		targets = append(targets, Target{
   766  			SubSystem: inputs[0],
   767  			KVS:       kvs,
   768  		})
   769  	} else {
   770  		// Use help for sub-system to preserve the order. Add deprecated
   771  		// keys at the end (in some order).
   772  		kvsOrder := append([]HelpKV{}, HelpSubSysMap[""]...)
   773  		for _, v := range HelpDeprecatedSubSysMap {
   774  			kvsOrder = append(kvsOrder, v)
   775  		}
   776  
   777  		for _, hkv := range kvsOrder {
   778  			if !strings.HasPrefix(hkv.Key, subSysPrefix) {
   779  				continue
   780  			}
   781  			if c[hkv.Key][Default].Empty() {
   782  				targets = append(targets, Target{
   783  					SubSystem: hkv.Key,
   784  					KVS:       defaultKVS[hkv.Key],
   785  				})
   786  			}
   787  			for k, kvs := range c[hkv.Key] {
   788  				for _, dkv := range defaultKVS[hkv.Key] {
   789  					_, ok := kvs.Lookup(dkv.Key)
   790  					if !ok {
   791  						kvs.Set(dkv.Key, dkv.Value)
   792  					}
   793  				}
   794  				if k != Default {
   795  					targets = append(targets, Target{
   796  						SubSystem: hkv.Key + SubSystemSeparator + k,
   797  						KVS:       kvs,
   798  					})
   799  				} else {
   800  					targets = append(targets, Target{
   801  						SubSystem: hkv.Key,
   802  						KVS:       kvs,
   803  					})
   804  				}
   805  			}
   806  		}
   807  	}
   808  	return targets, nil
   809  }
   810  
   811  // DelKVS - delete a specific key.
   812  func (c Config) DelKVS(s string) error {
   813  	subSys, inputs, tgt, err := GetSubSys(s)
   814  	if err != nil {
   815  		if !SubSystems.Contains(subSys) && len(inputs) == 1 {
   816  			// Unknown sub-system found try to remove it anyways.
   817  			delete(c, subSys)
   818  			return nil
   819  		}
   820  		return err
   821  	}
   822  
   823  	ck, ok := c[subSys][tgt]
   824  	if !ok {
   825  		return Error[ErrConfigNotFound]("sub-system %s:%s already deleted or does not exist", subSys, tgt)
   826  	}
   827  
   828  	if len(inputs) == 2 {
   829  		currKVS := ck.Clone()
   830  		defKVS := DefaultKVS[subSys]
   831  		for _, delKey := range strings.Fields(inputs[1]) {
   832  			_, ok := currKVS.Lookup(delKey)
   833  			if !ok {
   834  				return Error[ErrConfigNotFound]("key %s doesn't exist", delKey)
   835  			}
   836  			defVal, isDef := defKVS.Lookup(delKey)
   837  			if isDef {
   838  				currKVS.Set(delKey, defVal)
   839  			} else {
   840  				currKVS.Delete(delKey)
   841  			}
   842  		}
   843  		c[subSys][tgt] = currKVS
   844  	} else {
   845  		delete(c[subSys], tgt)
   846  	}
   847  	return nil
   848  }
   849  
   850  // Clone - clones a config map entirely.
   851  func (c Config) Clone() Config {
   852  	cp := New()
   853  	for subSys, tgtKV := range c {
   854  		cp[subSys] = make(map[string]KVS)
   855  		for tgt, kv := range tgtKV {
   856  			cp[subSys][tgt] = append(cp[subSys][tgt], kv...)
   857  		}
   858  	}
   859  	return cp
   860  }
   861  
   862  // GetSubSys - extracts subssystem info from given config string
   863  func GetSubSys(s string) (subSys string, inputs []string, tgt string, e error) {
   864  	tgt = Default
   865  	if len(s) == 0 {
   866  		return subSys, inputs, tgt, Errorf("input arguments cannot be empty")
   867  	}
   868  	inputs = strings.SplitN(s, KvSpaceSeparator, 2)
   869  
   870  	subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
   871  	subSys = subSystemValue[0]
   872  	if !SubSystems.Contains(subSys) {
   873  		return subSys, inputs, tgt, Errorf("unknown sub-system %s", s)
   874  	}
   875  
   876  	if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
   877  		return subSys, inputs, tgt, Errorf("sub-system '%s' only supports single target", subSystemValue[0])
   878  	}
   879  
   880  	if len(subSystemValue) == 2 {
   881  		tgt = subSystemValue[1]
   882  	}
   883  
   884  	return subSys, inputs, tgt, e
   885  }
   886  
   887  // kvFields - converts an input string of form "k1=v1 k2=v2" into
   888  // fields of ["k1=v1", "k2=v2"], the tokenization of each `k=v`
   889  // happens with the right number of input keys, if keys
   890  // input is empty returned value is empty slice as well.
   891  func kvFields(input string, keys []string) []string {
   892  	valueIndexes := make([]int, 0, len(keys))
   893  	for _, key := range keys {
   894  		i := strings.Index(input, key+KvSeparator)
   895  		if i == -1 {
   896  			continue
   897  		}
   898  		valueIndexes = append(valueIndexes, i)
   899  	}
   900  
   901  	sort.Ints(valueIndexes)
   902  	fields := make([]string, len(valueIndexes))
   903  	for i := range valueIndexes {
   904  		j := i + 1
   905  		if j < len(valueIndexes) {
   906  			fields[i] = strings.TrimSpace(input[valueIndexes[i]:valueIndexes[j]])
   907  		} else {
   908  			fields[i] = strings.TrimSpace(input[valueIndexes[i]:])
   909  		}
   910  	}
   911  	return fields
   912  }
   913  
   914  // SetKVS - set specific key values per sub-system.
   915  func (c Config) SetKVS(s string, defaultKVS map[string]KVS) (dynamic bool, err error) {
   916  	subSys, inputs, tgt, err := GetSubSys(s)
   917  	if err != nil {
   918  		return false, err
   919  	}
   920  
   921  	dynamic = SubSystemsDynamic.Contains(subSys)
   922  
   923  	fields := kvFields(inputs[1], defaultKVS[subSys].Keys())
   924  	if len(fields) == 0 {
   925  		return false, Errorf("sub-system '%s' cannot have empty keys", subSys)
   926  	}
   927  
   928  	kvs := KVS{}
   929  	var prevK string
   930  	for _, v := range fields {
   931  		kv := strings.SplitN(v, KvSeparator, 2)
   932  		if len(kv) == 0 {
   933  			continue
   934  		}
   935  		if len(kv) == 1 && prevK != "" {
   936  			value := strings.Join([]string{
   937  				kvs.Get(prevK),
   938  				madmin.SanitizeValue(kv[0]),
   939  			}, KvSpaceSeparator)
   940  			kvs.Set(prevK, value)
   941  			continue
   942  		}
   943  		if len(kv) == 2 {
   944  			prevK = kv[0]
   945  			kvs.Set(prevK, madmin.SanitizeValue(kv[1]))
   946  			continue
   947  		}
   948  		return false, Errorf("key '%s', cannot have empty value", kv[0])
   949  	}
   950  
   951  	_, ok := kvs.Lookup(Enable)
   952  	// Check if state is required
   953  	_, enableRequired := defaultKVS[subSys].Lookup(Enable)
   954  	if !ok && enableRequired {
   955  		// implicit state "on" if not specified.
   956  		kvs.Set(Enable, EnableOn)
   957  	}
   958  
   959  	var currKVS KVS
   960  	ck, ok := c[subSys][tgt]
   961  	if !ok {
   962  		currKVS = defaultKVS[subSys].Clone()
   963  	} else {
   964  		currKVS = ck.Clone()
   965  		for _, kv := range defaultKVS[subSys] {
   966  			if _, ok = currKVS.Lookup(kv.Key); !ok {
   967  				currKVS.Set(kv.Key, kv.Value)
   968  			}
   969  		}
   970  	}
   971  
   972  	for _, kv := range kvs {
   973  		if kv.Key == Comment {
   974  			// Skip comment and add it later.
   975  			continue
   976  		}
   977  		currKVS.Set(kv.Key, kv.Value)
   978  	}
   979  
   980  	v, ok := kvs.Lookup(Comment)
   981  	if ok {
   982  		currKVS.Set(Comment, v)
   983  	}
   984  
   985  	hkvs := HelpSubSysMap[subSys]
   986  	for _, hkv := range hkvs {
   987  		var enabled bool
   988  		if enableRequired {
   989  			enabled = currKVS.Get(Enable) == EnableOn
   990  		} else {
   991  			// when enable arg is not required
   992  			// then it is implicit on for the sub-system.
   993  			enabled = true
   994  		}
   995  		v, _ := currKVS.Lookup(hkv.Key)
   996  		if v == "" && !hkv.Optional && enabled {
   997  			// Return error only if the
   998  			// key is enabled, for state=off
   999  			// let it be empty.
  1000  			return false, Errorf(
  1001  				"'%s' is not optional for '%s' sub-system, please check '%s' documentation",
  1002  				hkv.Key, subSys, subSys)
  1003  		}
  1004  	}
  1005  	c[subSys][tgt] = currKVS
  1006  	return dynamic, nil
  1007  }
  1008  
  1009  // CheckValidKeys - checks if the config parameters for the given subsystem and
  1010  // target are valid. It checks both the configuration store as well as
  1011  // environment variables.
  1012  func (c Config) CheckValidKeys(subSys string, deprecatedKeys []string) error {
  1013  	defKVS, ok := DefaultKVS[subSys]
  1014  	if !ok {
  1015  		return Errorf("Subsystem %s does not exist", subSys)
  1016  	}
  1017  
  1018  	// Make a list of valid keys for the subsystem including the `comment`
  1019  	// key.
  1020  	validKeys := make([]string, 0, len(defKVS)+1)
  1021  	for _, param := range defKVS {
  1022  		validKeys = append(validKeys, param.Key)
  1023  	}
  1024  	validKeys = append(validKeys, Comment)
  1025  
  1026  	subSysEnvVars := env.List(fmt.Sprintf("%s%s", EnvPrefix, strings.ToUpper(subSys)))
  1027  
  1028  	// Set of env vars for the sub-system to validate.
  1029  	candidates := set.CreateStringSet(subSysEnvVars...)
  1030  
  1031  	// Remove all default target env vars from the candidates set (as they
  1032  	// are valid).
  1033  	for _, param := range validKeys {
  1034  		paramEnvName := getEnvVarName(subSys, Default, param)
  1035  		candidates.Remove(paramEnvName)
  1036  	}
  1037  
  1038  	isSingleTarget := SubSystemsSingleTargets.Contains(subSys)
  1039  	if isSingleTarget && len(candidates) > 0 {
  1040  		return Errorf("The following environment variables are unknown: %s",
  1041  			strings.Join(candidates.ToSlice(), ", "))
  1042  	}
  1043  
  1044  	if !isSingleTarget {
  1045  		// Validate other env vars for all targets.
  1046  		envVars := candidates.ToSlice()
  1047  		for _, envVar := range envVars {
  1048  			for _, param := range validKeys {
  1049  				pEnvName := getEnvVarName(subSys, Default, param) + Default
  1050  				if len(envVar) > len(pEnvName) && strings.HasPrefix(envVar, pEnvName) {
  1051  					// This envVar is valid - it has a
  1052  					// non-empty target.
  1053  					candidates.Remove(envVar)
  1054  				}
  1055  			}
  1056  		}
  1057  
  1058  		// Whatever remains are invalid env vars - return an error.
  1059  		if len(candidates) > 0 {
  1060  			return Errorf("The following environment variables are unknown: %s",
  1061  				strings.Join(candidates.ToSlice(), ", "))
  1062  		}
  1063  	}
  1064  
  1065  	validKeysSet := set.CreateStringSet(validKeys...)
  1066  	validKeysSet = validKeysSet.Difference(set.CreateStringSet(deprecatedKeys...))
  1067  	kvsMap := c[subSys]
  1068  	for tgt, kvs := range kvsMap {
  1069  		invalidKV := KVS{}
  1070  		for _, kv := range kvs {
  1071  			if !validKeysSet.Contains(kv.Key) {
  1072  				invalidKV = append(invalidKV, kv)
  1073  			}
  1074  		}
  1075  		if len(invalidKV) > 0 {
  1076  			return Errorf(
  1077  				"found invalid keys (%s) for '%s:%s' sub-system, use 'mc admin config reset myminio %s:%s' to fix invalid keys",
  1078  				invalidKV.String(), subSys, tgt, subSys, tgt)
  1079  		}
  1080  	}
  1081  	return nil
  1082  }
  1083  
  1084  // GetAvailableTargets - returns a list of targets configured for the given
  1085  // subsystem (whether they are enabled or not). A target could be configured via
  1086  // environment variables or via the configuration store. The default target is
  1087  // `_` and is always returned. The result is sorted so that the default target
  1088  // is the first one and the remaining entries are sorted in ascending order.
  1089  func (c Config) GetAvailableTargets(subSys string) ([]string, error) {
  1090  	if SubSystemsSingleTargets.Contains(subSys) {
  1091  		return []string{Default}, nil
  1092  	}
  1093  
  1094  	defKVS, ok := DefaultKVS[subSys]
  1095  	if !ok {
  1096  		return nil, Errorf("Subsystem %s does not exist", subSys)
  1097  	}
  1098  
  1099  	kvsMap := c[subSys]
  1100  	seen := set.NewStringSet()
  1101  
  1102  	// Add all targets that are configured in the config store.
  1103  	for k := range kvsMap {
  1104  		seen.Add(k)
  1105  	}
  1106  
  1107  	// env:prefix
  1108  	filterMap := map[string]string{}
  1109  	// Add targets that are configured via environment variables.
  1110  	for _, param := range defKVS {
  1111  		envVarPrefix := getEnvVarName(subSys, Default, param.Key) + Default
  1112  		envsWithPrefix := env.List(envVarPrefix)
  1113  		for _, k := range envsWithPrefix {
  1114  			tgtName := strings.TrimPrefix(k, envVarPrefix)
  1115  			if tgtName != "" {
  1116  				if v, ok := filterMap[k]; ok {
  1117  					if strings.HasPrefix(envVarPrefix, v) {
  1118  						filterMap[k] = envVarPrefix
  1119  					}
  1120  				} else {
  1121  					filterMap[k] = envVarPrefix
  1122  				}
  1123  			}
  1124  		}
  1125  	}
  1126  
  1127  	for k, v := range filterMap {
  1128  		seen.Add(strings.TrimPrefix(k, v))
  1129  	}
  1130  
  1131  	seen.Remove(Default)
  1132  	targets := seen.ToSlice()
  1133  	sort.Strings(targets)
  1134  	targets = append([]string{Default}, targets...)
  1135  
  1136  	return targets, nil
  1137  }
  1138  
  1139  func getEnvVarName(subSys, target, param string) string {
  1140  	if target == Default {
  1141  		return fmt.Sprintf("%s%s%s%s", EnvPrefix, strings.ToUpper(subSys), Default, strings.ToUpper(param))
  1142  	}
  1143  
  1144  	return fmt.Sprintf("%s%s%s%s%s%s", EnvPrefix, strings.ToUpper(subSys), Default, strings.ToUpper(param),
  1145  		Default, target)
  1146  }
  1147  
  1148  var resolvableSubsystems = set.CreateStringSet(IdentityOpenIDSubSys, IdentityLDAPSubSys, PolicyPluginSubSys)
  1149  
  1150  // ValueSource represents the source of a config parameter value.
  1151  type ValueSource uint8
  1152  
  1153  // Constants for ValueSource
  1154  const (
  1155  	ValueSourceAbsent ValueSource = iota // this is an error case
  1156  	ValueSourceDef
  1157  	ValueSourceCfg
  1158  	ValueSourceEnv
  1159  )
  1160  
  1161  // ResolveConfigParam returns the effective value of a configuration parameter,
  1162  // within a subsystem and subsystem target. The effective value is, in order of
  1163  // decreasing precedence:
  1164  //
  1165  // 1. the value of the corresponding environment variable if set,
  1166  // 2. the value of the parameter in the config store if set,
  1167  // 3. the default value,
  1168  //
  1169  // This function only works for a subset of sub-systems, others return
  1170  // `ValueSourceAbsent`. FIXME: some parameters have custom environment
  1171  // variables for which support needs to be added.
  1172  //
  1173  // When redactSecrets is true, the returned value is empty if the configuration
  1174  // parameter is a secret, and the returned isRedacted flag is set.
  1175  func (c Config) ResolveConfigParam(subSys, target, cfgParam string, redactSecrets bool,
  1176  ) (value string, cs ValueSource, isRedacted bool) {
  1177  	// cs = ValueSourceAbsent initially as it is iota by default.
  1178  
  1179  	// Initially only support OpenID
  1180  	if !resolvableSubsystems.Contains(subSys) {
  1181  		return
  1182  	}
  1183  
  1184  	// Check if config param requested is valid.
  1185  	defKVS, ok := DefaultKVS[subSys]
  1186  	if !ok {
  1187  		return
  1188  	}
  1189  
  1190  	defValue, isFound := defKVS.Lookup(cfgParam)
  1191  	// Comments usually are absent from `defKVS`, so we handle it specially.
  1192  	if !isFound && cfgParam == Comment {
  1193  		defValue, isFound = "", true
  1194  	}
  1195  	if !isFound {
  1196  		return
  1197  	}
  1198  
  1199  	if target == "" {
  1200  		target = Default
  1201  	}
  1202  
  1203  	if redactSecrets {
  1204  		// If the configuration parameter is a secret, make sure to redact it when
  1205  		// we return.
  1206  		helpKV, _ := HelpSubSysMap[subSys].Lookup(cfgParam)
  1207  		if helpKV.Secret {
  1208  			defer func() {
  1209  				value = ""
  1210  				isRedacted = true
  1211  			}()
  1212  		}
  1213  	}
  1214  
  1215  	envVar := getEnvVarName(subSys, target, cfgParam)
  1216  
  1217  	// Lookup Env var.
  1218  	value = env.Get(envVar, "")
  1219  	if value != "" {
  1220  		cs = ValueSourceEnv
  1221  		return
  1222  	}
  1223  
  1224  	// Lookup config store.
  1225  	if subSysStore, ok := c[subSys]; ok {
  1226  		if kvs, ok2 := subSysStore[target]; ok2 {
  1227  			var ok3 bool
  1228  			value, ok3 = kvs.Lookup(cfgParam)
  1229  			if ok3 {
  1230  				cs = ValueSourceCfg
  1231  				return
  1232  			}
  1233  		}
  1234  	}
  1235  
  1236  	// Return the default value.
  1237  	value = defValue
  1238  	cs = ValueSourceDef
  1239  	return
  1240  }
  1241  
  1242  // KVSrc represents a configuration parameter key and value along with the
  1243  // source of the value.
  1244  type KVSrc struct {
  1245  	Key   string
  1246  	Value string
  1247  	Src   ValueSource
  1248  }
  1249  
  1250  // GetResolvedConfigParams returns all applicable config parameters with their
  1251  // value sources.
  1252  func (c Config) GetResolvedConfigParams(subSys, target string, redactSecrets bool) ([]KVSrc, error) {
  1253  	if !resolvableSubsystems.Contains(subSys) {
  1254  		return nil, Errorf("unsupported subsystem: %s", subSys)
  1255  	}
  1256  
  1257  	// Check if config param requested is valid.
  1258  	defKVS, ok := DefaultKVS[subSys]
  1259  	if !ok {
  1260  		return nil, Errorf("unknown subsystem: %s", subSys)
  1261  	}
  1262  
  1263  	r := make([]KVSrc, 0, len(defKVS)+1)
  1264  	for _, kv := range defKVS {
  1265  		v, vs, isRedacted := c.ResolveConfigParam(subSys, target, kv.Key, redactSecrets)
  1266  
  1267  		// Fix `vs` when default.
  1268  		if v == kv.Value {
  1269  			vs = ValueSourceDef
  1270  		}
  1271  
  1272  		if redactSecrets && isRedacted {
  1273  			// Skip adding redacted secrets to the output.
  1274  			continue
  1275  		}
  1276  
  1277  		r = append(r, KVSrc{
  1278  			Key:   kv.Key,
  1279  			Value: v,
  1280  			Src:   vs,
  1281  		})
  1282  	}
  1283  
  1284  	// Add the comment key as well if non-empty (and comments are never
  1285  	// redacted).
  1286  	v, vs, _ := c.ResolveConfigParam(subSys, target, Comment, redactSecrets)
  1287  	if vs != ValueSourceDef {
  1288  		r = append(r, KVSrc{
  1289  			Key:   Comment,
  1290  			Value: v,
  1291  			Src:   vs,
  1292  		})
  1293  	}
  1294  
  1295  	return r, nil
  1296  }
  1297  
  1298  // getTargetKVS returns configuration KVs for the given subsystem and target. It
  1299  // does not return any secrets in the configuration values when `redactSecrets`
  1300  // is set.
  1301  func (c Config) getTargetKVS(subSys, target string, redactSecrets bool) KVS {
  1302  	store, ok := c[subSys]
  1303  	if !ok {
  1304  		return nil
  1305  	}
  1306  
  1307  	// Lookup will succeed, because this function only works with valid subSys
  1308  	// values.
  1309  	resultKVS := make([]KV, 0, len(store[target]))
  1310  	hkvs := HelpSubSysMap[subSys]
  1311  	for _, kv := range store[target] {
  1312  		hkv, _ := hkvs.Lookup(kv.Key)
  1313  		if hkv.Secret && redactSecrets && kv.Value != "" {
  1314  			// Skip returning secrets.
  1315  			continue
  1316  			// clonedKV := kv
  1317  			// clonedKV.Value = redactedSecret
  1318  			// resultKVS = append(resultKVS, clonedKV)
  1319  		}
  1320  		resultKVS = append(resultKVS, kv)
  1321  	}
  1322  
  1323  	return resultKVS
  1324  }
  1325  
  1326  // getTargetEnvs returns configured environment variable settings for the given
  1327  // subsystem and target.
  1328  func (c Config) getTargetEnvs(subSys, target string, defKVS KVS, redactSecrets bool) map[string]EnvPair {
  1329  	hkvs := HelpSubSysMap[subSys]
  1330  	envMap := make(map[string]EnvPair)
  1331  
  1332  	// Add all env vars that are set.
  1333  	for _, kv := range defKVS {
  1334  		envName := getEnvVarName(subSys, target, kv.Key)
  1335  		envPair := EnvPair{
  1336  			Name:  envName,
  1337  			Value: env.Get(envName, ""),
  1338  		}
  1339  		if envPair.Value != "" {
  1340  			hkv, _ := hkvs.Lookup(kv.Key)
  1341  			if hkv.Secret && redactSecrets {
  1342  				// Skip adding any secret to the returned value.
  1343  				continue
  1344  				// envPair.Value = redactedSecret
  1345  			}
  1346  			envMap[kv.Key] = envPair
  1347  		}
  1348  	}
  1349  	return envMap
  1350  }
  1351  
  1352  // EnvPair represents an environment variable and its value.
  1353  type EnvPair struct {
  1354  	Name, Value string
  1355  }
  1356  
  1357  // SubsysInfo holds config info for a subsystem target.
  1358  type SubsysInfo struct {
  1359  	SubSys, Target string
  1360  	Defaults       KVS
  1361  	Config         KVS
  1362  
  1363  	// map of config parameter name to EnvPair.
  1364  	EnvMap map[string]EnvPair
  1365  }
  1366  
  1367  // GetSubsysInfo returns `SubsysInfo`s for all targets for the subsystem, when
  1368  // target is empty. Otherwise returns `SubsysInfo` for the desired target only.
  1369  // To request the default target only, target must be set to `Default`.
  1370  func (c Config) GetSubsysInfo(subSys, target string, redactSecrets bool) ([]SubsysInfo, error) {
  1371  	// Check if config param requested is valid.
  1372  	defKVS1, ok := DefaultKVS[subSys]
  1373  	if !ok {
  1374  		return nil, Errorf("unknown subsystem: %s", subSys)
  1375  	}
  1376  
  1377  	targets, err := c.GetAvailableTargets(subSys)
  1378  	if err != nil {
  1379  		return nil, err
  1380  	}
  1381  
  1382  	if target != "" {
  1383  		found := false
  1384  		for _, t := range targets {
  1385  			if t == target {
  1386  				found = true
  1387  				break
  1388  			}
  1389  		}
  1390  		if !found {
  1391  			return nil, Errorf("there is no target `%s` for subsystem `%s`", target, subSys)
  1392  		}
  1393  		targets = []string{target}
  1394  	}
  1395  
  1396  	// The `Comment` configuration variable is optional but is available to be
  1397  	// set for all sub-systems. It is not present in the `DefaultKVS` map's
  1398  	// values. To enable fetching a configured comment value from the
  1399  	// environment we add it to the list of default keys for the subsystem.
  1400  	defKVS := make([]KV, len(defKVS1), len(defKVS1)+1)
  1401  	copy(defKVS, defKVS1)
  1402  	defKVS = append(defKVS, KV{Key: Comment})
  1403  
  1404  	r := make([]SubsysInfo, 0, len(targets))
  1405  	for _, target := range targets {
  1406  		r = append(r, SubsysInfo{
  1407  			SubSys:   subSys,
  1408  			Target:   target,
  1409  			Defaults: defKVS,
  1410  			Config:   c.getTargetKVS(subSys, target, redactSecrets),
  1411  			EnvMap:   c.getTargetEnvs(subSys, target, defKVS, redactSecrets),
  1412  		})
  1413  	}
  1414  
  1415  	return r, nil
  1416  }
  1417  
  1418  // AddEnvString adds env vars to the given string builder.
  1419  func (cs *SubsysInfo) AddEnvString(b *strings.Builder) {
  1420  	for _, v := range cs.Defaults {
  1421  		if ep, ok := cs.EnvMap[v.Key]; ok {
  1422  			b.WriteString(KvComment)
  1423  			b.WriteString(KvSpaceSeparator)
  1424  			b.WriteString(ep.Name)
  1425  			b.WriteString(EnvSeparator)
  1426  			b.WriteString(ep.Value)
  1427  			b.WriteString(KvNewline)
  1428  		}
  1429  	}
  1430  }
  1431  
  1432  // WriteTo writes the string representation of the configuration to the given
  1433  // builder. When off is true, adds a comment character before the config system
  1434  // output. It also ignores values when empty and deprecated.
  1435  func (cs *SubsysInfo) WriteTo(b *strings.Builder, off bool) {
  1436  	cs.AddEnvString(b)
  1437  	if off {
  1438  		b.WriteString(KvComment)
  1439  		b.WriteString(KvSpaceSeparator)
  1440  	}
  1441  	b.WriteString(cs.SubSys)
  1442  	if cs.Target != Default {
  1443  		b.WriteString(SubSystemSeparator)
  1444  		b.WriteString(cs.Target)
  1445  	}
  1446  	b.WriteString(KvSpaceSeparator)
  1447  	for _, kv := range cs.Config {
  1448  		dkv, ok := cs.Defaults.LookupKV(kv.Key)
  1449  		if !ok {
  1450  			continue
  1451  		}
  1452  		// Ignore empty and deprecated values
  1453  		if dkv.HiddenIfEmpty && kv.Value == "" {
  1454  			continue
  1455  		}
  1456  		// Do not need to print if state is on
  1457  		if kv.Key == Enable && kv.Value == EnableOn {
  1458  			continue
  1459  		}
  1460  		b.WriteString(kv.String())
  1461  		b.WriteString(KvSpaceSeparator)
  1462  	}
  1463  
  1464  	b.WriteString(KvNewline)
  1465  }