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