Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📖 Add more examples in documentation #2498

Merged
merged 2 commits into from Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
84 changes: 82 additions & 2 deletions example_test.go
Expand Up @@ -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"
Expand All @@ -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 {
Expand All @@ -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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the fact that this has a watch is the special bit about this example. And I'd rather not have an example that doesn't implement DeepCopy but just panics. Maybe do something like a special configmap key that references something else or such?

Suggested change
func Example_watches() {
func ExampleCustomHandler() {

Copy link
Contributor Author

@aerfio aerfio Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the fact that this has a watch is the special bit about this example.

That was my intention, to have an easy to grasp example for "Watches" 😢

Maybe do something like a special configmap key that references something else or such?

I didn't want to complicate this example too much, I thought about switching from LocalObjectReference to e.g ConfigMapKeySelector, but the code inside Watches would a too complicated for an example imho.

And I'd rather not have an example that doesn't implement DeepCopy but just panics.

Sure, I've changed it to json.Marshal + json.Unmarshal with panic on error, is that enough? I could use controller-gen for that, but that would result in new file in the root of the repo + some changes in makefile, which I wanted to omit

Update: I tried it and controller-gen seems to ignore *_test.go files 🤷🏻

And lastly, for example name I changed it now like that:

diff --git a/example_test.go b/example_test.go
index cbbf032b..83f6fcdb 100644
--- a/example_test.go
+++ b/example_test.go
@@ -108,7 +108,7 @@ func (in *ExampleCRDWithConfigMapRefList) DeepCopyObject() runtime.Object {
 // 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{})

to follow other examples in this repo (in this file also, see https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.2/example_test.go#L77) and also https://go.dev/blog/examples#example-function-names

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.
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pkg/builder/example_test.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 15 additions & 2 deletions pkg/client/example_test.go
Expand Up @@ -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 (
Expand Down Expand Up @@ -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.
Expand All @@ -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)
}

Expand All @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please mention why the dance of converting to unstructured is needed

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{}
Expand Down
6 changes: 4 additions & 2 deletions pkg/handler/example_test.go
Expand Up @@ -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).
Expand Down