Skip to content

Commit df2ab4b

Browse files
authored
Merge pull request #135 from harshanarayana/feature/git-109/enable-env-test-integration
GIT-109: enable CRD setup helper to ease the testing of Operators
2 parents 2aae5e4 + 7b3aabf commit df2ab4b

File tree

9 files changed

+365
-0
lines changed

9 files changed

+365
-0
lines changed

examples/crds/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Kubernetes Custom Resource Integration Test
2+
3+
While developing a kubernetes operator for your custom use cases, it is very common practice that you end up creating your own custom resources.
4+
5+
This example shows how to leverage the helper functions provided by the Framework itself to setup
6+
the CRD resources using the `decoder` package against your test cluster before starting the actual test workflow.
7+
8+
## How does this work ?
9+
10+
1. You can leverage the framework's `env.Func` type helper for setting up the CRDs and tearing them down after the tests
11+
2. Register the CRD scheme with the `resources.Resources` to leverage the helpers for interacting with Custom resource objects
12+
13+
## What does this test do ?
14+
15+
1. Create a Kind cluster with a random name generated with `crdtest-` as the cluster name prefix
16+
2. Create a custom namespace with `my-ns` as the prefix
17+
3. Register the CRDs listed under `./testdata/crds` using the resource decode helpers
18+
4. Create a new Custom Resource for the CRD created in step #3
19+
5. Fetch the CR created in Test setup and print the value
20+
21+
## How to run the tests
22+
23+
```bash
24+
go test -v .
25+
```

examples/crds/envtest_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package crds
18+
19+
import (
20+
"context"
21+
"os"
22+
"testing"
23+
24+
"k8s.io/klog/v2"
25+
"sigs.k8s.io/e2e-framework/examples/crds/testdata/crontabs"
26+
"sigs.k8s.io/e2e-framework/klient/decoder"
27+
"sigs.k8s.io/e2e-framework/klient/k8s/resources"
28+
"sigs.k8s.io/e2e-framework/pkg/envconf"
29+
"sigs.k8s.io/e2e-framework/pkg/features"
30+
)
31+
32+
func TestCRDSetup(t *testing.T) {
33+
feature := features.New("Custom Controller").
34+
Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
35+
r, err := resources.New(c.Client().RESTConfig())
36+
if err != nil {
37+
t.Fail()
38+
}
39+
crontabs.AddToScheme(r.GetScheme())
40+
r.WithNamespace(namespace)
41+
decoder.DecodeEachFile(
42+
ctx, os.DirFS("./testdata/crs"), "*",
43+
decoder.CreateHandler(r),
44+
decoder.MutateNamespace(namespace),
45+
)
46+
return ctx
47+
}).
48+
Assess("Check If Resource created", func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
49+
r, err := resources.New(c.Client().RESTConfig())
50+
if err != nil {
51+
t.Fail()
52+
}
53+
r.WithNamespace(namespace)
54+
crontabs.AddToScheme(r.GetScheme())
55+
ct := &crontabs.CronTab{}
56+
err = r.Get(ctx, "my-new-cron-object", namespace, ct)
57+
if err != nil {
58+
t.Fail()
59+
}
60+
klog.InfoS("CR Details", "cr", ct)
61+
return ctx
62+
}).Feature()
63+
64+
testEnv.Test(t, feature)
65+
}

examples/crds/main_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package crds
18+
19+
import (
20+
"os"
21+
"testing"
22+
23+
"sigs.k8s.io/e2e-framework/pkg/env"
24+
"sigs.k8s.io/e2e-framework/pkg/envconf"
25+
"sigs.k8s.io/e2e-framework/pkg/envfuncs"
26+
)
27+
28+
var (
29+
testEnv env.Environment
30+
kindClusterName string
31+
namespace string
32+
)
33+
34+
func TestMain(m *testing.M) {
35+
cfg, _ := envconf.NewFromFlags()
36+
testEnv = env.NewWithConfig(cfg)
37+
kindClusterName = envconf.RandomName("crdtest-", 16)
38+
namespace = envconf.RandomName("my-ns", 10)
39+
40+
testEnv.Setup(
41+
envfuncs.CreateKindCluster(kindClusterName),
42+
envfuncs.CreateNamespace(namespace),
43+
envfuncs.SetupCRDs("./testdata/crds", "*"),
44+
)
45+
46+
testEnv.Finish(
47+
envfuncs.DeleteNamespace(namespace),
48+
envfuncs.TeardownCRDs("./testdata/crds", "*"),
49+
envfuncs.DestroyKindCluster(kindClusterName),
50+
)
51+
52+
os.Exit(testEnv.Run(m))
53+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
apiVersion: apiextensions.k8s.io/v1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: crontabs.stable.example.com
5+
spec:
6+
group: stable.example.com
7+
versions:
8+
- name: v1
9+
served: true
10+
storage: true
11+
schema:
12+
openAPIV3Schema:
13+
type: object
14+
properties:
15+
spec:
16+
type: object
17+
properties:
18+
cronSpec:
19+
type: string
20+
image:
21+
type: string
22+
replicas:
23+
type: integer
24+
scope: Namespaced
25+
names:
26+
plural: crontabs
27+
singular: crontab
28+
kind: CronTab
29+
shortNames:
30+
- ct
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package crontabs
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"k8s.io/apimachinery/pkg/runtime"
22+
"k8s.io/apimachinery/pkg/runtime/schema"
23+
)
24+
25+
const GroupName = "stable.example.com"
26+
const GroupVersion = "v1"
27+
28+
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion}
29+
30+
var (
31+
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
32+
AddToScheme = SchemeBuilder.AddToScheme
33+
)
34+
35+
func addKnownTypes(scheme *runtime.Scheme) error {
36+
scheme.AddKnownTypes(SchemeGroupVersion,
37+
&CronTab{},
38+
&CronTabList{},
39+
)
40+
41+
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
42+
return nil
43+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package crontabs
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"k8s.io/apimachinery/pkg/runtime"
22+
)
23+
24+
type CronTabSpec struct {
25+
CronSpec string `json:"cronSpec"`
26+
Image string `json:"image"`
27+
Replicas int `json:"replicas,omitempty"`
28+
}
29+
30+
type CronTab struct {
31+
metav1.TypeMeta `json:",inline"`
32+
metav1.ObjectMeta `json:"metadata,omitempty"`
33+
34+
Spec CronTabSpec `json:"spec"`
35+
}
36+
37+
type CronTabList struct {
38+
metav1.TypeMeta `json:",inline"`
39+
metav1.ListMeta `json:"metadata,omitempty"`
40+
41+
Items []CronTab `json:"items"`
42+
}
43+
44+
// DeepCopyInto copies all properties of this object into another object of the
45+
// same type that is provided as a pointer.
46+
func (in *CronTab) DeepCopyInto(out *CronTab) {
47+
out.TypeMeta = in.TypeMeta
48+
out.ObjectMeta = in.ObjectMeta
49+
out.Spec = CronTabSpec{
50+
Replicas: in.Spec.Replicas,
51+
}
52+
}
53+
54+
// DeepCopyObject returns a generically typed copy of an object
55+
func (in *CronTab) DeepCopyObject() runtime.Object {
56+
out := CronTab{}
57+
in.DeepCopyInto(&out)
58+
59+
return &out
60+
}
61+
62+
// DeepCopyObject returns a generically typed copy of an object
63+
func (in *CronTabList) DeepCopyObject() runtime.Object {
64+
out := CronTabList{}
65+
out.TypeMeta = in.TypeMeta
66+
out.ListMeta = in.ListMeta
67+
68+
if in.Items != nil {
69+
out.Items = make([]CronTab, len(in.Items))
70+
for i := range in.Items {
71+
in.Items[i].DeepCopyInto(&out.Items[i])
72+
}
73+
}
74+
75+
return &out
76+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: "stable.example.com/v1"
2+
kind: CronTab
3+
metadata:
4+
name: my-new-cron-object
5+
spec:
6+
cronSpec: "* * * * */5"
7+
image: my-awesome-cron-image

klient/decoder/decoder.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"fmt"
2525
"io"
2626
"io/fs"
27+
"os"
2728
"strings"
2829

2930
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -92,6 +93,20 @@ func DecodeAllFiles(ctx context.Context, fsys fs.FS, pattern string, options ...
9293
return objects, err
9394
}
9495

96+
// ApplyWithManifestDir resolves all the files in the Directory dirPath against the globbing pattern and creates a kubernetes
97+
// resource for each of the resources found under the manifest directory.
98+
func ApplyWithManifestDir(ctx context.Context, r *resources.Resources, dirPath, pattern string, createOptions []resources.CreateOption, options ...DecodeOption) error {
99+
err := DecodeEachFile(ctx, os.DirFS(dirPath), pattern, CreateHandler(r, createOptions...), options...)
100+
return err
101+
}
102+
103+
// DeleteWithManifestDir does the reverse of ApplyUsingManifestDir does. This will resolve all files in the dirPath against the pattern and then
104+
// delete those kubernetes resources found under the manifest directory.
105+
func DeleteWithManifestDir(ctx context.Context, r *resources.Resources, dirPath, pattern string, deleteOptions []resources.DeleteOption, options ...DecodeOption) error {
106+
err := DecodeEachFile(ctx, os.DirFS(dirPath), pattern, DeleteHandler(r, deleteOptions...), options...)
107+
return err
108+
}
109+
95110
// Decode a stream of documents of any Kind using either the innate typing of the scheme.
96111
// Falls back to the unstructured.Unstructured type if a matching type cannot be found for the Kind.
97112
//

pkg/envfuncs/resource_funcs.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package envfuncs
18+
19+
import (
20+
"context"
21+
22+
"sigs.k8s.io/e2e-framework/klient/decoder"
23+
"sigs.k8s.io/e2e-framework/klient/k8s/resources"
24+
"sigs.k8s.io/e2e-framework/pkg/env"
25+
"sigs.k8s.io/e2e-framework/pkg/envconf"
26+
)
27+
28+
// SetupCRDs is provided as a helper env.Func handler that can be used to setup the CRDs that are required
29+
// to process your controller code for testing. For additional control on resource creation handling, please
30+
// use the decoder.ApplyWithManifestDir directly with suitable arguments to customize the behavior
31+
func SetupCRDs(crdPath, pattern string) env.Func {
32+
return func(ctx context.Context, c *envconf.Config) (context.Context, error) {
33+
r, err := resources.New(c.Client().RESTConfig())
34+
if err != nil {
35+
return ctx, err
36+
}
37+
return ctx, decoder.ApplyWithManifestDir(ctx, r, crdPath, pattern, []resources.CreateOption{})
38+
}
39+
}
40+
41+
// TeardownCRDs is provided as a handler function that can be hooked into your test's teardown sequence to
42+
// make sure that you can cleanup the CRDs that were setup as part of the SetupCRDs hook
43+
func TeardownCRDs(crdPath, pattern string) env.Func {
44+
return func(ctx context.Context, c *envconf.Config) (context.Context, error) {
45+
r, err := resources.New(c.Client().RESTConfig())
46+
if err != nil {
47+
return ctx, err
48+
}
49+
return ctx, decoder.DeleteWithManifestDir(ctx, r, crdPath, pattern, []resources.DeleteOption{})
50+
}
51+
}

0 commit comments

Comments
 (0)