Skip to content

Commit

Permalink
Create new translation mechanism, remove gateway lists support (istio…
Browse files Browse the repository at this point in the history
…#142)

* Create new translation mechanism, remove gateway slices

* Address comment

* Address comment, fix test

* Add test cases, fix common.values path, address comments
  • Loading branch information
richardwxn authored and istio-testing committed Aug 3, 2019
1 parent 1f64bb6 commit 567c532
Show file tree
Hide file tree
Showing 8 changed files with 1,056 additions and 72 deletions.
94 changes: 90 additions & 4 deletions pkg/name/name.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,30 @@ const (
IstioBaseComponentName ComponentName = "crds"
PilotComponentName ComponentName = "Pilot"
GalleyComponentName ComponentName = "Galley"
SidecarInjectorComponentName ComponentName = "SidecarInjector"
SidecarInjectorComponentName ComponentName = "Injector"
PolicyComponentName ComponentName = "Policy"
TelemetryComponentName ComponentName = "Telemetry"
CitadelComponentName ComponentName = "Citadel"
CertManagerComponentName ComponentName = "CertManager"
NodeAgentComponentName ComponentName = "NodeAgent"
IngressComponentName ComponentName = "Ingress"
EgressComponentName ComponentName = "Egress"
IngressComponentName ComponentName = "IngressGateway"
EgressComponentName ComponentName = "EgressGateway"
)

var (
ComponentNameToFeatureName = map[ComponentName]FeatureName{
IstioBaseComponentName: IstioBaseFeatureName,
PilotComponentName: TrafficManagementFeatureName,
GalleyComponentName: ConfigManagementFeatureName,
SidecarInjectorComponentName: AutoInjectionFeatureName,
PolicyComponentName: PolicyFeatureName,
TelemetryComponentName: TelemetryFeatureName,
CitadelComponentName: SecurityFeatureName,
CertManagerComponentName: SecurityFeatureName,
NodeAgentComponentName: SecurityFeatureName,
IngressComponentName: GatewayFeatureName,
EgressComponentName: GatewayFeatureName,
}
)

// ManifestMap is a map of ComponentName to its manifest string.
Expand Down Expand Up @@ -116,6 +132,76 @@ func IsComponentEnabledInSpec(featureName FeatureName, componentName ComponentNa
return componentNode.Value, nil
}

// IsComponentEnabledFromValue get whether component is enabled in helm value.yaml tree.
// valuePath points to component path in the values tree.
func IsComponentEnabledFromValue(valuePath string, valueSpec map[string]interface{}) (bool, error) {
enabledPath := valuePath + ".enabled"
enableNodeI, found, err := GetFromTreePath(valueSpec, util.ToYAMLPath(enabledPath))
if err != nil {
return false, fmt.Errorf("error finding component enablement path: %s in helm value.yaml tree", enabledPath)
}
if !found {
// Some components do not specify enablement should be treated as enabled if the root node in the component subtree exists.
_, found, err := GetFromTreePath(valueSpec, util.ToYAMLPath(valuePath))
if found && err == nil {
return true, nil
}
return false, nil
}
enableNode, ok := enableNodeI.(bool)
if !ok {
return false, fmt.Errorf("node at valuePath %s has bad type %T, expect bool", enabledPath, enableNodeI)
}
return enableNode, nil
}

// NamespaceFromValue gets the namespace value in helm value.yaml tree.
func NamespaceFromValue(valuePath string, valueSpec map[string]interface{}) (string, error) {
nsNodeI, found, err := GetFromTreePath(valueSpec, util.ToYAMLPath(valuePath))
if err != nil {
return "", fmt.Errorf("namespace path not found: %s from helm value.yaml tree", valuePath)
}
if !found || nsNodeI == nil {
return "", nil
}
nsNode, ok := nsNodeI.(string)
if !ok {
return "", fmt.Errorf("node at helm value.yaml tree path %s has bad type %T, expect string", valuePath, nsNodeI)
}
return nsNode, nil
}

// GetFromTreePath returns the value at path from the given tree, or false if the path does not exist.
func GetFromTreePath(inputTree map[string]interface{}, path util.Path) (interface{}, bool, error) {
scope.Debugf("GetFromTreePath path=%s", path)
if len(path) == 0 {
return nil, false, fmt.Errorf("path is empty")
}
val := inputTree[path[0]]
if val == nil {
return nil, false, nil
}
if len(path) == 1 {
return val, true, nil
}
switch newRoot := val.(type) {
case map[string]interface{}:
return GetFromTreePath(newRoot, path[1:])
case []interface{}:
for _, node := range newRoot {
nextVal, found, err := GetFromTreePath(node.(map[string]interface{}), path[1:])
if err != nil {
continue
}
if found {
return nextVal, true, nil
}
}
return nil, false, nil
}
return GetFromTreePath(val.(map[string]interface{}), path[1:])
}

// Namespace returns the namespace for the component. It follows these rules:
// 1. If CustomPackagePath is unset, log and error and return the empty string.
// 2. If the feature and component namespaces are unset, return CustomPackagePath.
Expand Down Expand Up @@ -193,7 +279,7 @@ func getFromStructPath(node interface{}, path util.Path) (interface{}, bool, err
var structElems reflect.Value
switch kind {
case reflect.Map, reflect.Slice:
if len(path) != 0 {
if len(path) == 0 {
return nil, false, fmt.Errorf("getFromStructPath path %s, unsupported leaf type %T", path, node)
}
case reflect.Ptr:
Expand Down
24 changes: 2 additions & 22 deletions pkg/patch/patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,9 @@
package patch

import (
"bytes"
"fmt"
"testing"

"github.com/ghodss/yaml"
"github.com/gogo/protobuf/proto"
"github.com/golang/protobuf/jsonpb"

"istio.io/operator/pkg/apis/istio/v1alpha2"
"istio.io/operator/pkg/util"
)
Expand Down Expand Up @@ -173,7 +168,7 @@ a:
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
rc := &v1alpha2.KubernetesResourcesSpec{}
err := unmarshalWithJSONPB(makeOverlayHeader(tt.path, tt.value), rc)
err := util.UnmarshalWithJSONPB(makeOverlayHeader(tt.path, tt.value), rc)
if err != nil {
t.Fatalf("unmarshalWithJSONPB(%s): got error %s", tt.desc, err)
}
Expand Down Expand Up @@ -422,7 +417,7 @@ spec:
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
rc := &v1alpha2.KubernetesResourcesSpec{}
err := unmarshalWithJSONPB(makeOverlayHeader(tt.path, tt.value), rc)
err := util.UnmarshalWithJSONPB(makeOverlayHeader(tt.path, tt.value), rc)
if err != nil {
t.Fatalf("unmarshalWithJSONPB(%s): got error %s", tt.desc, err)
}
Expand Down Expand Up @@ -458,21 +453,6 @@ overlays:
return ret
}

// unmarshalWithJSONPB unmarshals y into out using jsonpb (required for many proto defined structs).
func unmarshalWithJSONPB(y string, out proto.Message) error {
jb, err := yaml.YAMLToJSON([]byte(y))
if err != nil {
return err
}

u := jsonpb.Unmarshaler{}
err = u.Unmarshal(bytes.NewReader(jb), out)
if err != nil {
return err
}
return nil
}

// errToString returns the string representation of err and the empty string if
// err is nil.
func errToString(err error) string {
Expand Down
37 changes: 37 additions & 0 deletions pkg/tpath/tpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,43 @@ func WritePathContext(nc *PathContext, value interface{}) error {
return nil
}

// TODO Merge this into existing WritePathContext method
// DeleteFromTree sets value at path of input untyped tree to nil
func DeleteFromTree(valueTree map[string]interface{}, path util.Path, remainPath util.Path) (bool, error) {
if len(remainPath) == 0 {
return false, nil
}
for key, val := range valueTree {
if key == remainPath[0] {
// found the path to delete value
if len(remainPath) == 1 {
valueTree[key] = nil
return true, nil
}
remainPath = remainPath[1:]
switch node := val.(type) {
case map[string]interface{}:
return DeleteFromTree(node, path, remainPath)
case []interface{}:
for _, newNode := range node {
newMap, ok := newNode.(map[string]interface{})
if !ok {
return false, fmt.Errorf("fail to convert []interface{} to map[string]interface{}")
}
found, err := DeleteFromTree(newMap, path, remainPath)
if found && err == nil {
return found, nil
}
}
// leaf
default:
return false, nil
}
}
}
return false, nil
}

func stringsEqual(a, b interface{}) bool {
return fmt.Sprint(a) == fmt.Sprint(b)
}
Expand Down
45 changes: 21 additions & 24 deletions pkg/translate/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,10 @@ func (t *Translator) OverlayK8sSettings(yml string, icp *v1alpha2.IstioControlPl
// om is a map of kind:name string to Object ptr.
om := objects.ToNameKindMap()
for inPath, v := range t.KubernetesMapping {
inPath = renderFeatureComponentPathTemplate(inPath, t.ToFeature[componentName], componentName)
inPath, err := renderFeatureComponentPathTemplate(inPath, t.ToFeature[componentName], componentName)
if err != nil {
return "", err
}
log.Infof("Checking for path %s in IstioControlPlaneSpec", inPath)
m, found, err := name.GetFromStructPath(icp, inPath)
if err != nil {
Expand All @@ -260,7 +263,10 @@ func (t *Translator) OverlayK8sSettings(yml string, icp *v1alpha2.IstioControlPl
if err != nil {
return "", err
}
outPath := t.renderResourceComponentPathTemplate(v.outPath, componentName)
outPath, err := t.renderResourceComponentPathTemplate(v.outPath, componentName)
if err != nil {
return "", err
}
log.Infof("path has value in IstioControlPlaneSpec, mapping to output path %s", outPath)
path := util.PathFromString(outPath)
pe := path[0]
Expand Down Expand Up @@ -507,7 +513,7 @@ func getValuesPathMapping(mappings map[string]*Translation, path util.Path) (str

// renderFeatureComponentPathTemplate renders a template of the form <path>{{.FeatureName}}<path>{{.ComponentName}}<path> with
// the supplied parameters.
func renderFeatureComponentPathTemplate(tmpl string, featureName name.FeatureName, componentName name.ComponentName) string {
func renderFeatureComponentPathTemplate(tmpl string, featureName name.FeatureName, componentName name.ComponentName) (string, error) {
type Temp struct {
FeatureName name.FeatureName
ComponentName name.ComponentName
Expand All @@ -516,43 +522,34 @@ func renderFeatureComponentPathTemplate(tmpl string, featureName name.FeatureNam
FeatureName: featureName,
ComponentName: componentName,
}
t, err := template.New("").Parse(tmpl)
if err != nil {
log.Errorf("Failed to create template object, Error: %v. Template string: \n%s\n", err.Error(), tmpl)
return err.Error()
}
buf := new(bytes.Buffer)
err = t.Execute(buf, ts)
if err != nil {
log.Errorf("Failed to execute template: %v", err.Error())
return err.Error()
}
return buf.String()
return renderTemplate(tmpl, ts)
}

// renderResourceComponentPathTemplate renders a template of the form <path>{{.ResourceName}}<path>{{.ContainerName}}<path> with
// the supplied parameters.
func (t *Translator) renderResourceComponentPathTemplate(tmpl string, componentName name.ComponentName) string {
func (t *Translator) renderResourceComponentPathTemplate(tmpl string, componentName name.ComponentName) (string, error) {
ts := struct {
ResourceName string
ContainerName string
}{
ResourceName: t.ComponentMaps[componentName].ResourceName,
ContainerName: t.ComponentMaps[componentName].ContainerName,
}
// TODO: address comment
// Can extract the template execution part to a common method, so for each
// rendering method just need to create a template struct and call this common method
tm, err := template.New("").Parse(tmpl)
return renderTemplate(tmpl, ts)
}

// helper method to render template
func renderTemplate(tmpl string, ts interface{}) (string, error) {
t, err := template.New("").Parse(tmpl)
if err != nil {
return err.Error()
return "", err
}
buf := new(bytes.Buffer)
err = tm.Execute(buf, ts)
err = t.Execute(buf, ts)
if err != nil {
return err.Error()
return "", err
}
return buf.String()
return buf.String(), nil
}

// defaultTranslationFunc is the default translation to values. It maps a Go data path into a YAML path.
Expand Down
20 changes: 1 addition & 19 deletions pkg/translate/translate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@
package translate

import (
"bytes"
"testing"

"github.com/ghodss/yaml"
"github.com/gogo/protobuf/proto"
"github.com/golang/protobuf/jsonpb"
"github.com/kr/pretty"

"istio.io/operator/pkg/apis/istio/v1alpha2"
Expand Down Expand Up @@ -189,7 +185,7 @@ sidecarInjectorWebhook:
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
ispec := &v1alpha2.IstioControlPlaneSpec{}
err := unmarshalWithJSONPB(tt.yamlStr, ispec)
err := util.UnmarshalWithJSONPB(tt.yamlStr, ispec)
if err != nil {
t.Fatalf("unmarshalWithJSONPB(%s): got error %s", tt.desc, err)
}
Expand All @@ -205,20 +201,6 @@ sidecarInjectorWebhook:
}
}

func unmarshalWithJSONPB(y string, out proto.Message) error {
jb, err := yaml.YAMLToJSON([]byte(y))
if err != nil {
return err
}

u := jsonpb.Unmarshaler{}
err = u.Unmarshal(bytes.NewReader(jb), out)
if err != nil {
return err
}
return nil
}

// errToString returns the string representation of err and the empty string if
// err is nil.
func errToString(err error) string {
Expand Down
Loading

0 comments on commit 567c532

Please sign in to comment.