mirror of
https://github.com/ysoftdevs/gardener-extension-shoot-fleet-agent.git
synced 2026-05-19 13:27:18 +02:00
Initial v1.0.0 commit
This commit is contained in:
+451
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package chartutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
)
|
||||
|
||||
// ErrNoTable indicates that a chart does not have a matching table.
|
||||
type ErrNoTable error
|
||||
|
||||
// ErrNoValue indicates that Values does not contain a key with a value
|
||||
type ErrNoValue error
|
||||
|
||||
// GlobalKey is the name of the Values key that is used for storing global vars.
|
||||
const GlobalKey = "global"
|
||||
|
||||
// Values represents a collection of chart values.
|
||||
type Values map[string]interface{}
|
||||
|
||||
// YAML encodes the Values into a YAML string.
|
||||
func (v Values) YAML() (string, error) {
|
||||
b, err := yaml.Marshal(v)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
// Table gets a table (YAML subsection) from a Values object.
|
||||
//
|
||||
// The table is returned as a Values.
|
||||
//
|
||||
// Compound table names may be specified with dots:
|
||||
//
|
||||
// foo.bar
|
||||
//
|
||||
// The above will be evaluated as "The table bar inside the table
|
||||
// foo".
|
||||
//
|
||||
// An ErrNoTable is returned if the table does not exist.
|
||||
func (v Values) Table(name string) (Values, error) {
|
||||
names := strings.Split(name, ".")
|
||||
table := v
|
||||
var err error
|
||||
|
||||
for _, n := range names {
|
||||
table, err = tableLookup(table, n)
|
||||
if err != nil {
|
||||
return table, err
|
||||
}
|
||||
}
|
||||
return table, err
|
||||
}
|
||||
|
||||
// AsMap is a utility function for converting Values to a map[string]interface{}.
|
||||
//
|
||||
// It protects against nil map panics.
|
||||
func (v Values) AsMap() map[string]interface{} {
|
||||
if v == nil || len(v) == 0 {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Encode writes serialized Values information to the given io.Writer.
|
||||
func (v Values) Encode(w io.Writer) error {
|
||||
//return yaml.NewEncoder(w).Encode(v)
|
||||
out, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeInto takes the properties in src and merges them into Values. Maps
|
||||
// are merged while values and arrays are replaced.
|
||||
func (v Values) MergeInto(src Values) {
|
||||
for key, srcVal := range src {
|
||||
destVal, found := v[key]
|
||||
|
||||
if found && istable(srcVal) && istable(destVal) {
|
||||
destMap := destVal.(map[string]interface{})
|
||||
srcMap := srcVal.(map[string]interface{})
|
||||
Values(destMap).MergeInto(Values(srcMap))
|
||||
} else {
|
||||
v[key] = srcVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tableLookup(v Values, simple string) (Values, error) {
|
||||
v2, ok := v[simple]
|
||||
if !ok {
|
||||
return v, ErrNoTable(fmt.Errorf("no table named %q (%v)", simple, v))
|
||||
}
|
||||
if vv, ok := v2.(map[string]interface{}); ok {
|
||||
return vv, nil
|
||||
}
|
||||
|
||||
// This catches a case where a value is of type Values, but doesn't (for some
|
||||
// reason) match the map[string]interface{}. This has been observed in the
|
||||
// wild, and might be a result of a nil map of type Values.
|
||||
if vv, ok := v2.(Values); ok {
|
||||
return vv, nil
|
||||
}
|
||||
|
||||
var e ErrNoTable = fmt.Errorf("no table named %q", simple)
|
||||
return map[string]interface{}{}, e
|
||||
}
|
||||
|
||||
// ReadValues will parse YAML byte data into a Values.
|
||||
func ReadValues(data []byte) (vals Values, err error) {
|
||||
err = yaml.Unmarshal(data, &vals)
|
||||
if len(vals) == 0 {
|
||||
vals = Values{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadValuesFile will parse a YAML file into a map of values.
|
||||
func ReadValuesFile(filename string) (Values, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return map[string]interface{}{}, err
|
||||
}
|
||||
return ReadValues(data)
|
||||
}
|
||||
|
||||
// CoalesceValues coalesces all of the values in a chart (and its subcharts).
|
||||
//
|
||||
// Values are coalesced together using the following rules:
|
||||
//
|
||||
// - Values in a higher level chart always override values in a lower-level
|
||||
// dependency chart
|
||||
// - Scalar values and arrays are replaced, maps are merged
|
||||
// - A chart has access to all of the variables for it, as well as all of
|
||||
// the values destined for its dependencies.
|
||||
func CoalesceValues(chrt *chart.Chart, vals *chart.Config) (Values, error) {
|
||||
cvals := Values{}
|
||||
// Parse values if not nil. We merge these at the top level because
|
||||
// the passed-in values are in the same namespace as the parent chart.
|
||||
if vals != nil {
|
||||
evals, err := ReadValues([]byte(vals.Raw))
|
||||
if err != nil {
|
||||
return cvals, err
|
||||
}
|
||||
cvals, err = coalesce(chrt, evals)
|
||||
if err != nil {
|
||||
return cvals, err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
cvals, err = coalesceDeps(chrt, cvals)
|
||||
return cvals, err
|
||||
}
|
||||
|
||||
// coalesce coalesces the dest values and the chart values, giving priority to the dest values.
|
||||
//
|
||||
// This is a helper function for CoalesceValues.
|
||||
func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
dest, err = coalesceValues(ch, dest)
|
||||
if err != nil {
|
||||
return dest, err
|
||||
}
|
||||
coalesceDeps(ch, dest)
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
// coalesceDeps coalesces the dependencies of the given chart.
|
||||
func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) {
|
||||
for _, subchart := range chrt.Dependencies {
|
||||
if c, ok := dest[subchart.Metadata.Name]; !ok {
|
||||
// If dest doesn't already have the key, create it.
|
||||
dest[subchart.Metadata.Name] = map[string]interface{}{}
|
||||
} else if !istable(c) {
|
||||
return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Metadata.Name, c)
|
||||
}
|
||||
if dv, ok := dest[subchart.Metadata.Name]; ok {
|
||||
dvmap := dv.(map[string]interface{})
|
||||
|
||||
// Get globals out of dest and merge them into dvmap.
|
||||
coalesceGlobals(dvmap, dest, chrt.Metadata.Name)
|
||||
|
||||
var err error
|
||||
// Now coalesce the rest of the values.
|
||||
dest[subchart.Metadata.Name], err = coalesce(subchart, dvmap)
|
||||
if err != nil {
|
||||
return dest, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
// coalesceGlobals copies the globals out of src and merges them into dest.
|
||||
//
|
||||
// For convenience, returns dest.
|
||||
func coalesceGlobals(dest, src map[string]interface{}, chartName string) map[string]interface{} {
|
||||
var dg, sg map[string]interface{}
|
||||
|
||||
if destglob, ok := dest[GlobalKey]; !ok {
|
||||
dg = map[string]interface{}{}
|
||||
} else if dg, ok = destglob.(map[string]interface{}); !ok {
|
||||
log.Printf("Warning: Skipping globals for chart '%s' because destination '%s' is not a table.", chartName, GlobalKey)
|
||||
return dg
|
||||
}
|
||||
|
||||
if srcglob, ok := src[GlobalKey]; !ok {
|
||||
sg = map[string]interface{}{}
|
||||
} else if sg, ok = srcglob.(map[string]interface{}); !ok {
|
||||
log.Printf("Warning: skipping globals for chart '%s' because source '%s' is not a table.", chartName, GlobalKey)
|
||||
return dg
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: In the past, we have disallowed globals to test tables. This
|
||||
// reverses that decision. It may somehow be possible to introduce a loop
|
||||
// here, but I haven't found a way. So for the time being, let's allow
|
||||
// tables in globals.
|
||||
for key, val := range sg {
|
||||
if istable(val) {
|
||||
vv := copyMap(val.(map[string]interface{}))
|
||||
if destv, ok := dg[key]; ok {
|
||||
if destvmap, ok := destv.(map[string]interface{}); ok {
|
||||
// Basically, we reverse order of coalesce here to merge
|
||||
// top-down.
|
||||
coalesceTables(vv, destvmap, chartName)
|
||||
dg[key] = vv
|
||||
continue
|
||||
} else {
|
||||
log.Printf("Warning: For chart '%s', cannot merge map onto non-map for key '%q'. Skipping.", chartName, key)
|
||||
}
|
||||
} else {
|
||||
// Here there is no merge. We're just adding.
|
||||
dg[key] = vv
|
||||
}
|
||||
} else if dv, ok := dg[key]; ok && istable(dv) {
|
||||
// It's not clear if this condition can actually ever trigger.
|
||||
log.Printf("Warning: For chart '%s', key '%s' is a table. Skipping.", chartName, key)
|
||||
continue
|
||||
}
|
||||
// TODO: Do we need to do any additional checking on the value?
|
||||
dg[key] = val
|
||||
}
|
||||
dest[GlobalKey] = dg
|
||||
return dest
|
||||
}
|
||||
|
||||
func copyMap(src map[string]interface{}) map[string]interface{} {
|
||||
dest := make(map[string]interface{}, len(src))
|
||||
for k, v := range src {
|
||||
dest[k] = v
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
// coalesceValues builds up a values map for a particular chart.
|
||||
//
|
||||
// Values in v will override the values in the chart.
|
||||
func coalesceValues(c *chart.Chart, v map[string]interface{}) (map[string]interface{}, error) {
|
||||
// If there are no values in the chart, we just return the given values
|
||||
if c.Values == nil || c.Values.Raw == "" {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
nv, err := ReadValues([]byte(c.Values.Raw))
|
||||
if err != nil {
|
||||
// On error, we return just the overridden values.
|
||||
// FIXME: We should log this error. It indicates that the YAML data
|
||||
// did not parse.
|
||||
return v, fmt.Errorf("Error: Reading chart '%s' default values (%s): %s", c.Metadata.Name, c.Values.Raw, err)
|
||||
}
|
||||
|
||||
for key, val := range nv {
|
||||
if value, ok := v[key]; ok {
|
||||
if value == nil {
|
||||
// When the YAML value is null, we remove the value's key.
|
||||
// This allows Helm's various sources of values (value files or --set) to
|
||||
// remove incompatible keys from any previous chart, file, or set values.
|
||||
delete(v, key)
|
||||
} else if dest, ok := value.(map[string]interface{}); ok {
|
||||
// if v[key] is a table, merge nv's val table into v[key].
|
||||
src, ok := val.(map[string]interface{})
|
||||
if !ok {
|
||||
log.Printf("Warning: Building values map for chart '%s'. Skipped value (%+v) for '%s', as it is not a table.", c.Metadata.Name, src, key)
|
||||
continue
|
||||
}
|
||||
// Because v has higher precedence than nv, dest values override src
|
||||
// values.
|
||||
coalesceTables(dest, src, c.Metadata.Name)
|
||||
}
|
||||
} else {
|
||||
// If the key is not in v, copy it from nv.
|
||||
v[key] = val
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// coalesceTables merges a source map into a destination map.
|
||||
//
|
||||
// dest is considered authoritative.
|
||||
func coalesceTables(dst, src map[string]interface{}, chartName string) map[string]interface{} {
|
||||
// Because dest has higher precedence than src, dest values override src
|
||||
// values.
|
||||
for key, val := range src {
|
||||
if istable(val) {
|
||||
if innerdst, ok := dst[key]; !ok {
|
||||
dst[key] = val
|
||||
} else if istable(innerdst) {
|
||||
coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{}), chartName)
|
||||
} else {
|
||||
log.Printf("Warning: Merging destination map for chart '%s'. Cannot overwrite table item '%s', with non table value: %v", chartName, key, val)
|
||||
}
|
||||
continue
|
||||
} else if dv, ok := dst[key]; ok && istable(dv) {
|
||||
log.Printf("Warning: Merging destination map for chart '%s'. The destination item '%s' is a table and ignoring the source '%s' as it has a non-table value of: %v", chartName, key, key, val)
|
||||
continue
|
||||
} else if !ok { // <- ok is still in scope from preceding conditional.
|
||||
dst[key] = val
|
||||
continue
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// ReleaseOptions represents the additional release options needed
|
||||
// for the composition of the final values struct
|
||||
type ReleaseOptions struct {
|
||||
Name string
|
||||
Time *timestamp.Timestamp
|
||||
Namespace string
|
||||
IsUpgrade bool
|
||||
IsInstall bool
|
||||
Revision int
|
||||
}
|
||||
|
||||
// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
|
||||
//
|
||||
// WARNING: This function is deprecated for Helm > 2.1.99 Use ToRenderValuesCaps() instead. It will
|
||||
// remain in the codebase to stay SemVer compliant.
|
||||
//
|
||||
// In Helm 3.0, this will be changed to accept Capabilities as a fourth parameter.
|
||||
func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) {
|
||||
caps := &Capabilities{APIVersions: DefaultVersionSet}
|
||||
return ToRenderValuesCaps(chrt, chrtVals, options, caps)
|
||||
}
|
||||
|
||||
// ToRenderValuesCaps composes the struct from the data coming from the Releases, Charts and Values files
|
||||
//
|
||||
// This takes both ReleaseOptions and Capabilities to merge into the render values.
|
||||
func ToRenderValuesCaps(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions, caps *Capabilities) (Values, error) {
|
||||
|
||||
top := map[string]interface{}{
|
||||
"Release": map[string]interface{}{
|
||||
"Name": options.Name,
|
||||
"Time": options.Time,
|
||||
"Namespace": options.Namespace,
|
||||
"IsUpgrade": options.IsUpgrade,
|
||||
"IsInstall": options.IsInstall,
|
||||
"Revision": options.Revision,
|
||||
"Service": "Tiller",
|
||||
},
|
||||
"Chart": chrt.Metadata,
|
||||
"Files": NewFiles(chrt.Files),
|
||||
"Capabilities": caps,
|
||||
}
|
||||
|
||||
vals, err := CoalesceValues(chrt, chrtVals)
|
||||
if err != nil {
|
||||
return top, err
|
||||
}
|
||||
|
||||
top["Values"] = vals
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
|
||||
func istable(v interface{}) bool {
|
||||
_, ok := v.(map[string]interface{})
|
||||
return ok
|
||||
}
|
||||
|
||||
// PathValue takes a path that traverses a YAML structure and returns the value at the end of that path.
|
||||
// The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods.
|
||||
// Given the following YAML data the value at path "chapter.one.title" is "Loomings".
|
||||
//
|
||||
// chapter:
|
||||
// one:
|
||||
// title: "Loomings"
|
||||
func (v Values) PathValue(ypath string) (interface{}, error) {
|
||||
if len(ypath) == 0 {
|
||||
return nil, errors.New("YAML path string cannot be zero length")
|
||||
}
|
||||
yps := strings.Split(ypath, ".")
|
||||
if len(yps) == 1 {
|
||||
// if exists must be root key not table
|
||||
vals := v.AsMap()
|
||||
k := yps[0]
|
||||
if _, ok := vals[k]; ok && !istable(vals[k]) {
|
||||
// key found
|
||||
return vals[yps[0]], nil
|
||||
}
|
||||
// key not found
|
||||
return nil, ErrNoValue(fmt.Errorf("%v is not a value", k))
|
||||
}
|
||||
// join all elements of YAML path except last to get string table path
|
||||
ypsLen := len(yps)
|
||||
table := yps[:ypsLen-1]
|
||||
st := strings.Join(table, ".")
|
||||
// get the last element as a string key
|
||||
key := yps[ypsLen-1:]
|
||||
sk := string(key[0])
|
||||
// get our table for table path
|
||||
t, err := v.Table(st)
|
||||
if err != nil {
|
||||
//no table
|
||||
return nil, ErrNoValue(fmt.Errorf("%v is not a value", sk))
|
||||
}
|
||||
// check table for key and ensure value is not a table
|
||||
if k, ok := t[sk]; ok && !istable(k) {
|
||||
// key found
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// key not found
|
||||
return nil, ErrNoValue(fmt.Errorf("key not found: %s", sk))
|
||||
}
|
||||
Reference in New Issue
Block a user