Skip to content

Commit 31a7fde

Browse files
authored
Add support for testing Helm lookup function. (#82)
Also some documentation fixes.
1 parent 80ce849 commit 31a7fde

6 files changed

Lines changed: 52 additions & 9 deletions

File tree

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,23 @@ release: # Overrides for the Helm release properties. These are applied in root
2626
isInstall: bool # override for the "IsInstall" property of the release options
2727
isUpgrade: bool # override for the "IsUpgrade" property of the release options
2828
server:
29-
visibleSchema: # openAPI schema which is visible to helm, i.e. to check API resource availability
29+
visibleSchemas: # openAPI schema which is visible to helm, i.e. to check API resource availability
3030
# all valid schemas are:
3131
- kubernetes-1.20.2
3232
- openshift-3.11.0
3333
- openshift-4.1.0
3434
- com.coreos
3535
availableSchemas: [] # openAPI schema to validate against, i.e. to validate if rendered objects could be applied
36+
objects: # objects visible to Helm's k8s client, for example via the `lookup` function
37+
# example object specification:
38+
- apiVersion: string
39+
kind: string
40+
metadata:
41+
name: string
42+
namespace: string # optional for cluster-scoped objects
43+
noInherit: bool # indicates that server-side settings should *not* be inherited from the enclosing scope
44+
capabilities: # represents the .Capabilities in Helm
45+
kubeVersion: string # the kubernetes version which is discoverable via `.Capabilities.KubeVersion`
3646
values: # values as consumed by Helm via the `-f` CLI flag.
3747
key: value
3848
set: # alternative format for Helm values, as consumed via the `--set` CLI flag.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
gopkg.in/yaml.v3 v3.0.1
1212
helm.sh/helm/v3 v3.14.0
1313
k8s.io/apimachinery v0.29.1
14+
k8s.io/client-go v0.29.1
1415
k8s.io/kube-openapi v0.0.0-20240105020646-a37d4de58910
1516
k8s.io/kubectl v0.29.1
1617
k8s.io/utils v0.0.0-20240102154912-e7106e64919e
@@ -239,7 +240,6 @@ require (
239240
k8s.io/api v0.29.1 // indirect
240241
k8s.io/apiextensions-apiserver v0.29.0 // indirect
241242
k8s.io/cli-runtime v0.29.1 // indirect
242-
k8s.io/client-go v0.29.1 // indirect
243243
k8s.io/klog/v2 v2.110.1 // indirect
244244
mvdan.cc/gofumpt v0.5.0 // indirect
245245
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect

integration_test/testdata/helmtest/suite.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ server:
44

55
tests:
66
- name: "Expect secret to be rendered on upgrades"
7+
server:
8+
objects:
9+
- apiVersion: test.stackrox.io
10+
kind: FakeResource
11+
metadata:
12+
name: example-fr
13+
namespace: loadbalancer
714
release:
815
isUpgrade: true
916
expect: |
1017
.secrets["some-secret-on-upgrade"] | assertThat(. != null)
18+
.notesRaw | assertThat(. | contains("BTW, lookup saw 1 FakeResource"))

integration_test/testdata/nginx-example/templates/NOTES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@
2020
echo "Visit http://127.0.0.1:8080 to use your application"
2121
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
2222
{{- end }}
23+
24+
BTW, lookup saw {{ (lookup "test.stackrox.io" "FakeResource" .Release.Namespace "").items | len }} FakeResource

pkg/framework/runner.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ import (
2323
"github.com/stretchr/testify/require"
2424
"helm.sh/helm/v3/pkg/chartutil"
2525
"helm.sh/helm/v3/pkg/engine"
26+
"k8s.io/apimachinery/pkg/api/meta"
2627
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2728
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/runtime/schema"
2830
"k8s.io/apimachinery/pkg/util/yaml"
31+
"k8s.io/client-go/dynamic"
32+
"k8s.io/client-go/dynamic/fake"
2933
"k8s.io/kubectl/pkg/util/openapi"
3034
"k8s.io/kubectl/pkg/validation"
3135
k8sYaml "sigs.k8s.io/yaml"
@@ -112,7 +116,18 @@ func (r *runner) readAndValidateYAML(fileName, fileContents string, resources op
112116
return objs
113117
}
114118

115-
func (r *runner) instantiateWorld(renderVals chartutil.Values, resources openapi.Resources) map[string]interface{} {
119+
type clientProviderFromDynamicClient struct {
120+
dynIface dynamic.Interface
121+
}
122+
123+
func (c clientProviderFromDynamicClient) GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) {
124+
// This is suboptimal, but the best kind->resource translation we can do without a discovery client.
125+
gvr, _ := meta.UnsafeGuessKindToResource(schema.FromAPIVersionAndKind(apiVersion, kind))
126+
namespaced := true // we infer this from the namespace argument to the lookup function
127+
return c.dynIface.Resource(gvr), namespaced, nil
128+
}
129+
130+
func (r *runner) instantiateWorld(renderVals chartutil.Values, resources openapi.Resources, objects []runtime.Object) map[string]interface{} {
116131
world := make(map[string]interface{})
117132

118133
renderValsBytes, err := json.Marshal(renderVals)
@@ -123,9 +138,12 @@ func (r *runner) instantiateWorld(renderVals chartutil.Values, resources openapi
123138
if err := json.Unmarshal(renderValsBytes, &helmRenderVals); err != nil {
124139
panic(errors.Wrap(err, "unmarshaling Helm render values"))
125140
}
141+
142+
client := fake.NewSimpleDynamicClient(runtime.NewScheme(), objects...)
143+
clientProvider := clientProviderFromDynamicClient{dynIface: client}
126144
world["helm"] = helmRenderVals
127145

128-
renderedTemplates, err := (&engine.Engine{}).Render(r.tgt.Chart, renderVals)
146+
renderedTemplates, err := engine.RenderWithClientProvider(r.tgt.Chart, renderVals, clientProvider)
129147

130148
if *r.test.ExpectError {
131149
r.Require().Error(err, "expected rendering to fail")
@@ -177,7 +195,7 @@ func (r *runner) instantiateWorld(renderVals chartutil.Values, resources openapi
177195
return world
178196
}
179197

180-
func (r *runner) loadSchemas() (visible, available schemas.Schemas) {
198+
func (r *runner) loadServerSettings() (visible, available schemas.Schemas, objects []runtime.Object) {
181199
var visibleSchemaNames, availableSchemaNames []string
182200
r.test.forEachScopeTopDown(func(t *Test) {
183201
server := t.Server
@@ -198,6 +216,10 @@ func (r *runner) loadSchemas() (visible, available schemas.Schemas) {
198216
// Every visible schema is also available (but not vice versa)
199217
availableSchemaNames = append(availableSchemaNames, schemaName)
200218
}
219+
for _, o := range server.Objects {
220+
obj := &unstructured.Unstructured{Object: o}
221+
objects = append(objects, obj.DeepCopyObject())
222+
}
201223
})
202224

203225
availableSchemaNames = sliceutils.StringUnique(availableSchemaNames)
@@ -219,7 +241,7 @@ func (r *runner) loadSchemas() (visible, available schemas.Schemas) {
219241
visible = append(visible, schema)
220242
}
221243

222-
return visible, available
244+
return visible, available, objects
223245
}
224246

225247
func (r *runner) Run() {
@@ -241,7 +263,7 @@ func (r *runner) Run() {
241263
rel.apply(&releaseOpts)
242264
})
243265

244-
visibleSchemas, availableSchemas := r.loadSchemas()
266+
visibleSchemas, availableSchemas, availableObjects := r.loadServerSettings()
245267

246268
caps := r.tgt.Capabilities
247269
if caps == nil {
@@ -265,8 +287,7 @@ func (r *runner) Run() {
265287

266288
renderVals, err := chartutil.ToRenderValues(r.tgt.Chart, values, releaseOpts, caps)
267289
r.Require().NoError(err, "failed to obtain render values")
268-
269-
world := r.instantiateWorld(renderVals, availableSchemas)
290+
world := r.instantiateWorld(renderVals, availableSchemas, availableObjects)
270291
r.evaluatePredicates(world)
271292
}
272293

pkg/framework/spec.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ type ServerSpec struct {
5858
// VisibleSchemas are the names of schemas that are available on the server AND discoverable via
5959
// `.Capabilities.APIVersions`.
6060
VisibleSchemas []string `json:"visibleSchemas,omitempty" yaml:"visibleSchemas,omitempty"`
61+
// Objects are definitions of objects visible to Helm's k8s client, for example via the `lookup` function.
62+
Objects []map[string]interface{} `json:"objects,omitempty" yaml:"objects,omitempty"`
6163

6264
// NoInherit indicates that server-side settings should *not* be inherited from the enclosing scope.
6365
NoInherit bool `json:"noInherit,omitempty" yaml:"noInherit,omitempty"`

0 commit comments

Comments
 (0)