From 77fdfc735a4248f173916391548af830b32437da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Puczyn=CC=81ski?= Date: Mon, 18 Sep 2023 17:36:59 +0200 Subject: [PATCH 1/2] Add more examples in documentation --- example_test.go | 84 ++++++++++++++++++++++++++++++++++++- pkg/builder/example_test.go | 4 +- pkg/client/example_test.go | 17 +++++++- pkg/handler/example_test.go | 6 ++- 4 files changed, 103 insertions(+), 8 deletions(-) diff --git a/example_test.go b/example_test.go index 381959d5bb..c038ec6765 100644 --- a/example_test.go +++ b/example_test.go @@ -24,8 +24,14 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" // since we invoke tests with -ginkgo.junit-report we need to import ginkgo. _ "github.com/onsi/ginkgo/v2" @@ -38,7 +44,7 @@ import ( // // * Start the application. func Example() { - var log = ctrl.Log.WithName("builder-examples") + log := ctrl.Log.WithName("builder-examples") manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) if err != nil { @@ -62,6 +68,80 @@ func Example() { } } +type ExampleCRDWithConfigMapRef struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + ConfigMapRef corev1.LocalObjectReference `json:"configMapRef"` +} + +// DeepCopyObject implements client.Object. +func (*ExampleCRDWithConfigMapRef) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +type ExampleCRDWithConfigMapRefList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ExampleCRDWithConfigMapRef `json:"items"` +} + +// DeepCopyObject implements client.ObjectList. +func (*ExampleCRDWithConfigMapRefList) DeepCopyObject() runtime.Object { + panic("unimplemented") +} + +// This example creates a simple application Controller that is configured for ExampleCRDWithConfigMapRef CRD. +// Any change in the configMap referenced in this Custom Resource will cause the re-reconcile of the parent ExampleCRDWithConfigMapRef +// due to the implementation of the .Watches method of "sigs.k8s.io/controller-runtime/pkg/builder".Builder. +func Example_watches() { + log := ctrl.Log.WithName("builder-examples") + + manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) + if err != nil { + log.Error(err, "could not create manager") + os.Exit(1) + } + + err = ctrl. + NewControllerManagedBy(manager). + For(&ExampleCRDWithConfigMapRef{}). + Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, cm client.Object) []ctrl.Request { + // map a change to referenced configMap to ExampleCRDWithConfigMapRef, which causes its re-reconcile + crList := &ExampleCRDWithConfigMapRefList{} + if err := manager.GetClient().List(ctx, crList); err != nil { + manager.GetLogger().Error(err, "while listing ExampleCRDWithConfigMapRefs") + return nil + } + + reqs := make([]ctrl.Request, 0, len(crList.Items)) + for _, item := range crList.Items { + if item.ConfigMapRef.Name == cm.GetName() { + reqs = append(reqs, ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: item.GetNamespace(), + Name: item.GetName(), + }, + }) + } + } + + return reqs + })). + Complete(reconcile.Func(func(ctx context.Context, r reconcile.Request) (reconcile.Result, error) { + // Your business logic to implement the API by creating, updating, deleting objects goes here. + return reconcile.Result{}, nil + })) + if err != nil { + log.Error(err, "could not create controller") + os.Exit(1) + } + + if err := manager.Start(ctrl.SetupSignalHandler()); err != nil { + log.Error(err, "could not start manager") + os.Exit(1) + } +} + // This example creates a simple application Controller that is configured for ReplicaSets and Pods. // This application controller will be running leader election with the provided configuration in the manager options. // If leader election configuration is not provided, controller runs leader election with default values. @@ -75,7 +155,7 @@ func Example() { // // * Start the application. func Example_updateLeaderElectionDurations() { - var log = ctrl.Log.WithName("builder-examples") + log := ctrl.Log.WithName("builder-examples") leaseDuration := 100 * time.Second renewDeadline := 80 * time.Second retryPeriod := 20 * time.Second diff --git a/pkg/builder/example_test.go b/pkg/builder/example_test.go index 652c9b5833..e265853987 100644 --- a/pkg/builder/example_test.go +++ b/pkg/builder/example_test.go @@ -38,7 +38,7 @@ import ( func ExampleBuilder_metadata_only() { logf.SetLogger(zap.New()) - var log = logf.Log.WithName("builder-examples") + log := logf.Log.WithName("builder-examples") mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{}) if err != nil { @@ -95,7 +95,7 @@ func ExampleBuilder_metadata_only() { func ExampleBuilder() { logf.SetLogger(zap.New()) - var log = logf.Log.WithName("builder-examples") + log := logf.Log.WithName("builder-examples") mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{}) if err != nil { diff --git a/pkg/client/example_test.go b/pkg/client/example_test.go index 7d4cb8c616..03344b5315 100644 --- a/pkg/client/example_test.go +++ b/pkg/client/example_test.go @@ -25,11 +25,14 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + corev1ac "k8s.io/client-go/applyconfigurations/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) var ( @@ -159,7 +162,7 @@ func ExampleClient_update() { Namespace: "namespace", Name: "name", }, pod) - pod.SetFinalizers(append(pod.GetFinalizers(), "new-finalizer")) + controllerutil.AddFinalizer(pod, "new-finalizer") _ = c.Update(context.Background(), pod) // Using a unstructured object. @@ -173,7 +176,7 @@ func ExampleClient_update() { Namespace: "namespace", Name: "name", }, u) - u.SetFinalizers(append(u.GetFinalizers(), "new-finalizer")) + controllerutil.AddFinalizer(u, "new-finalizer") _ = c.Update(context.Background(), u) } @@ -188,6 +191,16 @@ func ExampleClient_patch() { }, client.RawPatch(types.StrategicMergePatchType, patch)) } +// This example shows how to use the client with unstructured objects to create/patch objects using Server Side Apply, +func ExampleClient_apply() { + // Using a typed object. + configMap := corev1ac.ConfigMap("name", "namespace").WithData(map[string]string{"key": "value"}) + // c is a created client. + u := &unstructured.Unstructured{} + u.Object, _ = runtime.DefaultUnstructuredConverter.ToUnstructured(configMap) + _ = c.Patch(context.Background(), u, client.Apply, client.ForceOwnership, client.FieldOwner("field-owner")) +} + // This example shows how to use the client with typed and unstructured objects to patch objects' status. func ExampleClient_patchStatus() { u := &unstructured.Unstructured{} diff --git a/pkg/handler/example_test.go b/pkg/handler/example_test.go index ad07e4e31d..575ea05fca 100644 --- a/pkg/handler/example_test.go +++ b/pkg/handler/example_test.go @@ -32,8 +32,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" ) -var mgr manager.Manager -var c controller.Controller +var ( + mgr manager.Manager + c controller.Controller +) // This example watches Pods and enqueues Requests with the Name and Namespace of the Pod from // the Event (i.e. change caused by a Create, Update, Delete). From c183365c722a674ca3e91c75ba9935dec02f9b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Puczyn=CC=81ski?= Date: Tue, 26 Sep 2023 10:41:19 +0200 Subject: [PATCH 2/2] update examples after review --- example_test.go | 27 +++++++++++++++++++++------ pkg/client/example_test.go | 2 ++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/example_test.go b/example_test.go index c038ec6765..cbbf032b0f 100644 --- a/example_test.go +++ b/example_test.go @@ -18,6 +18,7 @@ package controllerruntime_test import ( "context" + "encoding/json" "fmt" "os" "time" @@ -74,9 +75,23 @@ type ExampleCRDWithConfigMapRef struct { ConfigMapRef corev1.LocalObjectReference `json:"configMapRef"` } +func deepCopyObject(arg any) runtime.Object { + // DO NOT use this code in production code, this is only for presentation purposes. + // in real code you should generate DeepCopy methods by using controller-gen CLI tool. + argBytes, err := json.Marshal(arg) + if err != nil { + panic(err) + } + out := &ExampleCRDWithConfigMapRefList{} + if err := json.Unmarshal(argBytes, out); err != nil { + panic(err) + } + return out +} + // DeepCopyObject implements client.Object. -func (*ExampleCRDWithConfigMapRef) DeepCopyObject() runtime.Object { - panic("unimplemented") +func (in *ExampleCRDWithConfigMapRef) DeepCopyObject() runtime.Object { + return deepCopyObject(in) } type ExampleCRDWithConfigMapRefList struct { @@ -86,14 +101,14 @@ type ExampleCRDWithConfigMapRefList struct { } // DeepCopyObject implements client.ObjectList. -func (*ExampleCRDWithConfigMapRefList) DeepCopyObject() runtime.Object { - panic("unimplemented") +func (in *ExampleCRDWithConfigMapRefList) DeepCopyObject() runtime.Object { + return deepCopyObject(in) } // This example creates a simple application Controller that is configured for ExampleCRDWithConfigMapRef CRD. // Any change in the configMap referenced in this Custom Resource will cause the re-reconcile of the parent ExampleCRDWithConfigMapRef // due to the implementation of the .Watches method of "sigs.k8s.io/controller-runtime/pkg/builder".Builder. -func Example_watches() { +func Example_customHandler() { log := ctrl.Log.WithName("builder-examples") manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) @@ -106,7 +121,7 @@ func Example_watches() { NewControllerManagedBy(manager). For(&ExampleCRDWithConfigMapRef{}). Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, cm client.Object) []ctrl.Request { - // map a change to referenced configMap to ExampleCRDWithConfigMapRef, which causes its re-reconcile + // map a change from referenced configMap to ExampleCRDWithConfigMapRef, which causes its re-reconcile crList := &ExampleCRDWithConfigMapRefList{} if err := manager.GetClient().List(ctx, crList); err != nil { manager.GetLogger().Error(err, "while listing ExampleCRDWithConfigMapRefs") diff --git a/pkg/client/example_test.go b/pkg/client/example_test.go index 03344b5315..e433fc45c1 100644 --- a/pkg/client/example_test.go +++ b/pkg/client/example_test.go @@ -192,6 +192,8 @@ func ExampleClient_patch() { } // This example shows how to use the client with unstructured objects to create/patch objects using Server Side Apply, +// "k8s.io/apimachinery/pkg/runtime".DefaultUnstructuredConverter.ToUnstructured is used to convert an object into map[string]any representation, +// which is then set as an "Object" field in *unstructured.Unstructured struct, which implements client.Object. func ExampleClient_apply() { // Using a typed object. configMap := corev1ac.ConfigMap("name", "namespace").WithData(map[string]string{"key": "value"})