...

Source file src/helm.sh/helm/v3/pkg/cli/values/options.go

Documentation: helm.sh/helm/v3/pkg/cli/values

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

View as plain text