Skip to content

Commit 56b67c4

Browse files
Baarsgaardweisdd
andauthored
chore(tests): introduce testcontainers into integration tests as a partial replacement for chainsaw (#2083)
* fix(Grafana): Authenticate with .spec.config.security.admin_* as fallback * test: Retrieving Grafana credentials from CR Spec * chore(Go/Makefile): New test_short target for only running go tests * chore(deps): Add testcontainers * chore: shared objectMeta and commonSpec for external-grafana * chore(testcontainers): Prepare external Grafana instance * chore: Migrate force_delete_folder * chore: Refactor GetScopedMatchingInstances to rely more on suite_test.go vars * chore: Migrate datasource_values_from * chore(tests): Delete unused resource files * Chore: Simplify Grafana container readiness and prep Co-authored-by: Igor Beliakov <46579601+weisdd@users.noreply.github.com> * chore: Address new whitespace linter * fix: reformat code --------- Co-authored-by: Igor Beliakov <46579601+weisdd@users.noreply.github.com> Co-authored-by: Igor Beliakov <demtis.register@gmail.com>
1 parent 4a8a17e commit 56b67c4

File tree

17 files changed

+555
-996
lines changed

17 files changed

+555
-996
lines changed

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ test: $(ENVTEST) manifests generate vet golangci-lint api-docs kustomize-lint he
156156
$(info $(M) running $@)
157157
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(BIN) -p path)" go test ./... -coverprofile cover.out
158158

159+
.PHONY: test-short
160+
test-short: ## Skips slow integration tests
161+
$(info $(M) running $@)
162+
go test ./... -short -coverprofile cover.out
163+
159164
.PHONY: vet
160165
vet: ## Run go vet against code.
161166
$(info $(M) running $@)

api/v1beta1/suite_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ var (
4343
)
4444

4545
func TestAPIs(t *testing.T) {
46+
if testing.Short() {
47+
t.Skip("-short was passed, skipping CRDs")
48+
}
49+
4650
RegisterFailHandler(Fail)
4751

4852
RunSpecs(t, "CRDs Suite")

controllers/config/operator_constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818

1919
// Grafana env vars and admin user
2020
DefaultAdminUser = "admin"
21+
DefaultAdminPassword = "admin"
2122
GrafanaAdminUserEnvVar = "GF_SECURITY_ADMIN_USER"
2223
GrafanaAdminPasswordEnvVar = "GF_SECURITY_ADMIN_PASSWORD" // #nosec G101
2324
GrafanaPluginsEnvVar = "GF_INSTALL_PLUGINS"

controllers/content/fetchers/suite_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ var (
2121
)
2222

2323
func TestAPIs(t *testing.T) {
24+
if testing.Short() {
25+
t.Skip("-short was passed, skipping Fetchers")
26+
}
27+
2428
RegisterFailHandler(Fail)
2529

2630
RunSpecs(t, "Fetchers Suite")

controllers/content/suite_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ func (in *NopContentResource) DeepCopyInto(out *NopContentResource) {
8181
}
8282

8383
func TestAPIs(t *testing.T) {
84+
if testing.Short() {
85+
t.Skip("-short was passed, skipping Content")
86+
}
87+
8488
RegisterFailHandler(Fail)
8589

8690
RunSpecs(t, "Content Suite")

controllers/controller_shared_test.go

Lines changed: 33 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
package controllers
1818

1919
import (
20-
"context"
2120
"testing"
2221

2322
"github.com/grafana/grafana-operator/v5/api/v1beta1"
@@ -28,7 +27,7 @@ import (
2827
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2928
"k8s.io/apimachinery/pkg/types"
3029
ctrl "sigs.k8s.io/controller-runtime"
31-
logf "sigs.k8s.io/controller-runtime/pkg/log"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
3231
)
3332

3433
// Reusable objectMetas and CommonSpecs to make test tables less verbose
@@ -73,6 +72,16 @@ var (
7372
MatchLabels: map[string]string{"invalid-spec": "test"},
7473
},
7574
}
75+
76+
objectMetaSynchronized = metav1.ObjectMeta{
77+
Namespace: "default",
78+
Name: "synchronized",
79+
}
80+
commonSpecSynchronized = v1beta1.GrafanaCommonSpec{
81+
InstanceSelector: &metav1.LabelSelector{
82+
MatchLabels: map[string]string{"synchronized": "test"},
83+
},
84+
}
7685
)
7786

7887
func requestFromMeta(obj metav1.ObjectMeta) ctrl.Request {
@@ -345,24 +354,19 @@ func TestMergeReconcileErrors(t *testing.T) {
345354
}
346355

347356
var _ = Describe("GetMatchingInstances functions", Ordered, func() {
348-
ns1 := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
349-
Name: "get-matching-test",
350-
}}
351-
ns2 := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
352-
Name: "additional-grafana-namespace",
357+
ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
358+
Name: "matching-instances",
353359
}}
354360
allowFolder := v1beta1.GrafanaFolder{
355361
ObjectMeta: metav1.ObjectMeta{
362+
Namespace: ns.Name,
356363
Name: "allow-cross-namespace",
357-
Namespace: ns1.Name,
358364
},
359365
Spec: v1beta1.GrafanaFolderSpec{
360366
GrafanaCommonSpec: v1beta1.GrafanaCommonSpec{
361367
AllowCrossNamespaceImport: true,
362368
InstanceSelector: &metav1.LabelSelector{
363-
MatchLabels: map[string]string{
364-
"test": "folder",
365-
},
369+
MatchLabels: map[string]string{"matching-instances": "test"},
366370
},
367371
},
368372
},
@@ -373,69 +377,58 @@ var _ = Describe("GetMatchingInstances functions", Ordered, func() {
373377
denyFolder.Spec.AllowCrossNamespaceImport = false
374378

375379
matchAllFolder := allowFolder.DeepCopy()
376-
matchAllFolder.Name = "invalid-match-labels"
380+
matchAllFolder.Name = "match-all-grafanas"
377381
matchAllFolder.Spec.InstanceSelector = &metav1.LabelSelector{} // InstanceSelector is never nil
378382

379-
DefaultGrafana := v1beta1.Grafana{
383+
BaseGrafana := v1beta1.Grafana{
380384
ObjectMeta: metav1.ObjectMeta{
385+
Namespace: ns.Name,
381386
Name: "instance",
382-
Namespace: ns2.Name,
383-
Labels: map[string]string{
384-
"test": "folder",
385-
},
387+
Labels: map[string]string{"matching-instances": "test"},
386388
},
387389
Spec: v1beta1.GrafanaSpec{},
388390
}
389-
matchesNothingGrafana := DefaultGrafana.DeepCopy()
390-
matchesNothingGrafana.Name = "match-nothing-instance"
391+
matchesNothingGrafana := BaseGrafana.DeepCopy()
392+
matchesNothingGrafana.Name = "no-labels-instance"
391393
matchesNothingGrafana.Labels = nil
392394

393-
secondNamespaceGrafana := DefaultGrafana.DeepCopy()
394-
secondNamespaceGrafana.Name = "second-namespace-instance"
395-
secondNamespaceGrafana.Namespace = ns1.Name
396-
397395
// Status update is skipped for this
398-
unreadyGrafana := DefaultGrafana.DeepCopy()
396+
unreadyGrafana := BaseGrafana.DeepCopy()
399397
unreadyGrafana.Name = "unready-instance"
400398

401-
ctx := context.Background()
402-
testLog := logf.FromContext(ctx).WithSink(logf.NullLogSink{})
403-
ctx = logf.IntoContext(ctx, testLog)
399+
createCRs := []client.Object{&ns, &allowFolder, denyFolder, matchAllFolder, unreadyGrafana}
404400

405401
// Pre-create all resources
406402
BeforeAll(func() { // Necessary to use assertions
407-
Expect(k8sClient.Create(ctx, &ns1)).NotTo(HaveOccurred())
408-
Expect(k8sClient.Create(ctx, &ns2)).NotTo(HaveOccurred())
409-
Expect(k8sClient.Create(ctx, &allowFolder)).NotTo(HaveOccurred())
410-
Expect(k8sClient.Create(ctx, denyFolder)).NotTo(HaveOccurred())
411-
Expect(k8sClient.Create(ctx, matchAllFolder)).NotTo(HaveOccurred())
412-
Expect(k8sClient.Create(ctx, unreadyGrafana)).NotTo(HaveOccurred())
403+
for _, cr := range createCRs {
404+
Expect(k8sClient.Create(testCtx, cr)).Should(Succeed())
405+
}
413406

414-
grafanas := []v1beta1.Grafana{DefaultGrafana, *matchesNothingGrafana, *secondNamespaceGrafana}
407+
grafanas := []v1beta1.Grafana{BaseGrafana, *matchesNothingGrafana}
415408
for _, instance := range grafanas {
416-
Expect(k8sClient.Create(ctx, &instance)).NotTo(HaveOccurred())
409+
Expect(k8sClient.Create(testCtx, &instance)).NotTo(HaveOccurred())
417410

418411
// Apply status to pass instance ready check
419412
instance.Status.Stage = v1beta1.OperatorStageComplete
420413
instance.Status.StageStatus = v1beta1.OperatorStageResultSuccess
421-
Expect(k8sClient.Status().Update(ctx, &instance)).ToNot(HaveOccurred())
414+
Expect(k8sClient.Status().Update(testCtx, &instance)).ToNot(HaveOccurred())
422415
}
423416
})
424417

425418
Context("Ensure AllowCrossNamespaceImport is upheld by GetScopedMatchingInstances", func() {
426419
It("Finds all ready instances when instanceSelector is empty", func() {
427-
instances, err := GetScopedMatchingInstances(ctx, k8sClient, matchAllFolder)
420+
instances, err := GetScopedMatchingInstances(testCtx, k8sClient, matchAllFolder)
428421
Expect(err).ToNot(HaveOccurred())
429-
Expect(instances).To(HaveLen(3 + 1)) // +1 To account for instance created in suite_test.go to provoke ApplyFailed conditions
422+
Expect(instances).To(HaveLen(2 + 2)) // +2 To account for instances created in controllers/suite_test.go to provoke conditions
430423
})
431424
It("Finds all ready and Matching instances", func() {
432-
instances, err := GetScopedMatchingInstances(ctx, k8sClient, &allowFolder)
425+
instances, err := GetScopedMatchingInstances(testCtx, k8sClient, &allowFolder)
433426
Expect(err).ToNot(HaveOccurred())
434427
Expect(instances).ToNot(BeEmpty())
435428
Expect(instances).To(HaveLen(2))
436429
})
437430
It("Finds matching and ready and matching instance in namespace", func() {
438-
instances, err := GetScopedMatchingInstances(ctx, k8sClient, denyFolder)
431+
instances, err := GetScopedMatchingInstances(testCtx, k8sClient, denyFolder)
439432
Expect(err).ToNot(HaveOccurred())
440433
Expect(instances).ToNot(BeEmpty())
441434
Expect(instances).To(HaveLen(1))

controllers/datasource_controller_test.go

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
v1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1"
8+
grafanaclient "github.com/grafana/grafana-operator/v5/controllers/client"
89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
1011
corev1 "k8s.io/api/core/v1"
@@ -42,64 +43,132 @@ func TestGetDatasourceContent(t *testing.T) {
4243
})
4344
}
4445

45-
var _ = Describe("Datasource: Reconciler", func() {
46+
var _ = Describe("Datasource: substitute reference values", func() {
4647
It("Correctly substitutes valuesFrom", func() {
47-
cm := corev1.ConfigMap{
48+
cm := &corev1.ConfigMap{
4849
ObjectMeta: metav1.ObjectMeta{
49-
Name: "test-valuesfrom-plain",
5050
Namespace: "default",
51+
Name: "ds-valuesfrom-configmap",
5152
},
5253
Data: map[string]string{
53-
"CUSTOM_URL": "https://demo.promlabs.com",
54-
"CUSTOM_TRACEID": "substituted",
54+
"customTraceId": "substituted",
5555
},
5656
}
57-
err := k8sClient.Create(testCtx, &cm)
58-
Expect(err).ToNot(HaveOccurred())
59-
cr := &v1beta1.GrafanaDatasource{
57+
sc := &corev1.Secret{
58+
ObjectMeta: metav1.ObjectMeta{
59+
Namespace: "default",
60+
Name: "ds-values-from-secret",
61+
},
62+
StringData: map[string]string{
63+
"PROMETHEUS_TOKEN": "secret_token",
64+
"URL": "https://demo.promlabs.com",
65+
},
66+
}
67+
ds := &v1beta1.GrafanaDatasource{
6068
ObjectMeta: metav1.ObjectMeta{
6169
Namespace: "default",
70+
Name: "substitute-reference-values",
6271
},
6372
Spec: v1beta1.GrafanaDatasourceSpec{
73+
GrafanaCommonSpec: v1beta1.GrafanaCommonSpec{
74+
InstanceSelector: &metav1.LabelSelector{
75+
MatchLabels: map[string]string{
76+
"dashboards": "grafana",
77+
},
78+
},
79+
},
80+
CustomUID: "substitute",
6481
ValuesFrom: []v1beta1.ValueFrom{
82+
{
83+
TargetPath: "secureJsonData.httpHeaderValue1",
84+
ValueFrom: v1beta1.ValueFromSource{
85+
SecretKeyRef: &corev1.SecretKeySelector{
86+
LocalObjectReference: corev1.LocalObjectReference{
87+
Name: sc.Name,
88+
},
89+
Key: "PROMETHEUS_TOKEN",
90+
},
91+
},
92+
},
6593
{
6694
TargetPath: "url",
6795
ValueFrom: v1beta1.ValueFromSource{
68-
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
96+
SecretKeyRef: &corev1.SecretKeySelector{
6997
LocalObjectReference: corev1.LocalObjectReference{
70-
Name: cm.Name,
98+
Name: sc.Name,
7199
},
72-
Key: "CUSTOM_URL",
100+
Key: "URL",
73101
},
74102
},
75103
},
76104
{
77-
TargetPath: "jsonData.list[0].value",
105+
TargetPath: "jsonData.exemplarTraceIdDestinations[1].name",
78106
ValueFrom: v1beta1.ValueFromSource{
79107
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
80108
LocalObjectReference: corev1.LocalObjectReference{
81109
Name: cm.Name,
82110
},
83-
Key: "CUSTOM_TRACEID",
111+
Key: "customTraceId",
84112
},
85113
},
86114
},
87115
},
88116
Datasource: &v1beta1.GrafanaDatasourceInternal{
89-
URL: "${CUSTOM_URL}",
90-
JSONData: json.RawMessage([]byte(`{"list":[{"value":"${CUSTOM_TRACEID}"}]}`)),
117+
Name: "substitute-prometheus",
118+
Type: "prometheus",
119+
Access: "proxy",
120+
URL: "${URL}",
121+
JSONData: json.RawMessage([]byte(`{
122+
"tlsSkipVerify": true,
123+
"timeInterval": "10s",
124+
"httpHeaderName1": "Authorization",
125+
"exemplarTraceIdDestinations": [
126+
{"name": "traceID"},
127+
{"name": "${customTraceId}"}
128+
]
129+
}`)),
130+
SecureJSONData: json.RawMessage([]byte(`{
131+
"httpHeaderValue1": "Bearer ${PROMETHEUS_TOKEN}"
132+
}`)),
91133
},
92134
},
93135
}
136+
Expect(k8sClient.Create(testCtx, cm)).Should(Succeed())
137+
Expect(k8sClient.Create(testCtx, sc)).Should(Succeed())
138+
Expect(k8sClient.Create(testCtx, ds)).Should(Succeed())
94139

95-
r := GrafanaDatasourceReconciler{Client: k8sClient}
96-
content, hash, err := r.buildDatasourceModel(testCtx, cr)
140+
req := requestFromMeta(ds.ObjectMeta)
141+
r := GrafanaDatasourceReconciler{Client: k8sClient, Scheme: k8sClient.Scheme()}
142+
_, err := r.Reconcile(testCtx, req)
97143
Expect(err).ToNot(HaveOccurred())
98-
Expect(hash).ToNot(BeEmpty())
99-
Expect(content.URL).To(Equal(cm.Data["CUSTOM_URL"]))
100-
marshaled, err := json.Marshal(content.JSONData)
144+
145+
Expect(r.Get(testCtx, req.NamespacedName, ds)).Should(Succeed())
146+
Expect(ds.Status.Conditions).Should(ContainElement(HaveField("Type", conditionDatasourceSynchronized)))
147+
Expect(ds.Status.Conditions).Should(ContainElement(HaveField("Reason", conditionReasonApplySuccessful)))
148+
149+
cl, err := grafanaclient.NewGeneratedGrafanaClient(testCtx, k8sClient, externalGrafanaCr)
150+
Expect(err).ToNot(HaveOccurred())
151+
152+
model, err := cl.Datasources.GetDataSourceByUID(ds.Spec.CustomUID)
153+
Expect(err).ToNot(HaveOccurred())
154+
155+
Expect(model.Payload.URL).To(Equal("https://demo.promlabs.com"))
156+
Expect(model.Payload.SecureJSONFields["httpHeaderValue1"]).To(BeTrue())
157+
158+
// Serialize and Derserialize jsonData
159+
b, err := json.Marshal(model.Payload.JSONData)
160+
Expect(err).ToNot(HaveOccurred())
161+
162+
type ExemplarTraceIDDestination struct {
163+
Name string `json:"name"`
164+
}
165+
type SubstitutedJSONData struct {
166+
ExemplarTraceIDDestinations []ExemplarTraceIDDestination `json:"exemplarTraceIdDestinations"`
167+
}
168+
var jsonData SubstitutedJSONData // map with array of
169+
err = json.Unmarshal(b, &jsonData)
101170
Expect(err).ToNot(HaveOccurred())
102-
Expect(marshaled).To(ContainSubstring(cm.Data["CUSTOM_TRACEID"]))
171+
Expect(jsonData.ExemplarTraceIDDestinations[1].Name).To(Equal("substituted"))
103172
})
104173
})
105174

0 commit comments

Comments
 (0)