...

Source file src/sigs.k8s.io/kustomize/api/kv/kv.go

Documentation: sigs.k8s.io/kustomize/api/kv

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package kv
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"strings"
    11  	"unicode"
    12  	"unicode/utf8"
    13  
    14  	"sigs.k8s.io/kustomize/api/ifc"
    15  	"sigs.k8s.io/kustomize/api/internal/generators"
    16  	"sigs.k8s.io/kustomize/api/types"
    17  	"sigs.k8s.io/kustomize/kyaml/errors"
    18  )
    19  
    20  var utf8bom = []byte{0xEF, 0xBB, 0xBF}
    21  
    22  // loader reads and validates KV pairs.
    23  type loader struct {
    24  	// Used to read the filesystem.
    25  	ldr ifc.Loader
    26  
    27  	// Used to validate various k8s data fields.
    28  	validator ifc.Validator
    29  }
    30  
    31  func NewLoader(ldr ifc.Loader, v ifc.Validator) ifc.KvLoader {
    32  	return &loader{ldr: ldr, validator: v}
    33  }
    34  
    35  func (kvl *loader) Validator() ifc.Validator {
    36  	return kvl.validator
    37  }
    38  
    39  func (kvl *loader) Load(
    40  	args types.KvPairSources) (all []types.Pair, err error) {
    41  	pairs, err := kvl.keyValuesFromEnvFiles(args.EnvSources)
    42  	if err != nil {
    43  		return nil, errors.WrapPrefixf(err,
    44  			"env source files: %v",
    45  			args.EnvSources)
    46  	}
    47  	all = append(all, pairs...)
    48  
    49  	pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
    50  	if err != nil {
    51  		return nil, errors.WrapPrefixf(err,
    52  			"literal sources %v", args.LiteralSources)
    53  	}
    54  	all = append(all, pairs...)
    55  
    56  	pairs, err = kvl.keyValuesFromFileSources(args.FileSources)
    57  	if err != nil {
    58  		return nil, errors.WrapPrefixf(err,
    59  			"file sources: %v", args.FileSources)
    60  	}
    61  	return append(all, pairs...), nil
    62  }
    63  
    64  func keyValuesFromLiteralSources(sources []string) ([]types.Pair, error) {
    65  	var kvs []types.Pair
    66  	for _, s := range sources {
    67  		k, v, err := parseLiteralSource(s)
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  		kvs = append(kvs, types.Pair{Key: k, Value: v})
    72  	}
    73  	return kvs, nil
    74  }
    75  
    76  func (kvl *loader) keyValuesFromFileSources(sources []string) ([]types.Pair, error) {
    77  	var kvs []types.Pair
    78  	for _, s := range sources {
    79  		k, fPath, err := generators.ParseFileSource(s)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		content, err := kvl.ldr.Load(fPath)
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  		kvs = append(kvs, types.Pair{Key: k, Value: string(content)})
    88  	}
    89  	return kvs, nil
    90  }
    91  
    92  func (kvl *loader) keyValuesFromEnvFiles(paths []string) ([]types.Pair, error) {
    93  	var kvs []types.Pair
    94  	for _, p := range paths {
    95  		content, err := kvl.ldr.Load(p)
    96  		if err != nil {
    97  			return nil, err
    98  		}
    99  		more, err := kvl.keyValuesFromLines(content)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  		kvs = append(kvs, more...)
   104  	}
   105  	return kvs, nil
   106  }
   107  
   108  // keyValuesFromLines parses given content in to a list of key-value pairs.
   109  func (kvl *loader) keyValuesFromLines(content []byte) ([]types.Pair, error) {
   110  	var kvs []types.Pair
   111  
   112  	scanner := bufio.NewScanner(bytes.NewReader(content))
   113  	currentLine := 0
   114  	for scanner.Scan() {
   115  		// Process the current line, retrieving a key/value pair if
   116  		// possible.
   117  		scannedBytes := scanner.Bytes()
   118  		kv, err := kvl.keyValuesFromLine(scannedBytes, currentLine)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		currentLine++
   123  
   124  		if len(kv.Key) == 0 {
   125  			// no key means line was empty or a comment
   126  			continue
   127  		}
   128  
   129  		kvs = append(kvs, kv)
   130  	}
   131  	return kvs, nil
   132  }
   133  
   134  // KeyValuesFromLine returns a kv with blank key if the line is empty or a comment.
   135  func (kvl *loader) keyValuesFromLine(line []byte, currentLine int) (types.Pair, error) {
   136  	kv := types.Pair{}
   137  
   138  	if !utf8.Valid(line) {
   139  		return kv, fmt.Errorf("line %d has invalid utf8 bytes : %v", line, string(line))
   140  	}
   141  
   142  	// We trim UTF8 BOM from the first line of the file but no others
   143  	if currentLine == 0 {
   144  		line = bytes.TrimPrefix(line, utf8bom)
   145  	}
   146  
   147  	// trim the line from all leading whitespace first
   148  	line = bytes.TrimLeftFunc(line, unicode.IsSpace)
   149  
   150  	// If the line is empty or a comment, we return a blank key/value pair.
   151  	if len(line) == 0 || line[0] == '#' {
   152  		return kv, nil
   153  	}
   154  
   155  	data := strings.SplitN(string(line), "=", 2)
   156  	key := data[0]
   157  	if err := kvl.validator.IsEnvVarName(key); err != nil {
   158  		return kv, err
   159  	}
   160  
   161  	if len(data) == 2 {
   162  		kv.Value = data[1]
   163  	} else {
   164  		// If there is no value (no `=` in the line), we set the value to an empty string
   165  		kv.Value = ""
   166  	}
   167  	kv.Key = key
   168  	return kv, nil
   169  }
   170  
   171  // ParseLiteralSource parses the source key=val pair into its component pieces.
   172  // This functionality is distinguished from strings.SplitN(source, "=", 2) since
   173  // it returns an error in the case of empty keys, values, or a missing equals sign.
   174  func parseLiteralSource(source string) (keyName, value string, err error) {
   175  	// leading equal is invalid
   176  	if strings.Index(source, "=") == 0 {
   177  		return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
   178  	}
   179  	// split after the first equal (so values can have the = character)
   180  	items := strings.SplitN(source, "=", 2)
   181  	if len(items) != 2 {
   182  		return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
   183  	}
   184  	return items[0], removeQuotes(items[1]), nil
   185  }
   186  
   187  // removeQuotes removes the surrounding quotes from the provided string only if it is surrounded on both sides
   188  // rather than blindly trimming all quotation marks on either side.
   189  func removeQuotes(str string) string {
   190  	if len(str) < 2 || str[0] != str[len(str)-1] {
   191  		return str
   192  	}
   193  	if str[0] == '"' || str[0] == '\'' {
   194  		return str[1 : len(str)-1]
   195  	}
   196  	return str
   197  }
   198  

View as plain text