feat: implement export-values

Signed-off-by: Ilya Lesikov <ilya@lesikov.com>
Signed-off-by: Sumit Kulhadia <sumit.kulhadia@sap.com>

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Co-authored-by: Sumit Kulhadia <sumit.kulhadia@sap.com>
Co-authored-by: Philipp Stehle <philipp.stehle@sap.com>
Co-authored-by: Pavel Busko <pavel.busko@sap.com>
pull/10804/head
Ilya Lesikov 4 years ago committed by Pavel Busko
parent 54cbc75957
commit c1f5ddd638

@ -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 // 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. // string or pair of child/parent sublist items.
ImportValues []interface{} `json:"import-values,omitempty"` 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 usable alias to be used for the chart
Alias string `json:"alias,omitempty"` Alias string `json:"alias,omitempty"`
} }

@ -16,6 +16,7 @@ limitations under the License.
package chartutil package chartutil
import ( import (
"fmt"
"log" "log"
"strings" "strings"
@ -31,7 +32,7 @@ func ProcessDependencies(c *chart.Chart, v Values) error {
if err := processDependencyEnabled(c, v, ""); err != nil { if err := processDependencyEnabled(c, v, ""); err != nil {
return err return err
} }
return processDependencyImportValues(c, false) return processDependencyImportExportValues(c, false)
} }
// ProcessDependenciesWithMerge checks through this chart's dependencies, processing accordingly. // ProcessDependenciesWithMerge checks through this chart's dependencies, processing accordingly.
@ -41,7 +42,8 @@ func ProcessDependenciesWithMerge(c *chart.Chart, v Values) error {
if err := processDependencyEnabled(c, v, ""); err != nil { if err := processDependencyEnabled(c, v, ""); err != nil {
return err return err
} }
return processDependencyImportValues(c, true)
return processDependencyImportExportValues(c, true)
} }
// processDependencyConditions disables charts based on condition path value in values // processDependencyConditions disables charts based on condition path value in values
@ -233,8 +235,9 @@ func set(path []string, data map[string]interface{}) map[string]interface{} {
return cur return cur
} }
// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. // processImportExportValues merges values between child and parent based on the chart's dependencies' ImportValues
func processImportValues(c *chart.Chart, merge bool) error { // and ExportValues fields.
func processImportExportValues(c *chart.Chart, merge bool) error {
if c.Metadata.Dependencies == nil { if c.Metadata.Dependencies == nil {
return nil return nil
} }
@ -250,51 +253,16 @@ func processImportValues(c *chart.Chart, merge bool) error {
return err return err
} }
b := make(map[string]interface{}) 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 { for _, r := range c.Metadata.Dependencies {
var outiv []interface{} if merge {
for _, riv := range r.ImportValues { b = MergeTables(b, getExportedValues(c, r, cvals, merge))
switch iv := riv.(type) { b = MergeTables(b, getImportedValues(r, cvals, merge))
case map[string]interface{}: } else {
child := iv["child"].(string) b = CoalesceTables(b, getExportedValues(c, r, cvals, merge))
parent := iv["parent"].(string) b = CoalesceTables(b, getImportedValues(r, cvals, merge))
outiv = append(outiv, map[string]string{
"child": child,
"parent": parent,
})
// get child table
vv, err := cvals.Table(r.Name + "." + child)
if err != nil {
log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err)
continue
}
// create value map from child to be merged into parent
if merge {
b = MergeTables(b, pathToMap(parent, vv.AsMap()))
} else {
b = CoalesceTables(b, pathToMap(parent, vv.AsMap()))
}
case string:
child := "exports." + iv
outiv = append(outiv, map[string]string{
"child": child,
"parent": ".",
})
vm, err := cvals.Table(r.Name + "." + child)
if err != nil {
log.Printf("Warning: ImportValues missing table: %v", err)
continue
}
if merge {
b = MergeTables(b, vm.AsMap())
} else {
b = CoalesceTables(b, vm.AsMap())
}
}
} }
r.ImportValues = outiv
} }
// Imported values from a child to a parent chart have a higher priority than // Imported values from a child to a parent chart have a higher priority than
@ -345,13 +313,163 @@ func trimNilValues(vals map[string]interface{}) map[string]interface{} {
return valsCopyMap return valsCopyMap
} }
// processDependencyImportValues imports specified chart values from child to parent. // Generates values map which is imported from specified child to parent via import-values.
func processDependencyImportValues(c *chart.Chart, merge bool) error { func getImportedValues(r *chart.Dependency, cvals Values, merge bool) map[string]interface{} {
b := make(map[string]interface{})
var outiv []interface{}
for _, riv := range r.ImportValues {
switch iv := riv.(type) {
case map[string]interface{}:
child := iv["child"].(string)
parent := iv["parent"].(string)
outiv = append(outiv, map[string]string{
"child": child,
"parent": parent,
})
// get child table
vv, err := cvals.Table(r.Name + "." + child)
if err != nil {
log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err)
continue
}
// create value map from child to be merged into parent
if merge {
b = MergeTables(b, pathToMap(parent, vv.AsMap()))
} else {
b = CoalesceTables(b, pathToMap(parent, vv.AsMap()))
}
case string:
child := "exports." + iv
outiv = append(outiv, map[string]string{
"child": child,
"parent": ".",
})
vm, err := cvals.Table(r.Name + "." + child)
if err != nil {
log.Printf("Warning: ImportValues missing table: %v", err)
continue
}
if merge {
b = MergeTables(b, vm.AsMap())
} else {
b = CoalesceTables(b, vm.AsMap())
}
}
}
// set our formatted import values
r.ImportValues = outiv
return 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, merge bool) 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
}
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
if merge {
b = MergeTables(childValMap, b)
} else {
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 must be a string")
}
child, ok = ev["child"].(string)
if !ok {
return "", "", fmt.Errorf("child must be a string")
}
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
}
// 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, merge bool) error {
for _, d := range c.Dependencies() { for _, d := range c.Dependencies() {
// recurse // recurse
if err := processDependencyImportValues(d, merge); err != nil { if err := processDependencyImportExportValues(d, merge); err != nil {
return err return err
} }
} }
return processImportValues(c, merge) return processImportExportValues(c, merge)
} }

@ -218,7 +218,7 @@ func TestProcessDependencyImportValues(t *testing.T) {
e["SCBexported2A"] = "blaster" e["SCBexported2A"] = "blaster"
e["global.SC1exported2.all.SC1exported3"] = "SC1expstr" e["global.SC1exported2.all.SC1exported3"] = "SC1expstr"
if err := processDependencyImportValues(c, false); err != nil { if err := processDependencyImportExportValues(c, false); err != nil {
t.Fatalf("processing import values dependencies %v", err) t.Fatalf("processing import values dependencies %v", err)
} }
cc := Values(c.Values) cc := Values(c.Values)
@ -258,7 +258,7 @@ func TestProcessDependencyImportValues(t *testing.T) {
} }
c = loadChart(t, "testdata/subpop") c = loadChart(t, "testdata/subpop")
if err := processDependencyImportValues(c, true); err != nil { if err := processDependencyImportExportValues(c, true); err != nil {
t.Fatalf("processing import values dependencies %v", err) t.Fatalf("processing import values dependencies %v", err)
} }
cc = Values(c.Values) cc = Values(c.Values)
@ -294,7 +294,7 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
e["app2.service.port"] = "9090" e["app2.service.port"] = "9090"
e["app3.service.port"] = "9090" e["app3.service.port"] = "9090"
e["app4.service.port"] = "1234" e["app4.service.port"] = "1234"
if err := processDependencyImportValues(c, true); err != nil { if err := processDependencyImportExportValues(c, true); err != nil {
t.Fatalf("processing import values dependencies %v", err) t.Fatalf("processing import values dependencies %v", err)
} }
cc := Values(c.Values) cc := Values(c.Values)
@ -317,11 +317,69 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
} }
} }
func TestProcessDependencyExportValues(t *testing.T) {
chart := loadChart(t, "testdata/subpop")
expectation := make(map[string]interface{})
// single value export
expectation["subchart1.single-value-parent"] = "exported-from-parent-7"
// merge with overrides
expectation["subchart1.merged-values.SC1bool"] = true
expectation["subchart1.merged-values.SPExtra11"] = "should-be-unchanged"
expectation["subchart1.merged-values.SC1float"] = 22.2
expectation["subchart1.merged-values.SC1int"] = float64(222)
expectation["subchart1.merged-values.SC1string"] = "exported-from-parent-2"
// merge exports
expectation["subchart1.merged-values.SPNested1.SPExtra19"] = "exported-from-parent-6"
// `exports` style
expectation["subchart1.merged-values.SPExtra9"] = "exported-from-parent-4"
expectation["subchart1.merged-values.SPExtra12"] = "should-be-unchanged"
// passed from child to grandchild with overrides
expectation["subchart1.subcharta.exported-overridden-chart1.SCAbool"] = true
expectation["subchart1.subcharta.exported-overridden-chart1.SCAfloat"] = 33.3
expectation["subchart1.subcharta.exported-overridden-chart1.SCAint"] = float64(333)
expectation["subchart1.subcharta.exported-overridden-chart1.SCAstring"] = "exported-from-chart1"
expectation["subchart1.subcharta.exported-overridden-chart1.SPExtra13"] = "exported-from-chart1-2"
expectation["subchart1.subcharta.exported-overridden-chart1.SPExtra15"] = "should-be-unchanged"
// passed through from parent to grandchild
expectation["subchart1.subcharta.merged-values.SPExtra17"] = "exported-from-parent-5"
expectation["subchart1.subcharta.merged-values.SPExtra18"] = "should-be-unchanged"
expectation["subchart1.subcharta.merged-values.SPExtra19"] = "exported-from-parent-9" //overwritten
// passed from child to grandchild, `exports` style
expectation["subchart1.subcharta.merged-values.SPExtra14"] = "exported-from-chart1-3"
expectation["subchart1.subcharta.merged-values.SPExtra16"] = "should-be-unchanged"
// exporting everything
expectation["subchart1.explicit-export1.merged-values.SPExtra9"] = "exported-from-parent-4"
if err := ProcessDependencies(chart, Values{}); err != nil {
t.Fatalf("processing dependencies failed: %v", err)
}
chartValues := Values(chart.Values)
for path, expectedValue := range expectation {
actualValue, err := chartValues.PathValue(path)
if err != nil {
t.Fatalf("retrieving export values table %v %v", path, err)
}
if actualValue != expectedValue {
t.Errorf("failed to match exported string value %q with expected %v", actualValue, expectedValue)
}
}
}
func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) { func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) {
c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart") c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart")
nameOverride := "parent-chart-prod" nameOverride := "parent-chart-prod"
if err := processDependencyImportValues(c, true); err != nil { if err := processDependencyImportExportValues(c, true); err != nil {
t.Fatalf("processing import values dependencies %v", err) t.Fatalf("processing import values dependencies %v", err)
} }

@ -2,7 +2,9 @@ apiVersion: v1
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: parentchart name: parentchart
version: 0.1.0 version: 0.1.0
dependencies: dependencies:
- name: subchart1 - name: subchart1
repository: http://localhost:10191 repository: http://localhost:10191
version: 0.1.0 version: 0.1.0
@ -25,6 +27,17 @@ dependencies:
parent: . parent: .
- SCBexported2 - SCBexported2
- SC1exported1 - SC1exported1
export-values:
- parent: values-for-the-child
child: merged-values
- parent: values-for-the-child-2
child: merged-values
- parent: exported-single-value
child: single-value-parent
- parent: values-for-the-grandchild
child: subcharta.merged-values
- explicit-export1
- ""
- name: subchart2 - name: subchart2
repository: http://localhost:10191 repository: http://localhost:10191

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

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

@ -29,6 +29,22 @@ overridden-chartA:
imported-chartA-B: imported-chartA-B:
SC1extra5: "tiller" SC1extra5: "tiller"
merged-values:
SPExtra11: "should-be-unchanged"
SPExtra12: "should-be-unchanged"
SC1bool: false
SC1float: 11.1
SC1int: 111
SC1string: "should-be-overridden"
exported-overridden-chart1:
SCAbool: true
SCAfloat: 33.3
SCAint: 333
SCAstring: "exported-from-chart1"
SPExtra13: "exported-from-chart1-2"
overridden-chartA-B: overridden-chartA-B:
SCAbool: true SCAbool: true
SCAfloat: 3.33 SCAfloat: 3.33
@ -52,4 +68,7 @@ exports:
global: global:
SC1exported2: SC1exported2:
all: all:
SC1exported3: "SC1expstr" SC1exported3: "SC1expstr"
explicit-child-export:
merged-values:
SPExtra14: "exported-from-chart1-3"

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

@ -35,6 +35,28 @@ overridden-chartA-B:
SCBstring: "jango" SCBstring: "jango"
SPextra6: 111 SPextra6: 111
values-for-the-child:
SC1bool: true
SC1float: 22.2
SC1int: 222
SC1string: "exported-from-parent-2"
values-for-the-child-2:
SPNested1:
SPExtra19: "exported-from-parent-6"
exported-single-value: "exported-from-parent-7"
values-for-the-grandchild:
SPExtra17: "exported-from-parent-5"
SPExtra19: "exported-from-parent-9"
exports:
explicit-export1:
merged-values:
SPExtra9: "exported-from-parent-4"
tags: tags:
front-end: true front-end: true
back-end: false back-end: false

Loading…
Cancel
Save