Skip to content

Commit 28c0f43

Browse files
authored
Merge pull request #1378 from jguionnet/feat/add-remote-repo-support-in-helm
Feat/add snapshot based testing for helm
2 parents 807138f + b4b4d46 commit 28c0f43

File tree

8 files changed

+534
-15
lines changed

8 files changed

+534
-15
lines changed

go.mod

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ require (
3232
github.com/oracle/oci-go-sdk v7.1.0+incompatible
3333
github.com/pquerna/otp v1.2.0
3434
github.com/sirupsen/logrus v1.8.1
35-
github.com/stretchr/testify v1.8.2
35+
github.com/stretchr/testify v1.8.4
3636
github.com/tmccombs/hcl2json v0.3.3
3737
github.com/urfave/cli v1.22.2
3838
github.com/zclconf/go-cty v1.9.1
@@ -48,6 +48,8 @@ require (
4848

4949
require (
5050
cloud.google.com/go/cloudbuild v1.9.0
51+
github.com/gonvenience/ytbx v1.4.4
52+
github.com/homeport/dyff v1.6.0
5153
github.com/slack-go/slack v0.10.3
5254
gotest.tools/v3 v3.0.3
5355
)
@@ -63,6 +65,7 @@ require (
6365
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
6466
github.com/Azure/go-autorest/logger v0.2.1 // indirect
6567
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
68+
github.com/BurntSushi/toml v1.3.2 // indirect
6669
github.com/agext/levenshtein v1.2.3 // indirect
6770
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
6871
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
@@ -83,6 +86,11 @@ require (
8386
github.com/gogo/protobuf v1.3.2 // indirect
8487
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
8588
github.com/golang/protobuf v1.5.3 // indirect
89+
github.com/gonvenience/bunt v1.3.5 // indirect
90+
github.com/gonvenience/neat v1.3.12 // indirect
91+
github.com/gonvenience/term v1.0.2 // indirect
92+
github.com/gonvenience/text v1.0.7 // indirect
93+
github.com/gonvenience/wrap v1.1.2 // indirect
8694
github.com/google/gnostic-models v0.6.8 // indirect
8795
github.com/google/go-cmp v0.5.9 // indirect
8896
github.com/google/gofuzz v1.2.0 // indirect
@@ -97,9 +105,14 @@ require (
97105
github.com/josharian/intern v1.0.0 // indirect
98106
github.com/json-iterator/go v1.1.12 // indirect
99107
github.com/klauspost/compress v1.15.11 // indirect
108+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
100109
github.com/mailru/easyjson v0.7.7 // indirect
110+
github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect
111+
github.com/mattn/go-isatty v0.0.19 // indirect
112+
github.com/mitchellh/go-ps v1.0.0 // indirect
101113
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
102114
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
115+
github.com/mitchellh/hashstructure v1.1.0 // indirect
103116
github.com/moby/spdystream v0.2.0 // indirect
104117
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
105118
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -110,10 +123,13 @@ require (
110123
github.com/pmezard/go-difflib v1.0.0 // indirect
111124
github.com/russross/blackfriday/v2 v2.1.0 // indirect
112125
github.com/satori/go.uuid v1.2.0 // indirect
126+
github.com/sergi/go-diff v1.3.1 // indirect
113127
github.com/spf13/pflag v1.0.5 // indirect
128+
github.com/texttheater/golang-levenshtein v1.0.1 // indirect
114129
github.com/ulikunitz/xz v0.5.10 // indirect
130+
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect
115131
go.opencensus.io v0.24.0 // indirect
116-
golang.org/x/sync v0.1.0 // indirect
132+
golang.org/x/sync v0.4.0 // indirect
117133
golang.org/x/sys v0.13.0 // indirect
118134
golang.org/x/term v0.13.0 // indirect
119135
golang.org/x/text v0.13.0 // indirect

go.sum

Lines changed: 54 additions & 12 deletions
Large diffs are not rendered by default.

modules/helm/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ type Options struct {
1818
Logger *logger.Logger // Set a non-default logger that should be used. See the logger package for more info. Use logger.Discard to not print the output while executing the command.
1919
ExtraArgs map[string][]string // Extra arguments to pass to the helm install/upgrade/rollback/delete and helm repo add commands. The key signals the command (e.g., install) while the values are the extra arguments to pass through.
2020
BuildDependencies bool // If true, helm dependencies will be built before rendering template, installing or upgrade the chart.
21+
SnapshotPath string // The path to the snapshot directory when using snapshot based testing. Empty string means use default ($PWD/__snapshot__).
2122
}

modules/helm/template.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import (
1111

1212
"github.com/gruntwork-io/terratest/modules/files"
1313
"github.com/gruntwork-io/terratest/modules/testing"
14+
15+
"os"
16+
17+
"github.com/gonvenience/ytbx"
18+
"github.com/homeport/dyff/pkg/dyff"
1419
)
1520

1621
// RenderTemplate runs `helm template` to render the template given the provided options and returns stdout/stderr from
@@ -134,3 +139,123 @@ func UnmarshalK8SYamlE(t testing.TestingT, yamlData string, destinationObj inter
134139
}
135140
return nil
136141
}
142+
143+
// UpdateSnapshot creates or updates the k8s manifest snapshot of a chart (e.g bitnami/nginx).
144+
// It is one of the two functions needed to implement snapshot based testing for helm.
145+
// see https://github.com/gruntwork-io/terratest/issues/1377
146+
// A snapshot is used to compare the current manifests of a chart with the previous manifests.
147+
// A global diff is run against the two snapshosts and the number of differences is returned.
148+
func UpdateSnapshot(t testing.TestingT, options *Options, yamlData string, releaseName string) {
149+
require.NoError(t, UpdateSnapshotE(t, options, yamlData, releaseName))
150+
}
151+
152+
// UpdateSnapshotE creates or updates the k8s manifest snapshot of a chart (e.g bitnami/nginx).
153+
// It is one of the two functions needed to implement snapshot based testing for helm.
154+
// see https://github.com/gruntwork-io/terratest/issues/1377
155+
// A snapshot is used to compare the current manifests of a chart with the previous manifests.
156+
// A global diff is run against the two snapshosts and the number of differences is returned.
157+
// It will failed the test if there is an error while writing the manifests' snapshot in the file system
158+
func UpdateSnapshotE(t testing.TestingT, options *Options, yamlData string, releaseName string) error {
159+
160+
var snapshotDir = "__snapshot__"
161+
if options.SnapshotPath != "" {
162+
snapshotDir = options.SnapshotPath
163+
}
164+
// Create a directory if not exists
165+
if !files.FileExists(snapshotDir) {
166+
if err := os.Mkdir(snapshotDir, 0755); err != nil {
167+
return errors.WithStackTrace(err)
168+
}
169+
}
170+
171+
filename := filepath.Join(snapshotDir, releaseName+".yaml")
172+
// Open a file in write mode
173+
file, err := os.Create(filename)
174+
if err != nil {
175+
return errors.WithStackTrace(err)
176+
}
177+
defer file.Close()
178+
179+
// Write the k8s manifest into the file
180+
if _, err = file.WriteString(yamlData); err != nil {
181+
return errors.WithStackTrace(err)
182+
}
183+
184+
if options.Logger != nil {
185+
options.Logger.Logf(t, "helm chart manifest written into file: %s", filename)
186+
}
187+
return nil
188+
}
189+
190+
// DiffAgainstSnapshot compare the current manifests of a chart (e.g bitnami/nginx)
191+
// with the previous manifests stored in the snapshot.
192+
// see https://github.com/gruntwork-io/terratest/issues/1377
193+
// It returns the number of difference between the two manifests or -1 in case of error
194+
// It will fail the test if there is an error while reading or writing the two manifests in the file system
195+
func DiffAgainstSnapshot(t testing.TestingT, options *Options, yamlData string, releaseName string) int {
196+
numberOfDiffs, err := DiffAgainstSnapshotE(t, options, yamlData, releaseName)
197+
require.NoError(t, err)
198+
return numberOfDiffs
199+
}
200+
201+
// DiffAgainstSnapshotE compare the current manifests of a chart (e.g bitnami/nginx)
202+
// with the previous manifests stored in the snapshot.
203+
// see https://github.com/gruntwork-io/terratest/issues/1377
204+
// It returns the number of difference between the manifests or -1 in case of error
205+
func DiffAgainstSnapshotE(t testing.TestingT, options *Options, yamlData string, releaseName string) (int, error) {
206+
207+
var snapshotDir = "__snapshot__"
208+
if options.SnapshotPath != "" {
209+
snapshotDir = options.SnapshotPath
210+
}
211+
212+
// load the yaml snapshot file
213+
snapshot := filepath.Join(snapshotDir, releaseName+".yaml")
214+
from, err := ytbx.LoadFile(snapshot)
215+
if err != nil {
216+
return -1, errors.WithStackTrace(err)
217+
}
218+
219+
// write the current manifest into a file as `dyff` does not support string input
220+
currentManifests := releaseName + ".yaml"
221+
file, err := os.Create(currentManifests)
222+
if err != nil {
223+
return -1, errors.WithStackTrace(err)
224+
}
225+
226+
if _, err = file.WriteString(yamlData); err != nil {
227+
return -1, errors.WithStackTrace(err)
228+
}
229+
defer file.Close()
230+
defer os.Remove(currentManifests)
231+
232+
to, err := ytbx.LoadFile(currentManifests)
233+
if err != nil {
234+
return -1, errors.WithStackTrace(err)
235+
}
236+
237+
// compare the two manifests using `dyff`
238+
compOpt := dyff.KubernetesEntityDetection(false)
239+
240+
// create a report
241+
report, err := dyff.CompareInputFiles(from, to, compOpt)
242+
if err != nil {
243+
return -1, errors.WithStackTrace(err)
244+
}
245+
246+
// write any difference to stdout
247+
reportWriter := &dyff.HumanReport{
248+
Report: report,
249+
DoNotInspectCerts: false,
250+
NoTableStyle: false,
251+
OmitHeader: false,
252+
UseGoPatchPaths: false,
253+
}
254+
255+
err = reportWriter.WriteReport(os.Stdout)
256+
if err != nil {
257+
return -1, errors.WithStackTrace(err)
258+
}
259+
// return the number of diffs to use in assertion while testing: 0 = no differences
260+
return len(reportWriter.Diffs), nil
261+
}

modules/helm/template_test.go

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
appsv1 "k8s.io/api/apps/v1"
1818

1919
"github.com/gruntwork-io/terratest/modules/k8s"
20+
"github.com/gruntwork-io/terratest/modules/logger"
2021
"github.com/gruntwork-io/terratest/modules/random"
2122
)
2223

@@ -25,7 +26,7 @@ func TestRemoteChartRender(t *testing.T) {
2526
const (
2627
remoteChartSource = "https://charts.bitnami.com/bitnami"
2728
remoteChartName = "nginx"
28-
remoteChartVersion = "13.2.23"
29+
remoteChartVersion = "13.2.24"
2930
)
3031

3132
t.Parallel()
@@ -45,6 +46,7 @@ func TestRemoteChartRender(t *testing.T) {
4546
"image.tag": remoteChartVersion,
4647
},
4748
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
49+
Logger: logger.Discard,
4850
}
4951

5052
// Run RenderTemplate to render the template and capture the output. Note that we use the version without `E`, since
@@ -65,3 +67,81 @@ func TestRemoteChartRender(t *testing.T) {
6567
require.Equal(t, len(deploymentContainers), 1)
6668
require.Equal(t, deploymentContainers[0].Image, expectedContainerImage)
6769
}
70+
71+
// Test that we can dump all the manifest locally a remote chart (e.g bitnami/nginx)
72+
// so that I can use them later to compare between two versions of the same chart for example
73+
func TestRemoteChartRenderDump(t *testing.T) {
74+
const (
75+
remoteChartSource = "https://charts.bitnami.com/bitnami"
76+
remoteChartName = "nginx"
77+
remoteChartVersion = "13.2.20"
78+
// need to set a fix name for the namespace so it is not flag as a difference
79+
namespaceName = "dump-ns"
80+
)
81+
82+
releaseName := remoteChartName
83+
84+
options := &Options{
85+
SetValues: map[string]string{
86+
"image.repository": remoteChartName,
87+
"image.registry": "",
88+
"image.tag": remoteChartVersion,
89+
},
90+
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
91+
Logger: logger.Discard,
92+
}
93+
94+
// Run RenderTemplate to render the template and capture the output. Note that we use the version without `E`, since
95+
// we want to assert that the template renders without any errors.
96+
output := RenderRemoteTemplate(t, options, remoteChartSource, releaseName, []string{})
97+
98+
// Now we use kubernetes/client-go library to render the template output into the Deployment struct. This will
99+
// ensure the Deployment resource is rendered correctly.
100+
var deployment appsv1.Deployment
101+
UnmarshalK8SYaml(t, output, &deployment)
102+
103+
// Verify the namespace matches the expected supplied namespace.
104+
require.Equal(t, namespaceName, deployment.Namespace)
105+
106+
// write chart manifest to a local filesystem directory
107+
options = &Options{
108+
Logger: logger.Default,
109+
SnapshotPath: "__chart_manifests_snapshot__",
110+
}
111+
UpdateSnapshot(t, options, output, releaseName)
112+
}
113+
114+
// Test that we can diff all the manifest to a local snapshot using a remote chart (e.g bitnami/nginx)
115+
func TestRemoteChartRenderDiff(t *testing.T) {
116+
const (
117+
remoteChartSource = "https://charts.bitnami.com/bitnami"
118+
remoteChartName = "nginx"
119+
remoteChartVersion = "13.2.24"
120+
// need to set a fix name for the namespace so it is not flag as a difference
121+
namespaceName = "dump-ns"
122+
)
123+
124+
releaseName := remoteChartName
125+
options := &Options{
126+
SetValues: map[string]string{
127+
"image.repository": remoteChartName,
128+
"image.registry": "",
129+
"image.tag": remoteChartVersion,
130+
},
131+
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
132+
Logger: logger.Discard,
133+
SnapshotPath: "__chart_manifests_snapshot__",
134+
}
135+
136+
// Run RenderTemplate to render the template and capture the output. Note that we use the version without `E`, since
137+
// we want to assert that the template renders without any errors.
138+
output := RenderRemoteTemplate(t, options, remoteChartSource, releaseName, []string{})
139+
140+
// Now we use kubernetes/client-go library to render the template output into the Deployment struct. This will
141+
// ensure the Deployment resource is rendered correctly.
142+
var deployment appsv1.Deployment
143+
UnmarshalK8SYaml(t, output, &deployment)
144+
145+
// run the diff and assert there is only one difference: the image name
146+
require.Equal(t, 1, DiffAgainstSnapshot(t, options, output, releaseName))
147+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
metricsServer:
2+
replicaCount: 3
3+
operator:
4+
name: keda-operator
5+
replicaCount: 3
6+
podAnnotations:
7+
keda:
8+
sidecar.istio.io/inject: "false"
9+
metricsAdapter:
10+
sidecar.istio.io/inject: "false"
11+
podDisruptionBudget:
12+
metricServer:
13+
minAvailable: 1
14+
operator:
15+
minAvailable: 1
16+
resources:
17+
metricServer:
18+
limits:
19+
cpu: 100m
20+
memory: 1234Mi
21+
requests:
22+
cpu: 50m
23+
memory: 128Mi
24+
operator:
25+
limits:
26+
cpu: 100m
27+
memory: 1111Mi
28+
requests:
29+
cpu: 50m
30+
memory: 888Mi

0 commit comments

Comments
 (0)