diff --git a/go.mod b/go.mod index f4df3ac..4ff7b1d 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,8 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +replace helm.sh/helm/v3 => github.com/porridge/helm/v3 v3.9.0-rc.1.0.20231218090257-bfec4ec92622 + require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect @@ -61,7 +63,7 @@ require ( github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/esimonov/ifshort v1.0.4 // indirect github.com/ettle/strcase v0.1.1 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.15.0 // indirect github.com/fatih/structtag v1.2.0 // indirect diff --git a/go.sum b/go.sum index 3710592..876f51f 100644 --- a/go.sum +++ b/go.sum @@ -153,8 +153,8 @@ github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStB github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= @@ -496,6 +496,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.4.5 h1:70YWmMy4FgRHehGNOUask3HtSFSOLKgmDn7ryNe7LqI= github.com/polyfloyd/go-errorlint v1.4.5/go.mod h1:sIZEbFoDOCnTYYZoVkjc4hTnM459tuWA9H/EkdXwsKk= +github.com/porridge/helm/v3 v3.9.0-rc.1.0.20231218090257-bfec4ec92622 h1:YGIMhjCmmj+M4TcuxtgLvUWTqNPUq8kSpZQ/aY3zSpw= +github.com/porridge/helm/v3 v3.9.0-rc.1.0.20231218090257-bfec4ec92622/go.mod h1:YTpTufJ4h/ILV+mKDGRJGUqI3KGj8Z44FK90f6V8KLo= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -1087,8 +1089,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY= -helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/framework/runner.go b/pkg/framework/runner.go index fd64158..e9fd80b 100644 --- a/pkg/framework/runner.go +++ b/pkg/framework/runner.go @@ -23,9 +23,13 @@ import ( "github.com/stretchr/testify/require" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/engine" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" "k8s.io/kubectl/pkg/util/openapi" "k8s.io/kubectl/pkg/validation" k8sYaml "sigs.k8s.io/yaml" @@ -112,7 +116,18 @@ func (r *runner) readAndValidateYAML(fileName, fileContents string, resources op return objs } -func (r *runner) instantiateWorld(renderVals chartutil.Values, resources openapi.Resources) map[string]interface{} { +type clientProviderFromDynamicClient struct { + dynIface dynamic.Interface +} + +func (c clientProviderFromDynamicClient) GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) { + // This is suboptimal, but the best kind->resource translation we can do without a discovery client. + gvr, _ := meta.UnsafeGuessKindToResource(schema.FromAPIVersionAndKind(apiVersion, kind)) + namespaced := true // we infer this from the namespace argument to the lookup function + return c.dynIface.Resource(gvr), namespaced, nil +} + +func (r *runner) instantiateWorld(renderVals chartutil.Values, resources openapi.Resources, objects []runtime.Object) map[string]interface{} { world := make(map[string]interface{}) renderValsBytes, err := json.Marshal(renderVals) @@ -123,9 +138,12 @@ func (r *runner) instantiateWorld(renderVals chartutil.Values, resources openapi if err := json.Unmarshal(renderValsBytes, &helmRenderVals); err != nil { panic(errors.Wrap(err, "unmarshaling Helm render values")) } + + client := fake.NewSimpleDynamicClient(runtime.NewScheme(), objects...) + clientProvider := clientProviderFromDynamicClient{dynIface: client} world["helm"] = helmRenderVals - renderedTemplates, err := (&engine.Engine{}).Render(r.tgt.Chart, renderVals) + renderedTemplates, err := engine.RenderWithClientProvider(r.tgt.Chart, renderVals, clientProvider) if *r.test.ExpectError { r.Require().Error(err, "expected rendering to fail") @@ -263,10 +281,17 @@ func (r *runner) Run() { caps = &newCaps }) + var availableObjects []runtime.Object + r.test.forEachScopeTopDown(func(t *Test) { + for _, o := range t.Objects { + obj := &unstructured.Unstructured{Object: o} + availableObjects = append(availableObjects, obj.DeepCopyObject()) + } + }) + renderVals, err := chartutil.ToRenderValues(r.tgt.Chart, values, releaseOpts, caps) r.Require().NoError(err, "failed to obtain render values") - - world := r.instantiateWorld(renderVals, availableSchemas) + world := r.instantiateWorld(renderVals, availableSchemas, availableObjects) r.evaluatePredicates(world) } diff --git a/pkg/framework/spec.go b/pkg/framework/spec.go index 96ec9df..801ada3 100644 --- a/pkg/framework/spec.go +++ b/pkg/framework/spec.go @@ -20,10 +20,11 @@ type Test struct { Values RawDict `json:"values,omitempty" yaml:"values,omitempty"` Set RawDict `json:"set,omitempty" yaml:"set,omitempty"` - Defs string `json:"defs,omitempty" yaml:"defs,omitempty"` - Release *ReleaseSpec `json:"release,omitempty" yaml:"release,omitempty"` - Server *ServerSpec `json:"server,omitempty" yaml:"server,omitempty"` - Capabilities *CapabilitiesSpec `json:"capabilities,omitempty" yaml:"capabilities,omitempty"` + Defs string `json:"defs,omitempty" yaml:"defs,omitempty"` + Release *ReleaseSpec `json:"release,omitempty" yaml:"release,omitempty"` + Server *ServerSpec `json:"server,omitempty" yaml:"server,omitempty"` + Capabilities *CapabilitiesSpec `json:"capabilities,omitempty" yaml:"capabilities,omitempty"` + Objects []map[string]interface{} `json:"objects,omitempty" yaml:"objects,omitempty"` Expect string `json:"expect,omitempty" yaml:"expect,omitempty"` ExpectError *bool `json:"expectError,omitempty" yaml:"expectError,omitempty"`