github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/chartutil/values.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 chartutil
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"strings"
    24  
    25  	"github.com/pkg/errors"
    26  	"sigs.k8s.io/yaml"
    27  
    28  	"github.com/stefanmcshane/helm/pkg/chart"
    29  )
    30  
    31  // GlobalKey is the name of the Values key that is used for storing global vars.
    32  const GlobalKey = "global"
    33  
    34  // Values represents a collection of chart values.
    35  type Values map[string]interface{}
    36  
    37  // YAML encodes the Values into a YAML string.
    38  func (v Values) YAML() (string, error) {
    39  	b, err := yaml.Marshal(v)
    40  	return string(b), err
    41  }
    42  
    43  // Table gets a table (YAML subsection) from a Values object.
    44  //
    45  // The table is returned as a Values.
    46  //
    47  // Compound table names may be specified with dots:
    48  //
    49  //	foo.bar
    50  //
    51  // The above will be evaluated as "The table bar inside the table
    52  // foo".
    53  //
    54  // An ErrNoTable is returned if the table does not exist.
    55  func (v Values) Table(name string) (Values, error) {
    56  	table := v
    57  	var err error
    58  
    59  	for _, n := range parsePath(name) {
    60  		if table, err = tableLookup(table, n); err != nil {
    61  			break
    62  		}
    63  	}
    64  	return table, err
    65  }
    66  
    67  // AsMap is a utility function for converting Values to a map[string]interface{}.
    68  //
    69  // It protects against nil map panics.
    70  func (v Values) AsMap() map[string]interface{} {
    71  	if len(v) == 0 {
    72  		return map[string]interface{}{}
    73  	}
    74  	return v
    75  }
    76  
    77  // Encode writes serialized Values information to the given io.Writer.
    78  func (v Values) Encode(w io.Writer) error {
    79  	out, err := yaml.Marshal(v)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	_, err = w.Write(out)
    84  	return err
    85  }
    86  
    87  func tableLookup(v Values, simple string) (Values, error) {
    88  	v2, ok := v[simple]
    89  	if !ok {
    90  		return v, ErrNoTable{simple}
    91  	}
    92  	if vv, ok := v2.(map[string]interface{}); ok {
    93  		return vv, nil
    94  	}
    95  
    96  	// This catches a case where a value is of type Values, but doesn't (for some
    97  	// reason) match the map[string]interface{}. This has been observed in the
    98  	// wild, and might be a result of a nil map of type Values.
    99  	if vv, ok := v2.(Values); ok {
   100  		return vv, nil
   101  	}
   102  
   103  	return Values{}, ErrNoTable{simple}
   104  }
   105  
   106  // ReadValues will parse YAML byte data into a Values.
   107  func ReadValues(data []byte) (vals Values, err error) {
   108  	err = yaml.Unmarshal(data, &vals)
   109  	if len(vals) == 0 {
   110  		vals = Values{}
   111  	}
   112  	return vals, err
   113  }
   114  
   115  // ReadValuesFile will parse a YAML file into a map of values.
   116  func ReadValuesFile(filename string) (Values, error) {
   117  	data, err := ioutil.ReadFile(filename)
   118  	if err != nil {
   119  		return map[string]interface{}{}, err
   120  	}
   121  	return ReadValues(data)
   122  }
   123  
   124  // ReleaseOptions represents the additional release options needed
   125  // for the composition of the final values struct
   126  type ReleaseOptions struct {
   127  	Name      string
   128  	Namespace string
   129  	Revision  int
   130  	IsUpgrade bool
   131  	IsInstall bool
   132  }
   133  
   134  // ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
   135  //
   136  // This takes both ReleaseOptions and Capabilities to merge into the render values.
   137  func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities) (Values, error) {
   138  	if caps == nil {
   139  		caps = DefaultCapabilities
   140  	}
   141  	top := map[string]interface{}{
   142  		"Chart":        chrt.Metadata,
   143  		"Capabilities": caps,
   144  		"Release": map[string]interface{}{
   145  			"Name":      options.Name,
   146  			"Namespace": options.Namespace,
   147  			"IsUpgrade": options.IsUpgrade,
   148  			"IsInstall": options.IsInstall,
   149  			"Revision":  options.Revision,
   150  			"Service":   "Helm",
   151  		},
   152  	}
   153  
   154  	vals, err := CoalesceValues(chrt, chrtVals)
   155  	if err != nil {
   156  		return top, err
   157  	}
   158  
   159  	if err := ValidateAgainstSchema(chrt, vals); err != nil {
   160  		errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s"
   161  		return top, fmt.Errorf(errFmt, err.Error())
   162  	}
   163  
   164  	top["Values"] = vals
   165  	return top, nil
   166  }
   167  
   168  // istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
   169  func istable(v interface{}) bool {
   170  	_, ok := v.(map[string]interface{})
   171  	return ok
   172  }
   173  
   174  // PathValue takes a path that traverses a YAML structure and returns the value at the end of that path.
   175  // The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods.
   176  // Given the following YAML data the value at path "chapter.one.title" is "Loomings".
   177  //
   178  //	chapter:
   179  //	  one:
   180  //	    title: "Loomings"
   181  func (v Values) PathValue(path string) (interface{}, error) {
   182  	if path == "" {
   183  		return nil, errors.New("YAML path cannot be empty")
   184  	}
   185  	return v.pathValue(parsePath(path))
   186  }
   187  
   188  func (v Values) pathValue(path []string) (interface{}, error) {
   189  	if len(path) == 1 {
   190  		// if exists must be root key not table
   191  		if _, ok := v[path[0]]; ok && !istable(v[path[0]]) {
   192  			return v[path[0]], nil
   193  		}
   194  		return nil, ErrNoValue{path[0]}
   195  	}
   196  
   197  	key, path := path[len(path)-1], path[:len(path)-1]
   198  	// get our table for table path
   199  	t, err := v.Table(joinPath(path...))
   200  	if err != nil {
   201  		return nil, ErrNoValue{key}
   202  	}
   203  	// check table for key and ensure value is not a table
   204  	if k, ok := t[key]; ok && !istable(k) {
   205  		return k, nil
   206  	}
   207  	return nil, ErrNoValue{key}
   208  }
   209  
   210  func parsePath(key string) []string { return strings.Split(key, ".") }
   211  
   212  func joinPath(path ...string) string { return strings.Join(path, ".") }