Skip to content

Commit

Permalink
conversion webhook with envtest
Browse files Browse the repository at this point in the history
Signed-off-by: Mateus Oliveira <[email protected]>
  • Loading branch information
mateusoliveira43 committed Dec 30, 2024
1 parent 980fb71 commit 9746828
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ import (
"fmt"
"strings"

"log"

"sigs.k8s.io/controller-runtime/pkg/conversion"
logf "sigs.k8s.io/controller-runtime/pkg/log"

batchv1 "tutorial.kubebuilder.io/project/api/v1"
) // +kubebuilder:docs-gen:collapse=Imports

var cronjoblog = logf.Log.WithName("cronjob-resource")

/*
Our "spoke" versions need to implement the
[`Convertible`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible)
Expand All @@ -50,8 +51,10 @@ Most of the conversion is straightforward copying, except for converting our cha
// ConvertTo converts this CronJob (v2) to the Hub version (v1).
func (src *CronJob) ConvertTo(dstRaw conversion.Hub) error {
dst := dstRaw.(*batchv1.CronJob)
log.Printf("ConvertTo: Converting CronJob from Spoke version v2 to Hub version v1;"+
"source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name)
cronjoblog.Info(
"ConvertTo: Converting CronJob from Spoke version v2 to Hub version v1",
"name", src.GetName(), "namespace", src.GetNamespace(),
)

sched := src.Spec.Schedule
scheduleParts := []string{"*", "*", "*", "*", "*"}
Expand Down Expand Up @@ -102,8 +105,10 @@ Most of the conversion is straightforward copying, except for converting our cha
// ConvertFrom converts the Hub version (v1) to this CronJob (v2).
func (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error {
src := srcRaw.(*batchv1.CronJob)
log.Printf("ConvertFrom: Converting CronJob from Hub version v1 to Spoke version v2;"+
"source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name)
cronjoblog.Info(
"ConvertFrom: Converting CronJob from Hub version v1 to Spoke version v2",
"name", src.GetName(), "namespace", src.GetNamespace(),
)

schedParts := strings.Split(src.Spec.Schedule, " ")
if len(schedParts) != 5 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
k8s.io/api v0.31.0
k8s.io/apimachinery v0.31.0
k8s.io/client-go v0.31.0
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/controller-runtime v0.19.1
)

Expand Down Expand Up @@ -93,7 +94,6 @@ require (
k8s.io/component-base v0.31.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (
Next, we'll setup a logger for the webhooks.
*/

var cronjoblog = logf.Log.WithName("cronjob-resource")
var cronjoblog = logf.Log.WithName("cronjob-resource-v1")

/*
This setup doubles as setup for our conversion webhooks: as long as our
Expand Down Expand Up @@ -108,7 +108,7 @@ func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object
if !ok {
return fmt.Errorf("expected an CronJob object but got %T", obj)
}
cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName())
cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName(), "namespace", cronjob.GetNamespace())

// Set default values
d.applyDefaults(cronjob)
Expand Down Expand Up @@ -178,7 +178,7 @@ func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime
if !ok {
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
}
cronjoblog.Info("Validation for CronJob upon creation", "name", cronjob.GetName())
cronjoblog.Info("Validation for CronJob upon creation", "name", cronjob.GetName(), "namespace", cronjob.GetNamespace())

return nil, validateCronJob(cronjob)
}
Expand All @@ -189,7 +189,7 @@ func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, new
if !ok {
return nil, fmt.Errorf("expected a CronJob object for the newObj but got %T", newObj)
}
cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName())
cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName(), "namespace", cronjob.GetNamespace())

return nil, validateCronJob(cronjob)
}
Expand All @@ -200,7 +200,7 @@ func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime
if !ok {
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
}
cronjoblog.Info("Validation for CronJob upon deletion", "name", cronjob.GetName())
cronjoblog.Info("Validation for CronJob upon deletion", "name", cronjob.GetName(), "namespace", cronjob.GetNamespace())

// TODO(user): fill in your validation logic upon object deletion.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ package v1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
kbatchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

batchv1 "tutorial.kubebuilder.io/project/api/v1"
batchv2 "tutorial.kubebuilder.io/project/api/v2"
// TODO (user): Add any additional imports if needed
)

Expand Down Expand Up @@ -165,13 +170,41 @@ var _ = Describe("CronJob Webhook", func() {
})

Context("When creating CronJob under Conversion Webhook", func() {
// TODO (user): Add logic to convert the object to the desired version and verify the conversion
// Example:
// It("Should convert the object correctly", func() {
// convertedObj := &batchv1.CronJob{}
// Expect(obj.ConvertTo(convertedObj)).To(Succeed())
// Expect(convertedObj).ToNot(BeNil())
// })
It("Should convert the object correctly", func() {
testNamespaceName := "test-namespace"
Expect(k8sClient.Create(ctx, &corev1.Namespace{
ObjectMeta: v1.ObjectMeta{
Name: testNamespaceName,
},
})).To(Succeed())

cronJobv1 := &batchv1.CronJob{
ObjectMeta: v1.ObjectMeta{
Name: "example-v1",
Namespace: testNamespaceName,
},
Spec: batchv1.CronJobSpec{
Schedule: "*/1 * * * *",
JobTemplate: kbatchv1.JobTemplateSpec{
Spec: kbatchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{},
},
},
},
},
},
}
Expect(k8sClient.Create(ctx, cronJobv1)).To(Succeed())

cronJobv2 := &batchv2.CronJob{}
Expect(k8sClient.Get(ctx, types.NamespacedName{
Name: "example-v1",
Namespace: testNamespaceName,
}, cronJobv2)).To(Succeed())
Expect(cronJobv2).ToNot(BeNil())
})
})

})
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

admissionv1 "k8s.io/api/admission/v1"
apimachineryruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -41,6 +40,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook"

batchv1 "tutorial.kubebuilder.io/project/api/v1"
batchv2 "tutorial.kubebuilder.io/project/api/v2"
webhookv2 "tutorial.kubebuilder.io/project/internal/webhook/v2"
// +kubebuilder:scaffold:imports
)

Expand All @@ -66,8 +67,16 @@ var _ = BeforeSuite(func() {

ctx, cancel = context.WithCancel(context.TODO())

var err error
err = batchv1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

err = batchv2.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDInstallOptions: envtest.CRDInstallOptions{Scheme: scheme.Scheme},
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")},
ErrorIfCRDPathMissing: false,

Expand All @@ -81,29 +90,21 @@ var _ = BeforeSuite(func() {
testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()
}

var err error
// cfg is defined in this file globally.
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())

scheme := apimachineryruntime.NewScheme()
err = batchv1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())

err = admissionv1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())

// +kubebuilder:scaffold:scheme

k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())

// start webhook server using Manager.
webhookInstallOptions := &testEnv.WebhookInstallOptions
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
Scheme: scheme.Scheme,
WebhookServer: webhook.NewServer(webhook.Options{
Host: webhookInstallOptions.LocalServingHost,
Port: webhookInstallOptions.LocalServingPort,
Expand All @@ -117,6 +118,9 @@ var _ = BeforeSuite(func() {
err = SetupCronJobWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = webhookv2.SetupCronJobWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

// +kubebuilder:scaffold:webhook

go func() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import (

// nolint:unused
// log is for logging in this package.
var cronjoblog = logf.Log.WithName("cronjob-resource")
var cronjoblog = logf.Log.WithName("cronjob-resource-v2")

// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager.
func SetupCronJobWebhookWithManager(mgr ctrl.Manager) error {
Expand Down Expand Up @@ -79,7 +79,7 @@ func (d *CronJobCustomDefaulter) Default(ctx context.Context, obj runtime.Object
if !ok {
return fmt.Errorf("expected an CronJob object but got %T", obj)
}
cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName())
cronjoblog.Info("Defaulting for CronJob", "name", cronjob.GetName(), "namespace", cronjob.GetNamespace())

// Set default values
d.applyDefaults(cronjob)
Expand Down Expand Up @@ -109,7 +109,7 @@ func (v *CronJobCustomValidator) ValidateCreate(ctx context.Context, obj runtime
if !ok {
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
}
cronjoblog.Info("Validation for CronJob upon creation", "name", cronjob.GetName())
cronjoblog.Info("Validation for CronJob upon creation", "name", cronjob.GetName(), "namespace", cronjob.GetNamespace())

return nil, validateCronJob(cronjob)
}
Expand All @@ -120,7 +120,7 @@ func (v *CronJobCustomValidator) ValidateUpdate(ctx context.Context, oldObj, new
if !ok {
return nil, fmt.Errorf("expected a CronJob object for the newObj but got %T", newObj)
}
cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName())
cronjoblog.Info("Validation for CronJob upon update", "name", cronjob.GetName(), "namespace", cronjob.GetNamespace())

return nil, validateCronJob(cronjob)
}
Expand All @@ -131,7 +131,7 @@ func (v *CronJobCustomValidator) ValidateDelete(ctx context.Context, obj runtime
if !ok {
return nil, fmt.Errorf("expected a CronJob object but got %T", obj)
}
cronjoblog.Info("Validation for CronJob upon deletion", "name", cronjob.GetName())
cronjoblog.Info("Validation for CronJob upon deletion", "name", cronjob.GetName(), "namespace", cronjob.GetNamespace())

// TODO(user): fill in your validation logic upon object deletion.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ package v2
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
kbatchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"

batchv1 "tutorial.kubebuilder.io/project/api/v1"
batchv2 "tutorial.kubebuilder.io/project/api/v2"
// TODO (user): Add any additional imports if needed
)
Expand Down Expand Up @@ -84,4 +90,44 @@ var _ = Describe("CronJob Webhook", func() {
// })
})

Context("When creating CronJob under Conversion Webhook", func() {
It("Should convert the object correctly", func() {
testNamespaceName := "test-namespace-2"
Expect(k8sClient.Create(ctx, &corev1.Namespace{
ObjectMeta: v1.ObjectMeta{
Name: testNamespaceName,
},
})).To(Succeed())

cronJobv2 := &batchv2.CronJob{
ObjectMeta: v1.ObjectMeta{
Name: "example-v2",
Namespace: testNamespaceName,
},
Spec: batchv2.CronJobSpec{
Schedule: batchv2.CronSchedule{
Minute: ptr.To(batchv2.CronField("*/1")),
},
JobTemplate: kbatchv1.JobTemplateSpec{
Spec: kbatchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{},
},
},
},
},
},
}
Expect(k8sClient.Create(ctx, cronJobv2)).To(Succeed())

cronJobv1 := &batchv1.CronJob{}
Expect(k8sClient.Get(ctx, types.NamespacedName{
Name: "example-v2",
Namespace: testNamespaceName,
}, cronJobv1)).To(Succeed())
Expect(cronJobv1).ToNot(BeNil())
})
})

})
Loading

0 comments on commit 9746828

Please sign in to comment.