Skip to content

Commit c6275d0

Browse files
authored
Ensure mocked kubeversion constraint is sane and configurable in template rendering (#502)
Signed-off-by: Jose R. Gonzalez <[email protected]>
1 parent baeae30 commit c6275d0

File tree

8 files changed

+132
-7
lines changed

8 files changed

+132
-7
lines changed

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,22 @@ fmt: install.gofumpt
3434
${GOFUMPT} -l -w .
3535
git diff --exit-code
3636

37+
38+
# These values capture client-go's supported Kubernetes version and uses that to
39+
# inform some sane defaults for the chart-verifier CLI, particularly when faking server
40+
# interactions for things like template rendering. It's modeled after Helm.
41+
K8S_MODULES_VER=$(subst ., ,$(subst v,,$(shell go list -f '{{.Version}}' -m k8s.io/client-go)))
42+
K8S_MODULES_MAJOR_VER=$(shell echo $$(($(firstword $(K8S_MODULES_VER)) + 1)))
43+
K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER))
44+
45+
LDFLAGS :=
46+
LDFLAGS += -X github.com/redhat-certification/chart-verifier/cmd.CommitIDLong=$(COMMIT_ID_LONG)
47+
LDFLAGS += -X github.com/redhat-certification/chart-verifier/internal/chartverifier/checks.defaultMockedKubeVersionString=v$(K8S_MODULES_MAJOR_VER).$(K8S_MODULES_MINOR_VER)
48+
3749
.PHONY: bin
3850
bin:
3951
CGO_ENABLED=0 go build \
40-
-ldflags "-X 'github.com/redhat-certification/chart-verifier/cmd.CommitIDLong=$(COMMIT_ID_LONG)'" \
52+
-ldflags '$(LDFLAGS)' \
4153
-o ./out/chart-verifier main.go
4254

4355
.PHONY: lint

docs/helm-chart-checks.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ Helm chart checks are a set of checks against which the Red Hat Helm chart-verif
1414
- [Using the podman or docker command for Helm chart checks](#using-the-podman-or-docker-command-for-helm-chart-checks)
1515
- [Prerequisites](#prerequisites)
1616
- [Procedure](#procedure)
17-
- [Check processing](#check-processing)
18-
- [Chart testing timeouts](#chart-testing-timeouts)
1917
- [Signed charts](#signed-charts)
2018

2119
## Key features
@@ -513,6 +511,32 @@ Notes:
513511
- The [helm chart certification process](./helm-chart-submission.md#submission-of-helm-charts-for-red-hat-openShift-certification) uses default timeout values.
514512
- If a helm chart can only pass the chart testing check with modified timeouts a verifier report must be included in the chart submission.
515513
514+
## Images are Certified
515+
516+
The **images-are-certified** check must be able to render your templates in
517+
order to extract image references. In that process, your chart's kubeVersion
518+
constraint will be evaluated against a "server" version. No server is required
519+
for this check, and Chart Verifier will mock out the server version information
520+
for you (as is also done via `helm template`).
521+
522+
However, you may find that Chart Verifier mocks out server version that does not
523+
fall within your chart's constraints. In most cases, this will happen if you
524+
have an upper bound on your supported kubeVersion (e.g. `<= v1.30.0`).
525+
526+
If this is the case, you can override the mocked version
527+
string to another valid semantic version that falls within your constraints.
528+
This is done using the `--set images-are-certified.kube-version=<yourversion>`
529+
flag.
530+
531+
For example, to set the kube-version value for this check to `v1.29`:
532+
533+
```shell
534+
$ chart-verifier \
535+
verify \
536+
--enable images-are-certified \
537+
--set images-are-certified.kube-version=v1.29 \
538+
some-chart.tgz
539+
```
516540

517541
## Signed charts
518542

Binary file not shown.

internal/chartverifier/checks/checks.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,10 +482,34 @@ func downloadFile(fileURL *url.URL, directory string) (string, error) {
482482
return filePath, nil
483483
}
484484

485+
// defaultMockedKubeVersionString represents a baseline "kubeVersion" for
486+
// use in rendering templates without actually talking to a server.
487+
//
488+
// Rendering templates doesn't technically require a server, but the
489+
// rendering process used by upstream libraries still does a compatibility
490+
// check between Chart.yaml kubeVersion constraints and a mocked server
491+
// version.
492+
//
493+
// For this reason, we set this version expected to be "newer than all
494+
// versions" to satisfy a majority of cases. For everything else, users
495+
// should set `--set images-are-certified.kube-version=<version>` to match
496+
// their constraints.
497+
//
498+
// Our built binaries will default this to the current k8s version supported
499+
// by client.go.
500+
var defaultMockedKubeVersionString = "v99.99"
501+
485502
func certifyImages(r Result, opts *CheckOptions, registry string) Result {
486-
images, err := getImageReferences(opts.URI, opts.Values)
503+
kubeVersionString := defaultMockedKubeVersionString
504+
505+
if userKubeVersion := opts.ViperConfig.GetString("kube-version"); userKubeVersion != "" {
506+
kubeVersionString = userKubeVersion
507+
}
508+
509+
images, err := getImageReferences(opts.URI, opts.Values, kubeVersionString)
487510
if err != nil {
488511
r.SetResult(false, fmt.Sprintf("%s : Failed to get images, error running helm template : %v", ImageCertifyFailed, err))
512+
return r
489513
}
490514

491515
if len(images) == 0 {

internal/chartverifier/checks/checks_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,42 @@ func TestHelmLint(t *testing.T) {
403403
}
404404
}
405405

406+
func TestImageCertifyKubeVersionConstraints(t *testing.T) {
407+
specificKubeVersion := "v1.21.0"
408+
tests := []struct {
409+
description string
410+
uri string
411+
optionalKubeVersionString *string
412+
outputIsValidFunc func(s string) bool
413+
}{
414+
{
415+
description: "KubeVersion mismatch between default and chart should fail to run image checks",
416+
uri: "chart-0.1.0-v3.mocked-kubeversion-constraint-mismatch.tgz",
417+
outputIsValidFunc: func(s string) bool {
418+
return strings.Contains(s, ImageCertifyFailed) && strings.Contains(s, "which is incompatible with Kubernetes")
419+
},
420+
},
421+
{
422+
description: "KubeVersion should align when --set is used to set a kubeversion",
423+
uri: "chart-0.1.0-v3.mocked-kubeversion-constraint-mismatch.tgz",
424+
outputIsValidFunc: func(s string) bool {
425+
return strings.Contains(s, ImageCertified)
426+
},
427+
optionalKubeVersionString: &specificKubeVersion,
428+
},
429+
}
430+
431+
for _, test := range tests {
432+
v := viper.New()
433+
if test.optionalKubeVersionString != nil {
434+
v.Set("kube-version", *test.optionalKubeVersionString)
435+
}
436+
// error is ignored because this fn never returns an error.
437+
result, _ := ImagesAreCertified_V1_1(&CheckOptions{URI: test.uri, ViperConfig: v, HelmEnvSettings: cli.New()})
438+
require.True(t, test.outputIsValidFunc(result.Reason), test.description)
439+
}
440+
}
441+
406442
func TestImageCertify(t *testing.T) {
407443
checkImages(t)
408444
}
@@ -415,6 +451,7 @@ func checkImages(t *testing.T) {
415451
numErrors int
416452
numPasses int
417453
numSkips int
454+
numFailures int
418455
}{
419456
{
420457
description: "chart-0.1.0-v3.valid.tgz check images passes",

internal/chartverifier/checks/helm.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,34 @@ func IsChartNotFound(err error) bool {
189189
return ok
190190
}
191191

192-
func getImageReferences(chartURI string, vals map[string]interface{}) ([]string, error) {
192+
// getImageReferences renders the templates for chartURI and extracts
193+
// imageReferences from the template output, using vals as necessaary.
194+
//
195+
// Note that template rendering doesn't technically need a remote cluster, but
196+
// the chart's constraints are still validated against mocked cluster
197+
// information. For this reaosn, serverKubeVersionString must produce a valid
198+
// semantic version corresponding to a kubeVersion within the chart's
199+
// constraints as defined in Chart.yaml.
200+
func getImageReferences(chartURI string, vals map[string]interface{}, serverKubeVersionString string) ([]string, error) {
201+
// We'll start with DefaultCapabilities, but we'll really only use the
202+
// kubeVersion of this when rendering manifests because Helm replaces the
203+
// action config's capabilities for client-only execution.
204+
caps := chartutil.DefaultCapabilities.Copy()
205+
206+
kubeVersion, err := chartutil.ParseKubeVersion(serverKubeVersionString)
207+
if err != nil {
208+
return nil, fmt.Errorf("%s: %w", "unable to render manifests and extract images due to invalid kubeVersion in server capabilities", err)
209+
}
210+
211+
caps.KubeVersion = *kubeVersion
212+
193213
actionConfig := &action.Configuration{
194214
Releases: nil,
195215
KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
196-
Capabilities: chartutil.DefaultCapabilities,
216+
Capabilities: caps,
197217
Log: func(format string, v ...interface{}) {},
198218
}
219+
199220
mem := driver.NewMemory()
200221
mem.SetNamespace("TestNamespace")
201222
actionConfig.Releases = storage.Init(mem)

internal/chartverifier/checks/helm_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ func TestTemplate(t *testing.T) {
167167

168168
for _, tc := range TestCases {
169169
t.Run(tc.description, func(t *testing.T) {
170-
images, err := getImageReferences(tc.uri, map[string]interface{}{})
170+
images, err := getImageReferences(tc.uri, map[string]interface{}{}, defaultMockedKubeVersionString)
171171
require.NoError(t, err)
172172
require.Equal(t, len(images), len(tc.images))
173173
for i := 0; i < len(tc.images); i++ {

internal/helm/actions/template.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ func RenderManifests(name string, url string, vals map[string]interface{}, conf
2525
client.ClientOnly = !validate
2626
emptyResponse := ""
2727

28+
// Must set the capabilities on *action.Install{}.KubeVersion directly
29+
// because Helm will replace our capabilities with the defaults they
30+
// configure.
31+
if conf.Capabilities != nil {
32+
client.KubeVersion = &conf.Capabilities.KubeVersion
33+
}
34+
2835
name, chart, err := client.NameAndChart([]string{name, url})
2936
if err != nil {
3037
return emptyResponse, err

0 commit comments

Comments
 (0)