go-micro.dev/v5@v5.12.0/config/source/env/env.go (about)

     1  package env
     2  
     3  import (
     4  	"os"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  
     9  	"dario.cat/mergo"
    10  	"go-micro.dev/v5/config/source"
    11  )
    12  
    13  var (
    14  	DefaultPrefixes = []string{}
    15  )
    16  
    17  type env struct {
    18  	opts             source.Options
    19  	prefixes         []string
    20  	strippedPrefixes []string
    21  }
    22  
    23  func (e *env) Read() (*source.ChangeSet, error) {
    24  	var changes map[string]interface{}
    25  
    26  	for _, env := range os.Environ() {
    27  		if len(e.prefixes) > 0 || len(e.strippedPrefixes) > 0 {
    28  			notFound := true
    29  
    30  			if _, ok := matchPrefix(e.prefixes, env); ok {
    31  				notFound = false
    32  			}
    33  
    34  			if match, ok := matchPrefix(e.strippedPrefixes, env); ok {
    35  				env = strings.TrimPrefix(env, match)
    36  				notFound = false
    37  			}
    38  
    39  			if notFound {
    40  				continue
    41  			}
    42  		}
    43  
    44  		pair := strings.SplitN(env, "=", 2)
    45  		value := pair[1]
    46  		keys := strings.Split(strings.ToLower(pair[0]), "_")
    47  		reverse(keys)
    48  
    49  		tmp := make(map[string]interface{})
    50  		for i, k := range keys {
    51  			if i == 0 {
    52  				if intValue, err := strconv.Atoi(value); err == nil {
    53  					tmp[k] = intValue
    54  				} else if boolValue, err := strconv.ParseBool(value); err == nil {
    55  					tmp[k] = boolValue
    56  				} else {
    57  					tmp[k] = value
    58  				}
    59  				continue
    60  			}
    61  
    62  			tmp = map[string]interface{}{k: tmp}
    63  		}
    64  
    65  		if err := mergo.Map(&changes, tmp); err != nil {
    66  			return nil, err
    67  		}
    68  	}
    69  
    70  	b, err := e.opts.Encoder.Encode(changes)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	cs := &source.ChangeSet{
    76  		Format:    e.opts.Encoder.String(),
    77  		Data:      b,
    78  		Timestamp: time.Now(),
    79  		Source:    e.String(),
    80  	}
    81  	cs.Checksum = cs.Sum()
    82  
    83  	return cs, nil
    84  }
    85  
    86  func matchPrefix(pre []string, s string) (string, bool) {
    87  	for _, p := range pre {
    88  		if strings.HasPrefix(s, p) {
    89  			return p, true
    90  		}
    91  	}
    92  
    93  	return "", false
    94  }
    95  
    96  func reverse(ss []string) {
    97  	for i := len(ss)/2 - 1; i >= 0; i-- {
    98  		opp := len(ss) - 1 - i
    99  		ss[i], ss[opp] = ss[opp], ss[i]
   100  	}
   101  }
   102  
   103  func (e *env) Watch() (source.Watcher, error) {
   104  	return newWatcher()
   105  }
   106  
   107  func (e *env) Write(cs *source.ChangeSet) error {
   108  	return nil
   109  }
   110  
   111  func (e *env) String() string {
   112  	return "env"
   113  }
   114  
   115  // NewSource returns a config source for parsing ENV variables.
   116  // Underscores are delimiters for nesting, and all keys are lowercased.
   117  //
   118  // Example:
   119  //
   120  //	"DATABASE_SERVER_HOST=localhost" will convert to
   121  //
   122  //	{
   123  //	    "database": {
   124  //	        "server": {
   125  //	            "host": "localhost"
   126  //	        }
   127  //	    }
   128  //	}
   129  func NewSource(opts ...source.Option) source.Source {
   130  	options := source.NewOptions(opts...)
   131  
   132  	var sp []string
   133  	var pre []string
   134  	if p, ok := options.Context.Value(strippedPrefixKey{}).([]string); ok {
   135  		sp = p
   136  	}
   137  
   138  	if p, ok := options.Context.Value(prefixKey{}).([]string); ok {
   139  		pre = p
   140  	}
   141  
   142  	if len(sp) > 0 || len(pre) > 0 {
   143  		pre = append(pre, DefaultPrefixes...)
   144  	}
   145  	return &env{prefixes: pre, strippedPrefixes: sp, opts: options}
   146  }