diff --git a/go.mod b/go.mod index b96124ac2..044e6547e 100644 --- a/go.mod +++ b/go.mod @@ -44,12 +44,9 @@ require ( sigs.k8s.io/cli-utils v0.20.6 sigs.k8s.io/cluster-api v0.3.10 sigs.k8s.io/controller-runtime v0.5.11 - sigs.k8s.io/kustomize/api v0.5.1 - sigs.k8s.io/kustomize/kyaml v0.9.2 + sigs.k8s.io/kustomize/api v0.6.5 + sigs.k8s.io/kustomize/kyaml v0.10.0 sigs.k8s.io/yaml v1.2.0 ) -replace ( - k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd - sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.4.1 -) +replace k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd diff --git a/go.sum b/go.sum index 46f6995da..418378984 100644 --- a/go.sum +++ b/go.sum @@ -397,10 +397,14 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -485,6 +489,7 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -523,6 +528,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= @@ -790,7 +797,6 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= @@ -896,6 +902,7 @@ golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -974,7 +981,6 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo= gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= @@ -1077,10 +1083,14 @@ sigs.k8s.io/controller-runtime v0.5.11/go.mod h1:OTqxLuz7gVcrq+BHGUgedRu6b2VIKCE sigs.k8s.io/kind v0.7.1-0.20200303021537-981bd80d3802/go.mod h1:HIZ3PWUezpklcjkqpFbnYOqaqsAE1JeCTEwkgvPLXjk= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= -sigs.k8s.io/kustomize/api v0.5.1 h1:iHGTs5LcnJGqHstUSxWD/kX6XZgmd82x79LLlZwDU0I= -sigs.k8s.io/kustomize/api v0.5.1/go.mod h1:LGqJ9ZWOnWDqlECqrFgNUyEqSJc6ooA9ZiWZ4KFZv+I= -sigs.k8s.io/kustomize/kyaml v0.4.1 h1:NEqA/35upoAjb+I5vh1ODUqxoX4DOrezeQa9BhhG5Co= -sigs.k8s.io/kustomize/kyaml v0.4.1/go.mod h1:XJL84E6sOFeNrQ7CADiemc1B0EjIxHo3OhW4o1aJYNw= +sigs.k8s.io/kustomize/api v0.6.5 h1:xaAWZamIhpt9Y5Kn/vuBcBhZH8/m0zwew1d4HepIgXg= +sigs.k8s.io/kustomize/api v0.6.5/go.mod h1:Z96Z48h3nOWgVAmd4JGABszi5znhEnz7xoWHy+Bl7L4= +sigs.k8s.io/kustomize/kyaml v0.9.2 h1:QNP1Lg4V2wOgBeUim9Kmz1+2GqHtRyfoVEUQH0omrCI= +sigs.k8s.io/kustomize/kyaml v0.9.2/go.mod h1:UTm64bSWVdBUA8EQoYCxVOaBQxUdIOr5LKWxA4GNbkw= +sigs.k8s.io/kustomize/kyaml v0.9.4 h1:DDuzZtjIzFqp2IPy4DTyCI69Cl3bDgcJODjI6sjF9NY= +sigs.k8s.io/kustomize/kyaml v0.9.4/go.mod h1:UTm64bSWVdBUA8EQoYCxVOaBQxUdIOr5LKWxA4GNbkw= +sigs.k8s.io/kustomize/kyaml v0.10.0 h1:khdTVOtvXRiIXYRh0bPXMlxGjHI+5MAh7v//dw2HIzw= +sigs.k8s.io/kustomize/kyaml v0.10.0/go.mod h1:UTm64bSWVdBUA8EQoYCxVOaBQxUdIOr5LKWxA4GNbkw= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= diff --git a/pkg/api/v1alpha1/genericcontainer_types.go b/pkg/api/v1alpha1/genericcontainer_types.go index b03f3a348..d0ef9ef2f 100644 --- a/pkg/api/v1alpha1/genericcontainer_types.go +++ b/pkg/api/v1alpha1/genericcontainer_types.go @@ -54,6 +54,12 @@ func (in *GenericContainer) DeepCopyInto(out *GenericContainer) { *out = make([]runtimeutil.StorageMount, len(*in)) copy(*out, *in) } + if in.Spec.Container.Env != nil { + in, out := &in.Spec.Container.Env, &out.Spec.Container.Env + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Spec.Starlark = in.Spec.Starlark out.Spec.Exec = in.Spec.Exec if in.Spec.StorageMounts != nil { diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index 0b38330ae..17fd49cbc 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -19,7 +19,7 @@ package v1alpha1 import ( - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) diff --git a/pkg/bootstrap/cloudinit/testdata/networkdatamalformed.yaml b/pkg/bootstrap/cloudinit/testdata/networkdatamalformed.yaml index 2515fa737..b2733d2ea 100644 --- a/pkg/bootstrap/cloudinit/testdata/networkdatamalformed.yaml +++ b/pkg/bootstrap/cloudinit/testdata/networkdatamalformed.yaml @@ -14,11 +14,11 @@ stringData: --- apiVersion: v1 kind: Secret -namespace: malformed metadata: labels: test: networkdatamalformed name: networkdatamalformed-malformed + namespace: malformed type: Opaque stringData: no-net-data-key: the required 'net-data' key is missing diff --git a/pkg/bootstrap/cloudinit/testdata/validdocset.yaml b/pkg/bootstrap/cloudinit/testdata/validdocset.yaml index 28afaa74e..de63c0d1b 100644 --- a/pkg/bootstrap/cloudinit/testdata/validdocset.yaml +++ b/pkg/bootstrap/cloudinit/testdata/validdocset.yaml @@ -28,11 +28,11 @@ stringData: --- apiVersion: v1 kind: Secret -namespace: metal3 metadata: labels: test: validdocset name: master-1-networkdata + namespace: metal3 type: Opaque stringData: networkData: net-config diff --git a/pkg/bootstrap/isogen/mock_document_test.go b/pkg/bootstrap/isogen/mock_document_test.go index 3882a1f10..5ea7f3fbd 100644 --- a/pkg/bootstrap/isogen/mock_document_test.go +++ b/pkg/bootstrap/isogen/mock_document_test.go @@ -40,6 +40,7 @@ type MockDocument struct { MockMarshalJSON func() ([]byte, error) MockToObject func() error MockToAPIObject func() error + MockGetFieldValue func() (interface{}, error) } func (md *MockDocument) Annotate(_ map[string]string) { @@ -62,6 +63,10 @@ func (md *MockDocument) GetFloat64(_ string) (float64, error) { return md.MockGetFloat64() } +func (md *MockDocument) GetFieldValue(_ string) (interface{}, error) { + return md.MockGetFieldValue() +} + func (md *MockDocument) GetGroup() string { return md.MockGetGroup() } diff --git a/pkg/bootstrap/isogen/testdata/primary/site/test-site/ephemeral/bootstrap/secret.yaml b/pkg/bootstrap/isogen/testdata/primary/site/test-site/ephemeral/bootstrap/secret.yaml index 28afaa74e..de1e58dbe 100644 --- a/pkg/bootstrap/isogen/testdata/primary/site/test-site/ephemeral/bootstrap/secret.yaml +++ b/pkg/bootstrap/isogen/testdata/primary/site/test-site/ephemeral/bootstrap/secret.yaml @@ -28,11 +28,11 @@ stringData: --- apiVersion: v1 kind: Secret -namespace: metal3 metadata: labels: test: validdocset name: master-1-networkdata + namespace: metal3 type: Opaque stringData: networkData: net-config @@ -63,4 +63,4 @@ spec: credentialsName: master-1-bmc networkData: name: master-1-networkdata - namespace: metal3 \ No newline at end of file + namespace: metal3 diff --git a/pkg/container/executor_test.go b/pkg/container/executor_test.go index 426d765b3..45bfb15f8 100644 --- a/pkg/container/executor_test.go +++ b/pkg/container/executor_test.go @@ -83,8 +83,8 @@ object: defaultMode: 0777 metadata: annotations: - config.kubernetes.io/function: "container:\n image: quay.io/test/image:v0.0.1\n - \ network: {}\nexec: {}\nstarlark: {}\n" + config.kubernetes.io/function: "container:\n image: quay.io/test/image:v0.0.1\nexec: + {}\nstarlark: {}\n" ` singleExecutorBundlePath = "testdata/single" firstDocInput = `--- diff --git a/pkg/document/document.go b/pkg/document/document.go index 5da9bba4f..6edb57140 100644 --- a/pkg/document/document.go +++ b/pkg/document/document.go @@ -15,6 +15,8 @@ package document import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer/json" @@ -35,6 +37,7 @@ type Document interface { GetAnnotations() map[string]string GetBool(path string) (bool, error) GetFloat64(path string) (float64, error) + GetFieldValue(path string) (interface{}, error) GetGroup() string GetInt64(path string) (int64, error) GetKind() string @@ -96,26 +99,58 @@ func (d *Factory) GetString(path string) (string, error) { // GetStringSlice returns a string slice at path. func (d *Factory) GetStringSlice(path string) ([]string, error) { - r := d.GetKustomizeResource() - return r.GetStringSlice(path) + slice, err := d.GetSlice(path) + if err != nil { + return nil, err + } + lst := make([]string, len(slice)) + for i, val := range slice { + str, ok := val.(string) + if !ok { + return nil, ErrBadValueFormat{Value: path, Expected: "[]string", Actual: fmt.Sprintf("%T", val)} + } + lst[i] = str + } + return lst, nil } // GetBool returns a bool at path. func (d *Factory) GetBool(path string) (bool, error) { - r := d.GetKustomizeResource() - return r.GetBool(path) + val, err := d.GetFieldValue(path) + if err != nil { + return false, err + } + result, ok := val.(bool) + if !ok { + return false, ErrBadValueFormat{Value: path, Expected: "bool", Actual: fmt.Sprintf("%T", val)} + } + return result, nil } // GetFloat64 returns a float64 at path. func (d *Factory) GetFloat64(path string) (float64, error) { - r := d.GetKustomizeResource() - return r.GetFloat64(path) + val, err := d.GetFieldValue(path) + if err != nil { + return 0, err + } + result, ok := val.(float64) + if !ok { + return 0, ErrBadValueFormat{Value: path, Expected: "float64", Actual: fmt.Sprintf("%T", val)} + } + return result, nil } // GetInt64 returns an int64 at path. func (d *Factory) GetInt64(path string) (int64, error) { - r := d.GetKustomizeResource() - return r.GetInt64(path) + val, err := d.GetFieldValue(path) + if err != nil { + return 0, err + } + result, ok := val.(int64) + if !ok { + return 0, ErrBadValueFormat{Value: path, Expected: "int64", Actual: fmt.Sprintf("%T", val)} + } + return result, nil } // GetSlice returns a slice at path. @@ -126,14 +161,32 @@ func (d *Factory) GetSlice(path string) ([]interface{}, error) { // GetStringMap returns a string map at path. func (d *Factory) GetStringMap(path string) (map[string]string, error) { - r := d.GetKustomizeResource() - return r.GetStringMap(path) + val, err := d.GetMap(path) + if err != nil { + return nil, err + } + strMap := make(map[string]string, len(val)) + for k, v := range val { + str, ok := v.(string) + if !ok { + return nil, ErrBadValueFormat{Value: path, Expected: "map[string]string", Actual: fmt.Sprintf("%T", val)} + } + strMap[k] = str + } + return strMap, nil } // GetMap returns a map at path. func (d *Factory) GetMap(path string) (map[string]interface{}, error) { - r := d.GetKustomizeResource() - return r.GetMap(path) + val, err := d.GetFieldValue(path) + if err != nil { + return nil, err + } + result, ok := val.(map[string]interface{}) + if !ok { + return nil, ErrBadValueFormat{Value: path, Expected: "map[string]interface{}", Actual: fmt.Sprintf("%T", val)} + } + return result, nil } // AsYAML returns the document as a YAML byte stream. @@ -209,6 +262,12 @@ func (d *Factory) ToAPIObject(obj runtime.Object, scheme *runtime.Scheme) error return err } +// GetFieldValue get object at path +func (d *Factory) GetFieldValue(path string) (interface{}, error) { + r := d.GetKustomizeResource() + return r.GetFieldValue(path) +} + // NewDocument is a convenience method to construct a new Document. Although // an error is unlikely at this time, this provides some future proofing for // when we want more strict airship specific validation of documents getting diff --git a/pkg/document/errors.go b/pkg/document/errors.go index 70cb0c789..f66a07a41 100644 --- a/pkg/document/errors.go +++ b/pkg/document/errors.go @@ -49,6 +49,13 @@ type ErrRuntimeObjectKind struct { Obj runtime.Object } +// ErrBadValueFormat returned if wrong field type requested +type ErrBadValueFormat struct { + Value string + Expected string + Actual string +} + func (e ErrDocNotFound) Error() string { return fmt.Sprintf("document filtered by selector %v found no documents", e.Selector) } @@ -68,3 +75,7 @@ func (e ErrDocumentMalformed) Error() string { func (e ErrRuntimeObjectKind) Error() string { return fmt.Sprintf("object %#v has either none or multiple kinds in scheme (expected one)", e.Obj) } + +func (e ErrBadValueFormat) Error() string { + return fmt.Sprintf("value of %s expected to have %s type, got %s", e.Value, e.Expected, e.Actual) +}