feat: implement export-values

Signed-off-by: Ilya Lesikov <ilya@lesikov.com>
pull/10059/head
Ilya Lesikov 4 years ago
parent 6970b8dcc8
commit 23ca433c31

@ -45,6 +45,9 @@ type Dependency struct {
// ImportValues holds the mapping of source values to parent key to be imported. Each item can be a
// string or pair of child/parent sublist items.
ImportValues []interface{} `json:"import-values,omitempty"`
// ExportValues holds the mapping of parent values to child key to be exported. Each item can be a
// string or pair of parent/child sublist items.
ExportValues []interface{} `json:"export-values,omitempty"`
// Alias usable alias to be used for the chart
Alias string `json:"alias,omitempty"`
}

@ -16,6 +16,7 @@ limitations under the License.
package chartutil
import (
"fmt"
"log"
"strings"
@ -27,7 +28,7 @@ func ProcessDependencies(c *chart.Chart, v Values) error {
if err := processDependencyEnabled(c, v, ""); err != nil {
return err
}
return processDependencyImportValues(c)
return processDependencyImportExportValues(c)
}
// processDependencyConditions disables charts based on condition path value in values
@ -216,8 +217,9 @@ func set(path []string, data map[string]interface{}) map[string]interface{} {
return cur
}
// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field.
func processImportValues(c *chart.Chart) error {
// processImportExportValues merges values between child and parent based on the chart's dependencies' ImportValues
// and ExportValues fields.
func processImportExportValues(c *chart.Chart) error {
if c.Metadata.Dependencies == nil {
return nil
}
@ -227,8 +229,22 @@ func processImportValues(c *chart.Chart) error {
return err
}
b := make(map[string]interface{})
// import values from each dependency if specified in import-values
// For each dependency export values from parent or import values from child if export-values or import-values
// specified.
for _, r := range c.Metadata.Dependencies {
b = CoalesceTables(b, getExportedValues(c, r, cvals))
b = CoalesceTables(b, getImportedValues(r, cvals))
}
// set the new values
c.Values = CoalesceTables(cvals, b)
return nil
}
// Generates values map which is imported from specified child to parent via import-values.
func getImportedValues(r *chart.Dependency, cvals Values) map[string]interface{} {
b := make(map[string]interface{})
var outiv []interface{}
for _, riv := range r.ImportValues {
switch iv := riv.(type) {
@ -265,21 +281,112 @@ func processImportValues(c *chart.Chart) error {
}
// set our formatted import values
r.ImportValues = outiv
return b
}
// set the new values
c.Values = CoalesceTables(cvals, b)
// Generates values map which is exported from parent to specified child via export-values.
func getExportedValues(c *chart.Chart, r *chart.Dependency, cvals Values) map[string]interface{} {
b := make(map[string]interface{})
var exportValues []interface{}
for _, rev := range r.ExportValues {
parent, child, err := parseExportValues(rev, r)
if err != nil {
log.Printf("Warning: invalid ExportValues defined in chart %q for its dependency %q: %s", c.Name(), r.Name, err)
continue
}
return nil
exportValues = append(exportValues, map[string]string{
"parent": parent,
"child": child,
})
var childValMap map[string]interface{}
// try to get parent table
vm, err := cvals.Table(parent)
if err == nil {
childValMap = pathToMap(child, vm.AsMap())
} else {
// still it might be not a table but a simple value
value, e := cvals.PathValue(parent)
if e != nil {
log.Printf("Warning: ExportValues defined in chart %q for its dependency %q can't get the parent path: %v", c.Name(), r.Name, err)
continue
}
childSlice := parsePath(child)
childLen := len(childSlice)
if childLen == 1 {
log.Printf("Warning: in ExportValues defined in chart %q for its dependency %q you are trying to assign a primitive data type (string, int, etc) to the root of your dependent chart values. We will ignore this ExportValues, because this is most likely not what you want. Fix the ExportValues to hide this warning.", c.Name(), r.Name)
continue
}
childValMap = pathToMap(
joinPath(childSlice[:childLen-1]...),
map[string]interface{}{
childSlice[childLen-1]: value,
},
)
}
// merge new map with values exported to the child into the resulting values map
b = CoalesceTables(childValMap, b)
}
// set formatted export values
r.ExportValues = exportValues
return b
}
// Parse and validate export-values.
func parseExportValues(rev interface{}, r *chart.Dependency) (string, string, error) {
var parent, child string
switch ev := rev.(type) {
case map[string]interface{}:
var ok bool
parent, ok = ev["parent"].(string)
if !ok {
return "", "", fmt.Errorf("parent can't be of null type")
}
child, ok = ev["child"].(string)
if !ok {
return "", "", fmt.Errorf("child can't be of null type")
}
switch parent {
case "", ".":
return "", "", fmt.Errorf("parent %q is not allowed", parent)
}
switch child {
case "", ".":
child = r.Name
default:
child = r.Name + "." + child
}
case string:
switch parent = ev; parent {
case "", ".":
parent = "exports"
default:
parent = "exports." + parent
}
child = r.Name
default:
return "", "", fmt.Errorf("invalid type of ExportValues")
}
return parent, child, nil
}
// processDependencyImportValues imports specified chart values from child to parent.
func processDependencyImportValues(c *chart.Chart) error {
// processDependencyImportExportValues imports (child to parent) and/or exports (parent to child) values if
// import-values or export-values specified for the dependency.
func processDependencyImportExportValues(c *chart.Chart) error {
for _, d := range c.Dependencies() {
// recurse
if err := processDependencyImportValues(d); err != nil {
if err := processDependencyImportExportValues(d); err != nil {
return err
}
}
return processImportValues(c)
return processImportExportValues(c)
}

@ -212,7 +212,7 @@ func TestProcessDependencyImportValues(t *testing.T) {
e["SCBexported2A"] = "blaster"
e["global.SC1exported2.all.SC1exported3"] = "SC1expstr"
if err := processDependencyImportValues(c); err != nil {
if err := processDependencyImportExportValues(c); err != nil {
t.Fatalf("processing import values dependencies %v", err)
}
cc := Values(c.Values)
@ -247,7 +247,7 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
e["app1.service.port"] = "3456"
e["app2.service.port"] = "8080"
if err := processDependencyImportValues(c); err != nil {
if err := processDependencyImportExportValues(c); err != nil {
t.Fatalf("processing import values dependencies %v", err)
}
cc := Values(c.Values)
@ -270,11 +270,79 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
}
}
func TestProcessDependencyExportValues(t *testing.T) {
c := loadChart(t, "testdata/subpop")
e := make(map[string]string)
// merge with no overrides
e["subchart1.exported-parent.SPExtra7"] = "exported-from-parent"
e["subchart1.exported-parent.SPExtra10"] = "should-be-unchanged"
e["subchart1.exported-parent.SPNested1.SPExtra19"] = "exported-from-parent-n6"
// single value export
e["subchart1.exported-single-value-parent"] = "exported-from-parent-n7"
// merge with overrides
e["subchart1.exported-overridden-parent.SC1bool"] = "true"
e["subchart1.exported-overridden-parent.SC1float"] = "22.2"
e["subchart1.exported-overridden-parent.SC1int"] = "222"
e["subchart1.exported-overridden-parent.SC1string"] = "exported-from-parent-n2"
e["subchart1.exported-overridden-parent.SPExtra8"] = "exported-from-parent-n3"
e["subchart1.exported-overridden-parent.SPExtra11"] = "should-be-unchanged"
// `exports` style, no overrides
e["subchart1.exported-short-parent.SPExtra9"] = "exported-from-parent-n4"
e["subchart1.exported-short-parent.SPExtra12"] = "should-be-unchanged"
// passed from child to its own child with overrides
e["subchart1.subcharta.exported-overridden-chart1.SCAbool"] = "true"
e["subchart1.subcharta.exported-overridden-chart1.SCAfloat"] = "33.3"
e["subchart1.subcharta.exported-overridden-chart1.SCAint"] = "333"
e["subchart1.subcharta.exported-overridden-chart1.SCAstring"] = "exported-from-chart1"
e["subchart1.subcharta.exported-overridden-chart1.SPExtra13"] = "exported-from-chart1-n2"
e["subchart1.subcharta.exported-overridden-chart1.SPExtra15"] = "should-be-unchanged"
// passed from child to its own child, `exports` style, no overrides
e["subchart1.subcharta.exported-short-chart1.SPExtra14"] = "exported-from-chart1-n3"
e["subchart1.subcharta.exported-short-chart1.SPExtra16"] = "should-be-unchanged"
// passed through from parent to the child of the child chart, no overrides
e["subchart1.subcharta.exported-passthrough.SPExtra17"] = "exported-from-parent-n5"
e["subchart1.subcharta.exported-passthrough.SPExtra18"] = "should-be-unchanged"
if err := processDependencyImportExportValues(c); err != nil {
t.Fatalf("processing export values dependencies %v", err)
}
cc := Values(c.Values)
for kk, vv := range e {
pv, err := cc.PathValue(kk)
if err != nil {
t.Fatalf("retrieving export values table %v %v", kk, err)
}
switch pv := pv.(type) {
case float64:
if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv {
t.Errorf("failed to match exported float value %v with expected %v", s, vv)
}
case bool:
if b := strconv.FormatBool(pv); b != vv {
t.Errorf("failed to match exported bool value %v with expected %v", b, vv)
}
default:
if pv != vv {
t.Errorf("failed to match exported string value %q with expected %q", pv, vv)
}
}
}
}
func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) {
c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart")
nameOverride := "parent-chart-prod"
if err := processDependencyImportValues(c); err != nil {
if err := processDependencyImportExportValues(c); err != nil {
t.Fatalf("processing import values dependencies %v", err)
}

@ -25,6 +25,16 @@ dependencies:
parent: .
- SCBexported2
- SC1exported1
export-values:
- parent: exported-parent
child: exported-parent
- parent: exported-overridden-parent
child: exported-overridden-parent
- parent: exported-single-value-parent
child: exported-single-value-parent
- parent: exported-passthrough
child: subcharta.exported-passthrough
- exported-short-parent
- name: subchart2
repository: http://localhost:10191

@ -17,6 +17,10 @@ dependencies:
parent: overridden-chartA
- child: SCAdata
parent: imported-chartA-B
export-values:
- parent: exported-overridden-chart1
child: exported-overridden-chart1
- exported-short-chart1
- name: subchartb
repository: http://localhost:10191

@ -15,3 +15,15 @@ SCAdata:
SCAnested1:
SCAnested2: true
exported-overridden-chart1:
SCAbool: true
SCAfloat: 33.3
SCAint: 333
SCAstring: "exported-from-chart1"
SPExtra15: "should-be-unchanged"
exported-passthrough:
SPExtra18: "should-be-unchanged"
exported-short-chart1:
SPExtra16: "should-be-unchanged"

@ -29,6 +29,26 @@ overridden-chartA:
imported-chartA-B:
SC1extra5: "tiller"
exported-parent:
SPExtra10: "should-be-unchanged"
exported-overridden-parent:
SC1bool: false
SC1float: 11.1
SC1int: 111
SC1string: "should-be-overridden"
SPExtra11: "should-be-unchanged"
exported-short-parent:
SPExtra12: "should-be-unchanged"
exported-overridden-chart1:
SCAbool: true
SCAfloat: 33.3
SCAint: 333
SCAstring: "exported-from-chart1"
SPExtra13: "exported-from-chart1-n2"
overridden-chartA-B:
SCAbool: true
SCAfloat: 3.33
@ -53,3 +73,6 @@ exports:
SC1exported2:
all:
SC1exported3: "SC1expstr"
exported-short-chart1:
exported-short-chart1:
SPExtra14: "exported-from-chart1-n3"

@ -18,4 +18,3 @@ resources:
requests:
cpu: 100m
memory: 128Mi

@ -35,6 +35,28 @@ overridden-chartA-B:
SCBstring: "jango"
SPextra6: 111
exported-parent:
SPExtra7: "exported-from-parent"
SPNested1:
SPExtra19: "exported-from-parent-n6"
exported-overridden-parent:
SC1bool: true
SC1float: 22.2
SC1int: 222
SC1string: "exported-from-parent-n2"
SPExtra8: "exported-from-parent-n3"
exported-single-value-parent: "exported-from-parent-n7"
exported-passthrough:
SPExtra17: "exported-from-parent-n5"
exports:
exported-short-parent:
exported-short-parent:
SPExtra9: "exported-from-parent-n4"
tags:
front-end: true
back-end: false

Loading…
Cancel
Save