github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/cli/values/options.go (about)

     1  /*
     2  Copyright The Helm Authors.
     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  package values
    18  
    19  import (
    20  	"io/ioutil"
    21  	"net/url"
    22  	"os"
    23  	"strings"
    24  
    25  	"github.com/pkg/errors"
    26  	"sigs.k8s.io/yaml"
    27  
    28  	"github.com/stefanmcshane/helm/pkg/getter"
    29  	"github.com/stefanmcshane/helm/pkg/strvals"
    30  )
    31  
    32  type Options struct {
    33  	ValueFiles   []string
    34  	StringValues []string
    35  	Values       []string
    36  	FileValues   []string
    37  	JSONValues   []string
    38  }
    39  
    40  // MergeValues merges values from files specified via -f/--values and directly
    41  // via --set, --set-string, or --set-file, marshaling them to YAML
    42  func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) {
    43  	base := map[string]interface{}{}
    44  
    45  	// User specified a values files via -f/--values
    46  	for _, filePath := range opts.ValueFiles {
    47  		currentMap := map[string]interface{}{}
    48  
    49  		bytes, err := readFile(filePath, p)
    50  		if err != nil {
    51  			return nil, err
    52  		}
    53  
    54  		if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
    55  			return nil, errors.Wrapf(err, "failed to parse %s", filePath)
    56  		}
    57  		// Merge with the previous map
    58  		base = mergeMaps(base, currentMap)
    59  	}
    60  
    61  	// User specified a value via --set-json
    62  	for _, value := range opts.JSONValues {
    63  		if err := strvals.ParseJSON(value, base); err != nil {
    64  			return nil, errors.Errorf("failed parsing --set-json data %s", value)
    65  		}
    66  	}
    67  
    68  	// User specified a value via --set
    69  	for _, value := range opts.Values {
    70  		if err := strvals.ParseInto(value, base); err != nil {
    71  			return nil, errors.Wrap(err, "failed parsing --set data")
    72  		}
    73  	}
    74  
    75  	// User specified a value via --set-string
    76  	for _, value := range opts.StringValues {
    77  		if err := strvals.ParseIntoString(value, base); err != nil {
    78  			return nil, errors.Wrap(err, "failed parsing --set-string data")
    79  		}
    80  	}
    81  
    82  	// User specified a value via --set-file
    83  	for _, value := range opts.FileValues {
    84  		reader := func(rs []rune) (interface{}, error) {
    85  			bytes, err := readFile(string(rs), p)
    86  			if err != nil {
    87  				return nil, err
    88  			}
    89  			return string(bytes), err
    90  		}
    91  		if err := strvals.ParseIntoFile(value, base, reader); err != nil {
    92  			return nil, errors.Wrap(err, "failed parsing --set-file data")
    93  		}
    94  	}
    95  
    96  	return base, nil
    97  }
    98  
    99  func mergeMaps(a, b map[string]interface{}) map[string]interface{} {
   100  	out := make(map[string]interface{}, len(a))
   101  	for k, v := range a {
   102  		out[k] = v
   103  	}
   104  	for k, v := range b {
   105  		if v, ok := v.(map[string]interface{}); ok {
   106  			if bv, ok := out[k]; ok {
   107  				if bv, ok := bv.(map[string]interface{}); ok {
   108  					out[k] = mergeMaps(bv, v)
   109  					continue
   110  				}
   111  			}
   112  		}
   113  		out[k] = v
   114  	}
   115  	return out
   116  }
   117  
   118  // readFile load a file from stdin, the local directory, or a remote file with a url.
   119  func readFile(filePath string, p getter.Providers) ([]byte, error) {
   120  	if strings.TrimSpace(filePath) == "-" {
   121  		return ioutil.ReadAll(os.Stdin)
   122  	}
   123  	u, err := url.Parse(filePath)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	// FIXME: maybe someone handle other protocols like ftp.
   129  	g, err := p.ByScheme(u.Scheme)
   130  	if err != nil {
   131  		return ioutil.ReadFile(filePath)
   132  	}
   133  	data, err := g.Get(filePath, getter.WithURL(filePath))
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return data.Bytes(), err
   138  }