github.com/Axway/agent-sdk@v1.1.101/pkg/cmd/properties/properties.go (about)

     1  package properties
     2  
     3  import (
     4  	"encoding/json"
     5  	goflag "flag"
     6  	"fmt"
     7  	"os"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/Axway/agent-sdk/pkg/util"
    14  	"github.com/Axway/agent-sdk/pkg/util/errors"
    15  	"github.com/Axway/agent-sdk/pkg/util/log"
    16  	"github.com/spf13/cobra"
    17  	flag "github.com/spf13/pflag"
    18  	"github.com/spf13/viper"
    19  )
    20  
    21  // ErrInvalidSecretReference - Error for parsing properties with secret reference
    22  var ErrInvalidSecretReference = errors.Newf(1411, "invalid secret reference - %s, please check the value for %s config")
    23  
    24  // QA EnvVars
    25  const qaEnforceDurationLowerLimit = "QA_ENFORCE_DURATION_LOWER_LIMIT"
    26  
    27  const (
    28  	lowerLimitName  = "%s-lowerLimit"
    29  	upperLimitName  = "%s-upperLimit"
    30  	qaVarNameFormat = "qa.%s"
    31  )
    32  
    33  // SecretPropertyResolver - interface for resolving property values with secret references
    34  type SecretPropertyResolver interface {
    35  	ResolveSecret(secretRef string) (string, error)
    36  }
    37  
    38  // Properties - Root Command Properties interface for all configs to use for adding and parsing values
    39  type Properties interface {
    40  	// Methods for adding yaml properties and command flag
    41  	AddStringProperty(name string, defaultVal string, description string)
    42  	AddStringPersistentFlag(name string, defaultVal string, description string)
    43  	AddStringFlag(name string, description string)
    44  	AddDurationProperty(name string, defaultVal time.Duration, description string, options ...DurationOpt)
    45  	AddIntProperty(name string, defaultVal int, description string, options ...IntOpt)
    46  	AddBoolProperty(name string, defaultVal bool, description string)
    47  	AddBoolFlag(name, description string)
    48  	AddStringSliceProperty(name string, defaultVal []string, description string)
    49  	AddObjectSliceProperty(name string, objectPropertyNames []string)
    50  
    51  	// Methods to get the configured properties
    52  	StringPropertyValue(name string) string
    53  	StringFlagValue(name string) (bool, string)
    54  	DurationPropertyValue(name string) time.Duration
    55  	IntPropertyValue(name string) int
    56  	BoolPropertyValue(name string) bool
    57  	BoolPropertyValueOrTrue(name string) bool // Use this method when the default value, no config given, is true
    58  	BoolFlagValue(name string) bool
    59  	StringSlicePropertyValue(name string) []string
    60  	ObjectSlicePropertyValue(name string) []map[string]interface{}
    61  
    62  	// Methods to set a property
    63  	SetStringFlagValue(name string, value string)
    64  
    65  	// Log Properties
    66  	MaskValues(name string)
    67  	DebugLogProperties()
    68  	SetAliasKeyPrefix(aliasKeyPrefix string)
    69  }
    70  
    71  type durationOpts struct {
    72  	lower      time.Duration
    73  	upper      time.Duration
    74  	qaOverride bool
    75  }
    76  
    77  // DurationOpt are duration range options passed into AddDurationProperty
    78  type DurationOpt func(prop *durationOpts)
    79  
    80  // WithLowerLimit - lower limit of the duration range
    81  func WithLowerLimit(lower time.Duration) DurationOpt {
    82  	return func(d *durationOpts) {
    83  		d.lower = lower
    84  	}
    85  }
    86  
    87  // WithUpperLimit - upper limit of the duration range
    88  func WithUpperLimit(upper time.Duration) DurationOpt {
    89  	return func(d *durationOpts) {
    90  		d.upper = upper
    91  	}
    92  }
    93  
    94  // WithQAOverride - set to true to allow this setting to be overwritten with a qa env var
    95  func WithQAOverride() DurationOpt {
    96  	return func(d *durationOpts) {
    97  		d.qaOverride = true
    98  	}
    99  }
   100  
   101  type intOpts struct {
   102  	lower int
   103  	upper int
   104  }
   105  
   106  // DurationOpt are duration range options passed into AddDurationProperty
   107  type IntOpt func(prop *intOpts)
   108  
   109  // WithLowerLimitInt - lower limit of the int range
   110  func WithLowerLimitInt(lower int) IntOpt {
   111  	return func(d *intOpts) {
   112  		d.lower = lower
   113  	}
   114  }
   115  
   116  // WithUpperLimitInt - upper limit of the int range
   117  func WithUpperLimitInt(upper int) IntOpt {
   118  	return func(d *intOpts) {
   119  		d.upper = upper
   120  	}
   121  }
   122  
   123  var aliasKeyPrefix string
   124  
   125  type properties struct {
   126  	Properties
   127  	rootCmd                  *cobra.Command
   128  	envIntfArrayPropValues   map[string][]map[string]interface{}
   129  	envIntfArrayPropertyKeys map[string]map[string]bool
   130  	secretResolver           SecretPropertyResolver
   131  	flattenedProperties      map[string]string
   132  }
   133  
   134  var expansionRegEx *regexp.Regexp
   135  
   136  func init() {
   137  	expansionRegEx = regexp.MustCompile(`\$\{(\w+):(.*)\}|\$\{(\w+)\}`)
   138  }
   139  
   140  // NewProperties - Creates a new Properties struct
   141  func NewProperties(rootCmd *cobra.Command) Properties {
   142  	cmdprops := &properties{
   143  		rootCmd:                  rootCmd,
   144  		envIntfArrayPropertyKeys: make(map[string]map[string]bool),
   145  		flattenedProperties:      make(map[string]string),
   146  	}
   147  
   148  	return cmdprops
   149  }
   150  
   151  // NewPropertiesWithSecretResolver - Creates a new Properties struct with secret resolver for string property/flag
   152  func NewPropertiesWithSecretResolver(rootCmd *cobra.Command, secretResolver SecretPropertyResolver) Properties {
   153  	cmdprops := &properties{
   154  		rootCmd:                  rootCmd,
   155  		envIntfArrayPropertyKeys: make(map[string]map[string]bool),
   156  		flattenedProperties:      make(map[string]string),
   157  		secretResolver:           secretResolver,
   158  	}
   159  
   160  	return cmdprops
   161  }
   162  
   163  // SetAliasKeyPrefix -
   164  func SetAliasKeyPrefix(keyPrefix string) {
   165  	aliasKeyPrefix = keyPrefix
   166  }
   167  
   168  // GetAliasKeyPrefix -
   169  func GetAliasKeyPrefix() string {
   170  	return aliasKeyPrefix
   171  }
   172  
   173  func (p *properties) bindOrPanic(key string, flg *flag.Flag) {
   174  	if err := viper.BindPFlag(key, flg); err != nil {
   175  		panic(err)
   176  	}
   177  	if aliasKeyPrefix != "" {
   178  		if err := viper.BindPFlag(aliasKeyPrefix+"."+key, flg); err != nil {
   179  			panic(err)
   180  		}
   181  	}
   182  }
   183  
   184  func (p *properties) AddObjectSliceProperty(envPrefix string, intfPropertyNames []string) {
   185  	envPrefix = strings.ReplaceAll(envPrefix, ".", "_")
   186  	envPrefix = strings.ToUpper(envPrefix)
   187  	if !strings.HasSuffix(envPrefix, "_") {
   188  		envPrefix += "_"
   189  	}
   190  	iPropNames := make(map[string]bool)
   191  	for _, propName := range intfPropertyNames {
   192  		iPropNames[propName] = true
   193  	}
   194  
   195  	p.envIntfArrayPropertyKeys[envPrefix] = iPropNames
   196  }
   197  
   198  func (p *properties) AddStringProperty(name string, defaultVal string, description string) {
   199  	if p.rootCmd != nil {
   200  		flagName := p.nameToFlagName(name)
   201  		p.rootCmd.Flags().String(flagName, defaultVal, description)
   202  		p.bindOrPanic(name, p.rootCmd.Flags().Lookup(flagName))
   203  		p.rootCmd.Flags().MarkHidden(flagName)
   204  	}
   205  }
   206  
   207  func (p *properties) AddStringPersistentFlag(flagName string, defaultVal string, description string) {
   208  	if p.rootCmd != nil {
   209  		flg := goflag.CommandLine.Lookup(flagName)
   210  		if flg == nil {
   211  			goflag.CommandLine.String(flagName, "", description)
   212  			flg = goflag.CommandLine.Lookup(flagName)
   213  		}
   214  
   215  		p.rootCmd.PersistentFlags().AddGoFlag(flg)
   216  	}
   217  }
   218  
   219  func (p *properties) AddStringFlag(flagName string, description string) {
   220  	if p.rootCmd != nil {
   221  		p.rootCmd.Flags().String(flagName, "", description)
   222  	}
   223  }
   224  
   225  func (p *properties) SetStringFlagValue(flagName string, value string) {
   226  	if p.rootCmd != nil {
   227  		p.rootCmd.Flags().Set(flagName, value)
   228  	}
   229  }
   230  
   231  func (p *properties) AddStringSliceProperty(name string, defaultVal []string, description string) {
   232  	if p.rootCmd != nil {
   233  		flagName := p.nameToFlagName(name)
   234  		p.rootCmd.Flags().StringSlice(flagName, defaultVal, description)
   235  		p.bindOrPanic(name, p.rootCmd.Flags().Lookup(flagName))
   236  		p.rootCmd.Flags().MarkHidden(flagName)
   237  	}
   238  }
   239  
   240  func (p *properties) AddDurationProperty(name string, defaultVal time.Duration, description string, options ...DurationOpt) {
   241  	if p.rootCmd != nil {
   242  		flagName := p.nameToFlagName(name)
   243  
   244  		opts := &durationOpts{
   245  			lower: time.Second * 30,
   246  		}
   247  
   248  		// validate if WithLowerLimit and WithUpperLimit were called
   249  		for _, option := range options {
   250  			option(opts)
   251  		}
   252  
   253  		p.configureUpperAndLowerLimits(defaultVal, opts, flagName)
   254  
   255  		if opts.qaOverride {
   256  			qaName := fmt.Sprintf(qaVarNameFormat, name)
   257  			qaFlagName := p.nameToFlagName(qaName)
   258  			p.rootCmd.Flags().Duration(qaFlagName, -1*time.Second, "")
   259  			p.bindOrPanic(qaName, p.rootCmd.Flags().Lookup(qaFlagName))
   260  			p.rootCmd.Flags().MarkHidden(qaFlagName)
   261  		}
   262  
   263  		p.rootCmd.Flags().Duration(flagName, defaultVal, description)
   264  		p.bindOrPanic(name, p.rootCmd.Flags().Lookup(flagName))
   265  		p.rootCmd.Flags().MarkHidden(flagName)
   266  	}
   267  }
   268  
   269  func (p *properties) configureUpperAndLowerLimitsInt(defaultVal int, limits *intOpts, flagName string) {
   270  	lowerLimitFlag := fmt.Sprintf(lowerLimitName, flagName)
   271  	upperLimitFlag := fmt.Sprintf(upperLimitName, flagName)
   272  
   273  	// set lower limit
   274  	if limits.lower > -1 {
   275  		if defaultVal < limits.lower {
   276  			panic(fmt.Errorf("default value (%v) can not be smaller than lower limit (%v) for %s", defaultVal, limits.lower, flagName))
   277  		}
   278  		p.rootCmd.Flags().Int(lowerLimitFlag, limits.lower, fmt.Sprintf("lower limit flag for configuration %s", flagName))
   279  		p.rootCmd.Flags().MarkHidden(lowerLimitFlag)
   280  	}
   281  
   282  	// set upper limit if greater than zero
   283  	if limits.upper > -1 {
   284  		p.rootCmd.Flags().Int(upperLimitFlag, limits.upper, fmt.Sprintf("upper limit flag for configuration %s", flagName))
   285  		p.rootCmd.Flags().MarkHidden(upperLimitFlag)
   286  		// check for valid upper and lower limits
   287  		if limits.upper < limits.lower {
   288  			panic(fmt.Errorf("upper limit (%v) can not be smaller than lower limit (%v) for %s", limits.upper, limits.lower, flagName))
   289  		}
   290  		if defaultVal > limits.upper {
   291  			panic(fmt.Errorf("default value (%v) can not be larger than upper limit (%v) for %s", defaultVal, limits.upper, flagName))
   292  		}
   293  	}
   294  }
   295  
   296  func (p *properties) configureUpperAndLowerLimits(defaultVal time.Duration, limits *durationOpts, flagName string) {
   297  	lowerLimitFlag := fmt.Sprintf(lowerLimitName, flagName)
   298  	upperLimitFlag := fmt.Sprintf(upperLimitName, flagName)
   299  
   300  	// set lower limit
   301  	if defaultVal < limits.lower {
   302  		panic(fmt.Errorf("default value (%s) can not be smaller than lower limit (%s) for %s", defaultVal, limits.lower, flagName))
   303  	}
   304  	p.rootCmd.Flags().Duration(lowerLimitFlag, limits.lower, fmt.Sprintf("lower limit flag for configuration %s", flagName))
   305  	p.rootCmd.Flags().MarkHidden(lowerLimitFlag)
   306  
   307  	// set upper limit if greater than zero
   308  	if limits.upper > 0 {
   309  		p.rootCmd.Flags().Duration(upperLimitFlag, limits.upper, fmt.Sprintf("upper limit flag for configuration %s", flagName))
   310  		p.rootCmd.Flags().MarkHidden(upperLimitFlag)
   311  		// check for valid upper and lower limits
   312  		if limits.upper < limits.lower {
   313  			panic(fmt.Errorf("upper limit (%v) can not be smaller than lower limit (%v) for %s", limits.upper, limits.lower, flagName))
   314  		}
   315  		if defaultVal > limits.upper {
   316  			panic(fmt.Errorf("default value (%v) can not be larger than upper limit (%v) for %s", defaultVal, limits.upper, flagName))
   317  		}
   318  	}
   319  }
   320  
   321  func (p *properties) AddIntProperty(name string, defaultVal int, description string, options ...IntOpt) {
   322  	if p.rootCmd != nil {
   323  		flagName := p.nameToFlagName(name)
   324  
   325  		opts := &intOpts{
   326  			lower: -1,
   327  			upper: -1,
   328  		}
   329  
   330  		// validate if WithLowerLimit and WithUpperLimit were called
   331  		for _, option := range options {
   332  			option(opts)
   333  		}
   334  
   335  		p.configureUpperAndLowerLimitsInt(defaultVal, opts, flagName)
   336  
   337  		p.rootCmd.Flags().Int(flagName, defaultVal, description)
   338  		p.bindOrPanic(name, p.rootCmd.Flags().Lookup(flagName))
   339  		p.rootCmd.Flags().MarkHidden(flagName)
   340  	}
   341  }
   342  
   343  func (p *properties) AddBoolProperty(name string, defaultVal bool, description string) {
   344  	if p.rootCmd != nil {
   345  		flagName := p.nameToFlagName(name)
   346  		p.rootCmd.Flags().Bool(flagName, defaultVal, description)
   347  		p.bindOrPanic(name, p.rootCmd.Flags().Lookup(flagName))
   348  		p.rootCmd.Flags().MarkHidden(flagName)
   349  	}
   350  }
   351  
   352  func (p *properties) AddBoolFlag(flagName string, description string) {
   353  	if p.rootCmd != nil {
   354  		p.rootCmd.Flags().Bool(flagName, false, description)
   355  	}
   356  }
   357  
   358  func (p *properties) StringSlicePropertyValue(name string) []string {
   359  	val := viper.Get(name)
   360  
   361  	// special check to differentiate between yaml and commandline parsing. For commandline, must
   362  	// turn it into an array ourselves
   363  	switch val := val.(type) {
   364  	case string:
   365  		p.addPropertyToFlatMap(name, val)
   366  		return p.convertStringToSlice(fmt.Sprintf("%v", viper.Get(name)))
   367  	default:
   368  		return viper.GetStringSlice(name)
   369  	}
   370  }
   371  
   372  func (p *properties) convertStringToSlice(value string) []string {
   373  	slc := strings.Split(value, ",")
   374  	for i := range slc {
   375  		slc[i] = strings.TrimSpace(slc[i])
   376  	}
   377  	return slc
   378  }
   379  
   380  func (p *properties) parseStringValueForKey(key string) string {
   381  	s := strings.TrimSpace(viper.GetString(key))
   382  	if strings.Index(s, "$") == 0 {
   383  		matches := expansionRegEx.FindAllSubmatch([]byte(s), -1)
   384  		if len(matches) > 0 {
   385  			expSlice := matches[0]
   386  			if len(expSlice) > 2 {
   387  				s = p.parseSlice(s, expSlice)
   388  			}
   389  		}
   390  	}
   391  	return s
   392  }
   393  
   394  func (p *properties) parseSlice(s string, expSlice [][]byte) string {
   395  	rtnS := s
   396  	envVar := string(expSlice[1])
   397  	defaultVal := ""
   398  	if envVar == "" {
   399  		if len(expSlice) >= 4 {
   400  			envVar = strings.Trim(string(expSlice[3]), "\"")
   401  		}
   402  	} else {
   403  		if len(expSlice) >= 3 {
   404  			defaultVal = strings.Trim(string(expSlice[2]), "\"")
   405  		}
   406  	}
   407  
   408  	if envVar != "" {
   409  		rtnS = os.Getenv(envVar)
   410  		if rtnS == "" && defaultVal != "" {
   411  			rtnS = defaultVal
   412  		}
   413  	}
   414  
   415  	return rtnS
   416  }
   417  
   418  func (p *properties) parseStringValue(key string) string {
   419  	var s string
   420  	if aliasKeyPrefix != "" {
   421  		s = p.parseStringValueForKey(aliasKeyPrefix + "." + key)
   422  	}
   423  	// If no alias or no value parsed for alias key
   424  	if s == "" {
   425  		s = p.parseStringValueForKey(key)
   426  	}
   427  	return s
   428  }
   429  
   430  func (p *properties) resolveSecretReference(cfgName, cfgValue string) string {
   431  	if p.secretResolver != nil {
   432  		secretValue, err := p.secretResolver.ResolveSecret(cfgValue)
   433  		if err != nil {
   434  			// only log the error and return empty string,
   435  			// validation on config triggers the agent to return error to root command
   436  			log.Error(ErrInvalidSecretReference.FormatError(err.Error(), cfgName))
   437  			cfgValue = ""
   438  		}
   439  		if secretValue != "" {
   440  			cfgValue = secretValue
   441  		}
   442  	}
   443  	return cfgValue
   444  }
   445  
   446  func (p *properties) StringPropertyValue(name string) string {
   447  	s := p.parseStringValue(name)
   448  	s = p.resolveSecretReference(name, s)
   449  	p.addPropertyToFlatMap(name, s)
   450  	return s
   451  }
   452  
   453  func (p *properties) StringFlagValue(name string) (bool, string) {
   454  	flag := p.rootCmd.Flag(name)
   455  	if flag == nil || flag.Value.String() == "" {
   456  		return false, ""
   457  	}
   458  	fv := flag.Value.String()
   459  	fv = p.resolveSecretReference(name, fv)
   460  	p.addPropertyToFlatMap(name, fv)
   461  	return true, fv
   462  }
   463  
   464  func (p *properties) DurationPropertyValue(name string) time.Duration {
   465  	s := p.parseStringValue(name)
   466  	d, _ := time.ParseDuration(s)
   467  
   468  	// check if the duration has a qa equivalent that should be used
   469  	if qaVal := p.getQADuration(name); qaVal > 0 {
   470  		return qaVal
   471  	}
   472  
   473  	if !isDurationLowerLimitEnforced() {
   474  		return d
   475  	}
   476  
   477  	flagName := p.nameToFlagName(name)
   478  	flag := p.rootCmd.Flag(flagName)
   479  	lowerLimit, upperLimit := p.getDurationLimits(flagName)
   480  	defaultVal, _ := time.ParseDuration(flag.DefValue)
   481  
   482  	if lowerLimit > 0 && d < lowerLimit {
   483  		d = defaultVal
   484  		log.Warnf("Configuration %s has been set to the default value of %s. Please update this value greater than the lower limit of %s", name, d, lowerLimit)
   485  	} else if upperLimit > 0 && d > upperLimit {
   486  		d = defaultVal
   487  		log.Warnf("Configuration %s has been set to the default value of %s. Please update this value lower than the upper limit of %s", name, d, upperLimit)
   488  	}
   489  
   490  	p.addPropertyToFlatMap(name, s)
   491  	return d
   492  }
   493  
   494  // getQADuration - returns the qa variables duration
   495  func (p *properties) getQADuration(name string) time.Duration {
   496  	qaName := fmt.Sprintf(qaVarNameFormat, name)
   497  	qaVal := -1 * time.Second
   498  	if s := p.parseStringValue(qaName); s != "" {
   499  		qaVal, _ = time.ParseDuration(s)
   500  	}
   501  
   502  	return qaVal
   503  }
   504  
   505  // getDurationLimits - returns the duration limits, negative number returned for unset
   506  func (p *properties) getDurationLimits(flagName string) (time.Duration, time.Duration) {
   507  	lowerLimitFlag := p.rootCmd.Flag(fmt.Sprintf(lowerLimitName, flagName))
   508  	upperLimitFlag := p.rootCmd.Flag(fmt.Sprintf(upperLimitName, flagName))
   509  
   510  	lowerLimit := -1 * time.Second
   511  	upperLimit := -1 * time.Second
   512  	if lowerLimitFlag != nil {
   513  		lowerLimit, _ = time.ParseDuration(lowerLimitFlag.Value.String())
   514  	}
   515  	if upperLimitFlag != nil {
   516  		upperLimit, _ = time.ParseDuration(upperLimitFlag.Value.String())
   517  	}
   518  
   519  	return lowerLimit, upperLimit
   520  }
   521  
   522  func (p *properties) IntPropertyValue(name string) int {
   523  	s := p.parseStringValue(name)
   524  	i, _ := strconv.Atoi(s)
   525  
   526  	p.addPropertyToFlatMap(name, s)
   527  	return i
   528  }
   529  
   530  func (p *properties) BoolPropertyValue(name string) bool {
   531  	return p.boolPropertyValue(name, false)
   532  }
   533  
   534  func (p *properties) BoolPropertyValueOrTrue(name string) bool {
   535  	return p.boolPropertyValue(name, true)
   536  }
   537  
   538  func (p *properties) boolPropertyValue(name string, defVal bool) bool {
   539  	s := p.parseStringValue(name)
   540  	if s == "" {
   541  		return defVal
   542  	}
   543  	b, _ := strconv.ParseBool(s)
   544  
   545  	p.addPropertyToFlatMap(name, s)
   546  	return b
   547  }
   548  
   549  func (p *properties) BoolFlagValue(name string) bool {
   550  	flag := p.rootCmd.Flag(name)
   551  	if flag == nil {
   552  		return false
   553  	}
   554  	if flag.Value.String() == "true" {
   555  		return true
   556  	}
   557  	return false
   558  }
   559  
   560  func (p *properties) nameToFlagName(name string) (flagName string) {
   561  	parts := strings.Split(name, ".")
   562  	flagName = parts[0]
   563  	for _, part := range parts[1:] {
   564  		flagName += strings.Title(part)
   565  	}
   566  	return
   567  }
   568  
   569  // String array containing any sensitive data that needs to be masked with "*" (asterisks)
   570  // Add any sensitive data here using flattened key format
   571  var maskValues = make([]string, 0)
   572  
   573  func (p *properties) addPropertyToFlatMap(key, value string) {
   574  	for _, maskValue := range maskValues {
   575  		match, _ := regexp.MatchString("\\b"+strings.TrimSpace(maskValue)+"\\b", key)
   576  		if match {
   577  			value = util.MaskValue(value)
   578  		}
   579  	}
   580  	p.flattenedProperties[key] = value
   581  }
   582  
   583  func (p *properties) MaskValues(maskedKeys string) {
   584  	maskValues = strings.Split(maskedKeys, ",")
   585  }
   586  
   587  func (p *properties) DebugLogProperties() {
   588  	data, _ := json.MarshalIndent(p.flattenedProperties, "", " ")
   589  	log.Debugf("%s\n", data)
   590  }
   591  
   592  // enforce the lower limit by default
   593  func isDurationLowerLimitEnforced() bool {
   594  	if val := os.Getenv(qaEnforceDurationLowerLimit); val != "" {
   595  		ret, err := strconv.ParseBool(val)
   596  		if err != nil {
   597  			log.Errorf("Invalid value %s for env variable %s", val, qaEnforceDurationLowerLimit)
   598  			return true
   599  		}
   600  		return ret
   601  	}
   602  	return true
   603  }
   604  
   605  // ObjectSlicePropertyValue
   606  func (p *properties) ObjectSlicePropertyValue(name string) []map[string]interface{} {
   607  	p.readEnv()
   608  	name = strings.ReplaceAll(name, ".", "_")
   609  	name = strings.ToUpper(name)
   610  	if !strings.HasSuffix(name, "_") {
   611  		name += "_"
   612  	}
   613  	return p.envIntfArrayPropValues[name]
   614  }
   615  
   616  func (p *properties) readEnv() {
   617  	if p.envIntfArrayPropValues != nil {
   618  		return
   619  	}
   620  
   621  	p.envIntfArrayPropValues = make(map[string][]map[string]interface{})
   622  	envVarsMap := p.parseEnvPropertiesFlatMap()
   623  
   624  	for prefix, eVals := range envVarsMap {
   625  		propValues, ok := p.envIntfArrayPropValues[prefix]
   626  		if !ok {
   627  			propValues = make([]map[string]interface{}, 0)
   628  		}
   629  
   630  		for _, val := range eVals {
   631  			v := p.convertFlatMap(val)
   632  			propValues = append(propValues, v)
   633  		}
   634  		p.envIntfArrayPropValues[prefix] = propValues
   635  	}
   636  }
   637  
   638  func (p *properties) convertFlatMap(flatMap map[string]string) map[string]interface{} {
   639  	m := make(map[string]interface{})
   640  	for key, val := range flatMap {
   641  		ok := strings.Contains(key, ".")
   642  		if !ok {
   643  			m[key] = val
   644  		} else {
   645  			k := strings.Split(key, ".")
   646  			p.setChildMapProperty(m, k, val)
   647  		}
   648  	}
   649  	return m
   650  }
   651  
   652  func (p *properties) setChildMapProperty(parentMap map[string]interface{}, childKeys []string, val string) {
   653  	cm, ok := parentMap[childKeys[0]]
   654  	if !ok {
   655  		cm = make(map[string]interface{})
   656  	}
   657  
   658  	childMap, ok := cm.(map[string]interface{})
   659  	if ok {
   660  		if len(childKeys) > 2 {
   661  			p.setChildMapProperty(childMap, childKeys[1:], val)
   662  		} else {
   663  			childMap[childKeys[1]] = val
   664  		}
   665  		parentMap[childKeys[0]] = cm
   666  	}
   667  
   668  }
   669  
   670  func (p *properties) parseEnvPropertiesFlatMap() map[string]map[string]map[string]string {
   671  	envVarsMap := make(map[string]map[string]map[string]string)
   672  	for _, element := range os.Environ() {
   673  		variable := strings.SplitN(element, "=", 2)
   674  		name := variable[0]
   675  		val := variable[1]
   676  		for prefix, iPropNames := range p.envIntfArrayPropertyKeys {
   677  			p.fillEnvVarsMap(name, val, prefix, iPropNames, envVarsMap)
   678  		}
   679  	}
   680  	return envVarsMap
   681  }
   682  
   683  func (p *properties) fillEnvVarsMap(name string, val string, prefix string, iPropNames map[string]bool, envVarsMap map[string]map[string]map[string]string) {
   684  	if strings.HasPrefix(name, prefix) {
   685  		n := strings.ReplaceAll(name, prefix, "")
   686  		elements := strings.Split(name, "_")
   687  		lastSuffix := elements[len(elements)-1]
   688  		_, ok := envVarsMap[prefix]
   689  
   690  		if !ok {
   691  			envVarsMap[prefix] = make(map[string]map[string]string)
   692  		}
   693  
   694  		m, ok := envVarsMap[prefix][lastSuffix]
   695  		if !ok {
   696  			m = make(map[string]string)
   697  		}
   698  		for pName := range iPropNames {
   699  			propName := strings.ReplaceAll(pName, ".", "_")
   700  			propName = strings.ToUpper(propName)
   701  			if strings.HasPrefix(n, propName) {
   702  				m[pName] = val
   703  				envVarsMap[prefix][lastSuffix] = m
   704  			}
   705  		}
   706  	}
   707  }