Skip to content

Commit 04d180d

Browse files
authored
Merge pull request #36 from bouskaJ/setup_envtest
test: Add setup envtest
2 parents 8e01421 + 21ddebb commit 04d180d

File tree

5 files changed

+214
-26
lines changed

5 files changed

+214
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
/bin
44

5+
cover.out
56
# editor and IDE paraphernalia
67
.idea
78
.vscode

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ OPERATOR_SDK_VERSION ?= v1.39.2
5353
IMG ?= controller:latest
5454
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
5555
ENVTEST_K8S_VERSION = 1.31.0
56+
ENVTEST_VERSION ?= release-0.17
5657

5758
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
5859
ifeq (,$(shell go env GOBIN))
@@ -141,7 +142,7 @@ run: manifests generate fmt vet ## Run a controller from your host.
141142
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
142143
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
143144
.PHONY: docker-build
144-
docker-build: ## Build docker image with the manager.
145+
docker-build: test ## Build docker image with the manager.
145146
$(CONTAINER_TOOL) build -t ${IMG} .
146147

147148
.PHONY: docker-push

go.mod

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ toolchain go1.23.4
66

77
require (
88
github.com/go-logr/logr v1.4.2
9-
github.com/stretchr/testify v1.9.0
9+
github.com/onsi/ginkgo/v2 v2.21.0
10+
github.com/onsi/gomega v1.35.1
1011
k8s.io/api v0.31.0
1112
k8s.io/apimachinery v0.32.0
1213
k8s.io/client-go v0.31.0
14+
k8s.io/klog/v2 v2.130.1
1315
sigs.k8s.io/controller-runtime v0.19.4
1416
)
1517

@@ -31,13 +33,15 @@ require (
3133
github.com/go-openapi/jsonpointer v0.21.0 // indirect
3234
github.com/go-openapi/jsonreference v0.20.2 // indirect
3335
github.com/go-openapi/swag v0.23.0 // indirect
36+
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
3437
github.com/gogo/protobuf v1.3.2 // indirect
3538
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
3639
github.com/golang/protobuf v1.5.4 // indirect
3740
github.com/google/cel-go v0.20.1 // indirect
3841
github.com/google/gnostic-models v0.6.8 // indirect
3942
github.com/google/go-cmp v0.6.0 // indirect
4043
github.com/google/gofuzz v1.2.0 // indirect
44+
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
4145
github.com/google/uuid v1.6.0 // indirect
4246
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
4347
github.com/imdario/mergo v0.3.6 // indirect
@@ -49,7 +53,6 @@ require (
4953
github.com/modern-go/reflect2 v1.0.2 // indirect
5054
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
5155
github.com/pkg/errors v0.9.1 // indirect
52-
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
5356
github.com/prometheus/client_golang v1.19.1 // indirect
5457
github.com/prometheus/client_model v0.6.1 // indirect
5558
github.com/prometheus/common v0.55.0 // indirect
@@ -76,19 +79,18 @@ require (
7679
golang.org/x/term v0.30.0 // indirect
7780
golang.org/x/text v0.23.0 // indirect
7881
golang.org/x/time v0.7.0 // indirect
82+
golang.org/x/tools v0.26.0 // indirect
7983
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
8084
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
8185
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
8286
google.golang.org/grpc v1.65.0 // indirect
8387
google.golang.org/protobuf v1.35.1 // indirect
84-
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
8588
gopkg.in/inf.v0 v0.9.1 // indirect
8689
gopkg.in/yaml.v2 v2.4.0 // indirect
8790
gopkg.in/yaml.v3 v3.0.1 // indirect
8891
k8s.io/apiextensions-apiserver v0.31.0 // indirect
8992
k8s.io/apiserver v0.31.0 // indirect
9093
k8s.io/component-base v0.31.0 // indirect
91-
k8s.io/klog/v2 v2.130.1 // indirect
9294
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
9395
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
9496
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect

internal/webhooks/pod_webhook_test.go

Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,103 @@ package webhooks
22

33
import (
44
"context"
5-
"encoding/json"
6-
"testing"
75

8-
admissionv1 "k8s.io/api/admission/v1"
9-
"k8s.io/client-go/kubernetes/scheme"
10-
"sigs.k8s.io/controller-runtime/pkg/client/fake"
11-
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
12-
13-
"github.com/stretchr/testify/assert"
6+
"github.com/miyunari/model-validation-controller/api/v1alpha1"
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
corev1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/types"
1412
)
1513

16-
func Test_PodInterceptor_Handle(t *testing.T) {
17-
const req = `{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"uid":"c33e1334-1a38-4684-aff8-e8c366ea89b9","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"name":"ollama","namespace":"model-validation-controller","operation":"CREATE","userInfo":{"username":"admin","groups":["system:masters","system:authenticated"],"extra":{"authentication.kubernetes.io/credential-id":["X509SHA256=acb312b9049f7fbeb8788001d7e61e145f10df1e4a752d1ef2caa3097d233246"]}},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"ollama","namespace":"model-validation-controller","creationTimestamp":null,"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{\"validation.rhtas.redhat.com/ml\":\"true\"},\"name\":\"ollama\",\"namespace\":\"model-validation-controller\"},\"spec\":{\"containers\":[{\"image\":\"ollama/ollama\",\"name\":\"ollama\",\"ports\":[{\"containerPort\":11434}]}]}}\n","validation.rhtas.redhat.com/ml":"true"},"managedFields":[{"manager":"kubectl-client-side-apply","operation":"Update","apiVersion":"v1","time":"2025-01-18T20:43:09Z","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{},"f:validation.rhtas.redhat.com/ml":{}}},"f:spec":{"f:containers":{"k:{\"name\":\"ollama\"}":{".":{},"f:image":{},"f:imagePullPolicy":{},"f:name":{},"f:ports":{".":{},"k:{\"containerPort\":11434,\"protocol\":\"TCP\"}":{".":{},"f:containerPort":{},"f:protocol":{}}},"f:resources":{},"f:terminationMessagePath":{},"f:terminationMessagePolicy":{}}},"f:dnsPolicy":{},"f:enableServiceLinks":{},"f:restartPolicy":{},"f:schedulerName":{},"f:securityContext":{},"f:terminationGracePeriodSeconds":{}}}}]},"spec":{"volumes":[{"name":"kube-api-access-v9hhf","projected":{"sources":[{"serviceAccountToken":{"expirationSeconds":3607,"path":"token"}},{"configMap":{"name":"kube-root-ca.crt","items":[{"key":"ca.crt","path":"ca.crt"}]}},{"downwardAPI":{"items":[{"path":"namespace","fieldRef":{"apiVersion":"v1","fieldPath":"metadata.namespace"}}]}}],"defaultMode":420}}],"containers":[{"name":"ollama","image":"ollama/ollama","ports":[{"containerPort":11434,"protocol":"TCP"}],"resources":{},"volumeMounts":[{"name":"kube-api-access-v9hhf","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","serviceAccountName":"default","serviceAccount":"default","securityContext":{},"schedulerName":"default-scheduler","tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":300},{"key":"node.kubernetes.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":300}],"priority":0,"enableServiceLinks":true,"preemptionPolicy":"PreemptLowerPriority"},"status":{}},"oldObject":null,"dryRun":false,"options":{"kind":"CreateOptions","apiVersion":"meta.k8s.io/v1","fieldManager":"kubectl-client-side-apply"}}}`
14+
var _ = Describe("Pod webhook", func() {
15+
Context("Pod webhook test", func() {
1816

19-
var review admissionv1.AdmissionReview
20-
err := json.Unmarshal([]byte(req), &review)
21-
assert.NoError(t, err)
17+
const (
18+
Name = "test"
19+
Namespace = "default"
20+
)
2221

23-
decoder := admission.NewDecoder(scheme.Scheme)
24-
handler := NewPodInterceptor(fake.NewClientBuilder().Build(), decoder)
22+
ctx := context.Background()
2523

26-
resp := handler.Handle(context.Background(), admission.Request{
27-
AdmissionRequest: *review.Request,
28-
})
24+
namespace := &corev1.Namespace{
25+
ObjectMeta: metav1.ObjectMeta{
26+
Name: Name,
27+
Namespace: Namespace,
28+
},
29+
}
30+
31+
typeNamespaceName := types.NamespacedName{Name: Name, Namespace: Namespace}
32+
33+
BeforeEach(func() {
34+
By("Creating the Namespace to perform the tests")
35+
err := k8sClient.Create(ctx, namespace)
36+
Expect(err).To(Not(HaveOccurred()))
37+
38+
By("Create ModelValidation resource")
39+
err = k8sClient.Create(ctx, &v1alpha1.ModelValidation{
40+
ObjectMeta: metav1.ObjectMeta{
41+
Name: Name,
42+
Namespace: Namespace,
43+
},
44+
Spec: v1alpha1.ModelValidationSpec{
45+
Model: v1alpha1.Model{
46+
Path: "test",
47+
SignaturePath: "test",
48+
},
49+
Config: v1alpha1.ValidationConfig{
50+
SigstoreConfig: nil,
51+
PkiConfig: nil,
52+
PrivateKeyConfig: nil,
53+
},
54+
},
55+
})
56+
Expect(err).To(Not(HaveOccurred()))
57+
})
2958

30-
assert.NotNil(t, resp)
31-
assert.True(t, resp.Allowed)
32-
}
59+
AfterEach(func() {
60+
// TODO(user): Attention if you improve this code by adding other context test you MUST
61+
// be aware of the current delete namespace limitations.
62+
// More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations
63+
By("Deleting the Namespace to perform the tests")
64+
_ = k8sClient.Delete(ctx, namespace)
65+
})
66+
67+
It("Should create sidecar container", func() {
68+
By("create labeled pod")
69+
instance := &corev1.Pod{
70+
ObjectMeta: metav1.ObjectMeta{
71+
Name: Name,
72+
Namespace: Namespace,
73+
Labels: map[string]string{"validation.rhtas.redhat.com/ml": "true"},
74+
},
75+
Spec: corev1.PodSpec{
76+
Containers: []corev1.Container{
77+
{
78+
Name: "test",
79+
Image: "test",
80+
},
81+
},
82+
},
83+
}
84+
err := k8sClient.Create(ctx, instance)
85+
Expect(err).To(Not(HaveOccurred()))
86+
87+
By("Checking that validation sidecar was created")
88+
found := &corev1.Pod{}
89+
Eventually(func() error {
90+
return k8sClient.Get(ctx, typeNamespaceName, found)
91+
}).Should(Succeed())
92+
93+
Eventually(
94+
func(g Gomega) []corev1.Container {
95+
Expect(k8sClient.Get(ctx, typeNamespaceName, found)).To(Succeed())
96+
return found.Spec.InitContainers
97+
},
98+
).Should(And(
99+
WithTransform(func(containers []corev1.Container) int { return len(containers) }, Equal(1)),
100+
WithTransform(func(containers []corev1.Container) string { return containers[0].Image }, Equal("ghcr.io/sigstore/model-transparency-cli:v1.0.1")),
101+
))
102+
})
103+
})
104+
})

internal/webhooks/suite_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package webhooks
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"path/filepath"
7+
"runtime"
8+
"testing"
9+
10+
"github.com/miyunari/model-validation-controller/api/v1alpha1"
11+
"k8s.io/klog/v2"
12+
"k8s.io/klog/v2/test"
13+
"sigs.k8s.io/controller-runtime/pkg/webhook"
14+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
15+
16+
. "github.com/onsi/ginkgo/v2"
17+
. "github.com/onsi/gomega"
18+
"k8s.io/client-go/kubernetes/scheme"
19+
"k8s.io/client-go/rest"
20+
ctrl "sigs.k8s.io/controller-runtime"
21+
"sigs.k8s.io/controller-runtime/pkg/client"
22+
"sigs.k8s.io/controller-runtime/pkg/envtest"
23+
//+kubebuilder:scaffold:imports
24+
)
25+
26+
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
27+
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
28+
29+
var (
30+
cfg *rest.Config
31+
k8sClient client.Client // You'll be using this client in your tests.
32+
testEnv *envtest.Environment
33+
ctx context.Context
34+
cancel context.CancelFunc
35+
)
36+
37+
func TestAPIs(t *testing.T) {
38+
fs := test.InitKlog(t)
39+
_ = fs.Set("v", "5")
40+
klog.SetOutput(GinkgoWriter)
41+
ctrl.SetLogger(klog.NewKlogr())
42+
43+
RegisterFailHandler(Fail)
44+
RunSpecs(t, "Webhook Suite")
45+
}
46+
47+
var _ = BeforeSuite(func() {
48+
ctx, cancel = context.WithCancel(context.TODO())
49+
50+
By("bootstrapping test environment")
51+
testEnv = &envtest.Environment{
52+
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
53+
ErrorIfCRDPathMissing: true,
54+
55+
// The BinaryAssetsDirectory is only required if you want to run the tests directly
56+
// without call the makefile target test. If not informed it will look for the
57+
// default path defined in controller-runtime which is /usr/local/kubebuilder/.
58+
// Note that you must have the required binaries setup under the bin directory to perform
59+
// the tests directly. When we run make test it will be setup and used automatically.
60+
BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s",
61+
fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)),
62+
WebhookInstallOptions: envtest.WebhookInstallOptions{Paths: []string{filepath.Join("..", "..", "config", "webhook")}},
63+
}
64+
65+
var err error
66+
// cfg is defined in this file globally.
67+
cfg, err = testEnv.Start()
68+
Expect(err).NotTo(HaveOccurred())
69+
Expect(cfg).NotTo(BeNil())
70+
71+
err = v1alpha1.AddToScheme(scheme.Scheme)
72+
Expect(err).NotTo(HaveOccurred())
73+
74+
//+kubebuilder:scaffold:scheme
75+
76+
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
77+
Expect(err).NotTo(HaveOccurred())
78+
Expect(k8sClient).NotTo(BeNil())
79+
80+
webhookServer := webhook.NewServer(webhook.Options{
81+
Host: testEnv.WebhookInstallOptions.LocalServingHost,
82+
Port: testEnv.WebhookInstallOptions.LocalServingPort,
83+
CertDir: testEnv.WebhookInstallOptions.LocalServingCertDir,
84+
})
85+
86+
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
87+
Scheme: scheme.Scheme,
88+
WebhookServer: webhookServer,
89+
})
90+
Expect(err).ToNot(HaveOccurred())
91+
Expect(k8sClient).NotTo(BeNil())
92+
93+
// Create a decoder for your webhook
94+
decoder := admission.NewDecoder(scheme.Scheme)
95+
podWebhookHandler := NewPodInterceptor(mgr.GetClient(), decoder)
96+
mgr.GetWebhookServer().Register("/mutate-v1-pod", &admission.Webhook{
97+
Handler: podWebhookHandler,
98+
})
99+
100+
go func() {
101+
defer GinkgoRecover()
102+
err = mgr.Start(ctx)
103+
Expect(err).ToNot(HaveOccurred(), "failed to run manager")
104+
}()
105+
})
106+
107+
var _ = AfterSuite(func() {
108+
cancel()
109+
By("tearing down the test environment")
110+
err := testEnv.Stop()
111+
Expect(err).NotTo(HaveOccurred())
112+
})

0 commit comments

Comments
 (0)