Improve error handling during var resolution.

This commit is contained in:
jregan
2019-02-24 10:17:11 -08:00
committed by Jeffrey Regan
parent f8c80b7335
commit 6bfd7cff72
8 changed files with 150 additions and 44 deletions

View File

@@ -22,6 +22,12 @@ import (
"sigs.k8s.io/kustomize/pkg/constants" "sigs.k8s.io/kustomize/pkg/constants"
) )
func TestNewOptionsToSilenceCodeInspectionError(t *testing.T) {
if NewOptions("foo", "bar") == nil {
t.Fatal("could not make new options")
}
}
func TestBuildValidate(t *testing.T) { func TestBuildValidate(t *testing.T) {
var cases = []struct { var cases = []struct {
name string name string

View File

@@ -94,7 +94,7 @@ func (a *flagsAndArgs) ExpandFileSource(fSys fs.FileSystem) error {
if key != "" { if key != "" {
if len(result) != 1 { if len(result) != 1 {
return fmt.Errorf( return fmt.Errorf(
"'pattern '%s' catches files %v, should catch only one.", pattern, result) "'pattern '%s' catches files %v, should catch only one", pattern, result)
} }
fileSource := fmt.Sprintf("%s=%s", key, result[0]) fileSource := fmt.Sprintf("%s=%s", key, result[0])
results = append(results, fileSource) results = append(results, fileSource)

View File

@@ -44,7 +44,6 @@ func MappingFuncFor(context ...map[string]string) func(string) string {
return val return val
} }
} }
return syntaxWrap(input) return syntaxWrap(input)
} }
} }

View File

@@ -46,9 +46,7 @@ func TestMapReference(t *testing.T) {
"BLU": "$(ZOO)-2", "BLU": "$(ZOO)-2",
} }
serviceEnv := map[string]string{} mapping := MappingFuncFor(declaredEnv)
mapping := MappingFuncFor(declaredEnv, serviceEnv)
for _, env := range envs { for _, env := range envs {
declaredEnv[env.Name] = Expand(env.Value, mapping) declaredEnv[env.Name] = Expand(env.Value, mapping)

View File

@@ -46,15 +46,6 @@ func (m ResMap) GetMatchingIds(matches IdMatcher) []resid.ResId {
return result return result
} }
// DemandOneGvknMatchForId find the matched resource by Group/Version/Kind and Name
func (m ResMap) DemandOneGvknMatchForId(inputId resid.ResId) (*resource.Resource, bool) {
result := m.GetMatchingIds(inputId.GvknEquals)
if len(result) == 1 {
return m[result[0]], true
}
return nil, false
}
// EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`. // EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
func (m ResMap) EncodeAsYaml() ([]byte, error) { func (m ResMap) EncodeAsYaml() ([]byte, error) {
var ids []resid.ResId var ids []resid.ResId

View File

@@ -92,35 +92,34 @@ func TestDemandOneGvknMatchForId(t *testing.T) {
}), }),
} }
_, ok := rm1.DemandOneGvknMatchForId( result := rm1.GetMatchingIds(
resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix1", "ns1")) resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix1", "ns1").GvknEquals)
if !ok { if len(result) != 1 {
t.Fatal("Expected single map entry but got none") t.Fatalf("Expected single map entry but got %v", result)
} }
// confirm that ns and prefix are not included in match // confirm that ns and prefix are not included in match
_, ok = rm1.DemandOneGvknMatchForId( result = rm1.GetMatchingIds(
resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix", "ns")) resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix", "ns").GvknEquals)
if !ok { if len(result) != 1 {
t.Fatal("Expected single map entry but got none") t.Fatalf("Expected single map entry but got %v", result)
} }
// confirm that name is matched correctly // confirm that name is matched correctly
result, ok := rm1.DemandOneGvknMatchForId( result = rm1.GetMatchingIds(
resid.NewResIdWithPrefixNamespace(cmap, "cm3", "prefix1", "ns1")) resid.NewResIdWithPrefixNamespace(cmap, "cm3", "prefix1", "ns1").GvknEquals)
if ok { if len(result) > 0 {
t.Fatalf("Expected no map entries but got %v", result) t.Fatalf("Expected no map entries but got %v", result)
} }
cmap2 := gvk.Gvk{Version: "v2", Kind: "ConfigMap"} cmap2 := gvk.Gvk{Version: "v2", Kind: "ConfigMap"}
// confirm that gvk is matched correctly // confirm that gvk is matched correctly
result, ok = rm1.DemandOneGvknMatchForId( result = rm1.GetMatchingIds(
resid.NewResIdWithPrefixNamespace(cmap2, "cm2", "prefix1", "ns1")) resid.NewResIdWithPrefixNamespace(cmap2, "cm2", "prefix1", "ns1").GvknEquals)
if ok { if len(result) > 0 {
t.Fatalf("Expected no map entries but got %v", result) t.Fatalf("Expected no map entries but got %v", result)
} }
} }
func TestFilterBy(t *testing.T) { func TestFilterBy(t *testing.T) {

View File

@@ -18,7 +18,6 @@ package target
import ( import (
"fmt" "fmt"
"log"
"sigs.k8s.io/kustomize/pkg/resid" "sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap" "sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/transformers" "sigs.k8s.io/kustomize/pkg/transformers"
@@ -101,17 +100,27 @@ func (ra *ResAccumulator) MergeAccumulator(other *ResAccumulator) (err error) {
// for substitution wherever the $(var.Name) occurs. // for substitution wherever the $(var.Name) occurs.
func (ra *ResAccumulator) makeVarReplacementMap() (map[string]string, error) { func (ra *ResAccumulator) makeVarReplacementMap() (map[string]string, error) {
result := map[string]string{} result := map[string]string{}
for _, v := range ra.varSet.Set() { for _, v := range ra.Vars() {
id := resid.NewResId(v.ObjRef.GVK(), v.ObjRef.Name) matched := ra.resMap.GetMatchingIds(
if r, found := ra.resMap.DemandOneGvknMatchForId(id); found { resid.NewResId(v.ObjRef.GVK(), v.ObjRef.Name).GvknEquals)
s, err := r.GetFieldValue(v.FieldRef.FieldPath) if len(matched) > 1 {
return nil, fmt.Errorf(
"found %d resId matches for var %s "+
"(unable to disambiguate)",
len(matched), v)
}
if len(matched) == 1 {
s, err := ra.resMap[matched[0]].GetFieldValue(v.FieldRef.FieldPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("field path err for var: %+v", v) return nil, fmt.Errorf(
"field specified in var '%v' "+
"not found in corresponding resource", v)
} }
result[v.Name] = s result[v.Name] = s
} else { } else {
// Should this be an error? return nil, fmt.Errorf(
log.Printf("var %v defined but not used", v) "var '%v' cannot be mapped to a field "+
"in the set of known resources", v)
} }
} }
return result, nil return result, nil

View File

@@ -30,11 +30,11 @@ import (
"sigs.k8s.io/kustomize/pkg/types" "sigs.k8s.io/kustomize/pkg/types"
) )
func TestResolveVars(t *testing.T) { func makeResAccumulator() (*ResAccumulator, *resource.Factory, error) {
ra := MakeEmptyAccumulator() ra := MakeEmptyAccumulator()
err := ra.MergeConfig(config.MakeDefaultConfig()) err := ra.MergeConfig(config.MakeDefaultConfig())
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) return nil, nil, err
} }
rf := resource.NewFactory( rf := resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl()) kunstruct.NewKunstructuredFactoryImpl())
@@ -55,8 +55,8 @@ func TestResolveVars(t *testing.T) {
map[string]interface{}{ map[string]interface{}{
"command": []interface{}{ "command": []interface{}{
"myserver", "myserver",
"--somebackendService $(FOO)", "--somebackendService $(SERVICE_ONE)",
"--yetAnother $(BAR)", "--yetAnother $(SERVICE_TWO)",
}, },
}, },
}, },
@@ -85,14 +85,23 @@ func TestResolveVars(t *testing.T) {
}, },
}), }),
}) })
return ra, rf, nil
}
func TestResolveVarsHappy(t *testing.T) {
ra, _, err := makeResAccumulator()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
err = ra.MergeVars([]types.Var{ err = ra.MergeVars([]types.Var{
{ {
Name: "FOO", Name: "SERVICE_ONE",
ObjRef: types.Target{ ObjRef: types.Target{
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"}, Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
Name: "backendOne"}, Name: "backendOne"},
}, { },
Name: "BAR", {
Name: "SERVICE_TWO",
ObjRef: types.Target{ ObjRef: types.Target{
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"}, Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
Name: "backendTwo"}, Name: "backendTwo"},
@@ -111,6 +120,101 @@ func TestResolveVars(t *testing.T) {
} }
} }
func TestResolveVarsVarNeedsDisambiguation(t *testing.T) {
ra, rf, err := makeResAccumulator()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
ra.MergeResourcesWithErrorOnIdCollision(resmap.ResMap{
resid.NewResIdWithPrefixNamespace(
gvk.Gvk{Version: "v1", Kind: "Service"},
"backendOne", "", "fooNamespace"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Service",
"metadata": map[string]interface{}{
"name": "backendOne",
},
}),
})
err = ra.MergeVars([]types.Var{
{
Name: "SERVICE_ONE",
ObjRef: types.Target{
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
Name: "backendOne",
},
},
})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
err = ra.ResolveVars()
if err == nil {
t.Fatalf("expected error")
}
if !strings.Contains(
err.Error(), "unable to disambiguate") {
t.Fatalf("unexpected err: %v", err)
}
}
func TestResolveVarsGoodResIdBadField(t *testing.T) {
ra, _, err := makeResAccumulator()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
err = ra.MergeVars([]types.Var{
{
Name: "SERVICE_ONE",
ObjRef: types.Target{
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
Name: "backendOne"},
FieldRef: types.FieldSelector{FieldPath: "nope_nope_nope"},
},
})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
err = ra.ResolveVars()
if err == nil {
t.Fatalf("expected error")
}
if !strings.Contains(
err.Error(),
"not found in corresponding resource") {
t.Fatalf("unexpected err: %v", err)
}
}
func TestResolveVarsUnmappableVar(t *testing.T) {
ra, _, err := makeResAccumulator()
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
err = ra.MergeVars([]types.Var{
{
Name: "SERVICE_THREE",
ObjRef: types.Target{
Gvk: gvk.Gvk{Version: "v1", Kind: "Service"},
Name: "doesNotExist"},
},
})
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
err = ra.ResolveVars()
if err == nil {
t.Fatalf("expected error")
}
if !strings.Contains(
err.Error(),
"cannot be mapped to a field in the set of known resources") {
t.Fatalf("unexpected err: %v", err)
}
}
func find(name string, resMap resmap.ResMap) *resource.Resource { func find(name string, resMap resmap.ResMap) *resource.Resource {
for k, v := range resMap { for k, v := range resMap {
if k.Name() == name { if k.Name() == name {