-
Notifications
You must be signed in to change notification settings - Fork 53
✨ Add informer test #610
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 informer test #610
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # The watch-objs command | ||
|
|
||
| This is a simple example client of the generated code. | ||
|
|
||
| This client focuses on one Kubernetes namespace and makes an informer on all the | ||
| KubeFlex objects in that namespace. | ||
| When informed of any add/delete/update of such an object, a line is logged; | ||
| V(2) for add/delete, V(4) for update. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "context" | ||
| "flag" | ||
| "fmt" | ||
| "os" | ||
| "time" | ||
|
|
||
| "github.com/spf13/pflag" | ||
|
|
||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/labels" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "k8s.io/client-go/tools/cache" | ||
| "k8s.io/client-go/tools/clientcmd" | ||
| "k8s.io/klog/v2" | ||
|
|
||
| api "github.com/kubestellar/kubeflex/api/v1alpha1" | ||
| kfclient "github.com/kubestellar/kubeflex/pkg/generated/clientset/versioned" | ||
| kfinformers "github.com/kubestellar/kubeflex/pkg/generated/informers/externalversions" | ||
| ) | ||
|
|
||
| const ControllerName = "ensure-label" | ||
|
|
||
| func main() { | ||
| loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() | ||
| overrides := &clientcmd.ConfigOverrides{} | ||
|
|
||
| klog.InitFlags(flag.CommandLine) | ||
| pflag.CommandLine.AddGoFlagSet(flag.CommandLine) | ||
| pflag.CommandLine.StringVar(&loadingRules.ExplicitPath, "kubeconfig", loadingRules.ExplicitPath, "Path to the kubeconfig file to use") | ||
| pflag.CommandLine.StringVar(&overrides.CurrentContext, "context", overrides.CurrentContext, "The name of the kubeconfig context to use") | ||
| pflag.CommandLine.StringVar(&overrides.Context.AuthInfo, "user", overrides.Context.AuthInfo, "The name of the kubeconfig user to use") | ||
| pflag.CommandLine.StringVar(&overrides.Context.Cluster, "cluster", overrides.Context.Cluster, "The name of the kubeconfig cluster to use") | ||
| pflag.CommandLine.StringVarP(&overrides.Context.Namespace, "namespace", "n", overrides.Context.Namespace, "The name of the Kubernetes Namespace to work in (NOT optional)") | ||
| pflag.Parse() | ||
| ctx := context.Background() | ||
| logger := klog.FromContext(ctx) | ||
|
|
||
| logger.V(1).Info("Start", "time", time.Now()) | ||
|
|
||
| pflag.CommandLine.VisitAll(func(f *pflag.Flag) { | ||
| logger.V(1).Info("Flag", "name", f.Name, "value", f.Value.String()) | ||
| }) | ||
|
|
||
| if len(overrides.Context.Namespace) == 0 { | ||
| fmt.Fprintln(os.Stderr, "Namespace must not be the empty string") | ||
| os.Exit(1) | ||
| } else { | ||
| logger.Info("Focusing on one namespace", "name", overrides.Context.Namespace) | ||
| } | ||
|
|
||
| restConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides).ClientConfig() | ||
| if err != nil { | ||
| logger.Error(err, "Failed to construct restConfig") | ||
| os.Exit(10) | ||
| } | ||
| if len(restConfig.UserAgent) == 0 { | ||
| restConfig.UserAgent = ControllerName | ||
| } else { | ||
| restConfig.UserAgent += "/" + ControllerName | ||
| } | ||
|
|
||
| kfClient, err := kfclient.NewForConfig(restConfig) | ||
| if err != nil { | ||
| logger.Error(err, "Failed to construct client") | ||
| os.Exit(10) | ||
| } | ||
| sif := kfinformers.NewSharedInformerFactoryWithOptions(kfClient, 0, kfinformers.WithNamespace(overrides.Context.Namespace)) | ||
| cpPreInf := sif.Tenancy().V1alpha1().ControlPlanes() | ||
| pchPreInf := sif.Tenancy().V1alpha1().PostCreateHooks() | ||
| cpInformer, cpLister := cpPreInf.Informer(), cpPreInf.Lister() | ||
| pchInformer, pchLister := pchPreInf.Informer(), pchPreInf.Lister() | ||
| cpInformer.AddEventHandler(eventHandler[*api.ControlPlane]{logger, "ControlPlane", cpLister}) | ||
| pchInformer.AddEventHandler(eventHandler[*api.PostCreateHook]{logger, "PostCreateHook", pchLister}) | ||
| sif.Start(ctx.Done()) | ||
| if !cache.WaitForCacheSync(ctx.Done(), cpInformer.HasSynced, pchInformer.HasSynced) { | ||
| logger.Error(nil, "Failed to sync informer caches") | ||
| os.Exit(20) | ||
| } | ||
| <-ctx.Done() | ||
| } | ||
|
|
||
| type mrObject interface { | ||
| metav1.Object | ||
| runtime.Object | ||
| } | ||
|
|
||
| type GenericLister[ObjectType mrObject] interface { | ||
| // List lists all objects in the indexer. | ||
| // Objects returned here must be treated as read-only. | ||
| List(selector labels.Selector) (ret []ObjectType, err error) | ||
| // Get retrieves the object from the index for a given name. | ||
| // Objects returned here must be treated as read-only. | ||
| Get(name string) (ObjectType, error) | ||
| } | ||
|
|
||
| type eventHandler[ObjectType mrObject] struct { | ||
| logger klog.Logger | ||
| kind string | ||
| lister GenericLister[ObjectType] | ||
| } | ||
|
|
||
| func (eh eventHandler[ObjectType]) OnAdd(obj any, isInitial bool) { | ||
| mrObj := obj.(mrObject) | ||
|
||
| eh.logger.V(2).Info("Notified of add", "kind", eh.kind, "name", mrObj.GetName()) | ||
| fromCache, err := eh.lister.Get(mrObj.GetName()) | ||
| if err != nil { | ||
| eh.logger.Error(err, "Failed to Get object from lister", "kind", eh.kind, "name", mrObj.GetName()) | ||
| } | ||
| if fromCache.GetName() != mrObj.GetName() { | ||
| eh.logger.Error(nil, "Lister returned object of different name", "kind", eh.kind, "name", mrObj.GetName(), "nameFromLister", fromCache.GetName()) | ||
| } | ||
| } | ||
|
|
||
| func (eh eventHandler[ObjectType]) OnUpdate(oldObj, obj any) { | ||
| mrObj := obj.(mrObject) | ||
|
||
| eh.logger.V(4).Info("Notified of update", "kind", eh.kind, "name", mrObj.GetName()) | ||
| } | ||
|
|
||
| func (eh eventHandler[ObjectType]) OnDelete(obj any) { | ||
| if dfsu, is := obj.(cache.DeletedFinalStateUnknown); is { | ||
| obj = dfsu.Obj | ||
| } | ||
| mrObj := obj.(mrObject) | ||
|
||
| eh.logger.V(2).Info("Notified of delete", "kind", eh.kind, "name", mrObj.GetName()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||||||||||
| set -x | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| function waitfor() { | ||||||||||||||||||||||||||||||||||||||||||
| cmd="$1" | ||||||||||||||||||||||||||||||||||||||||||
| let count=1 | ||||||||||||||||||||||||||||||||||||||||||
| while true; do | ||||||||||||||||||||||||||||||||||||||||||
| sleep 5 | ||||||||||||||||||||||||||||||||||||||||||
| if { eval "$cmd" ; } ; then return 0; fi | ||||||||||||||||||||||||||||||||||||||||||
| let count=count+1 | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+8
to
+12
|
||||||||||||||||||||||||||||||||||||||||||
| let count=1 | |
| while true; do | |
| sleep 5 | |
| if { eval "$cmd" ; } ; then return 0; fi | |
| let count=count+1 | |
| local -i count=1 | |
| while true; do | |
| sleep 5 | |
| if { eval "$cmd" ; } ; then return 0; fi | |
| (( count++ )) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meh
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'let' command is deprecated in modern bash scripts. Use arithmetic expansion '(( count++ ))' or '(( count += 1 ))' instead for better portability and clarity.
| let count=1 | |
| while true; do | |
| sleep 5 | |
| if { eval "$cmd" ; } ; then return 0; fi | |
| let count=count+1 | |
| count=1 | |
| while true; do | |
| sleep 5 | |
| if { eval "$cmd" ; } ; then return 0; fi | |
| (( count++ )) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duplicate comment
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path 'bin/watch-objs' is relative and may fail depending on the current working directory. Consider using an absolute path or ensuring the script changes to a known directory (like REPO_ROOT) before executing this command.
| bin/watch-objs -n default -v=4 &> $logfile & | |
| "$REPO_ROOT/bin/watch-objs" -n default -v=4 &> "$logfile" & |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a bad idea.
But there is plenty of precedent in this and other test scripts for that assumption.
Maybe do a comprehensive improvement in a separate PR.
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These commands use './bin/kflex' while line 24 uses 'bin/watch-objs' (without the leading dot). For consistency, either prefix all paths with './' or omit it from all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interface is generic but the comments specifically mention PostCreateHook. Should probably say "objects" instead since this is used for both ControlPlane and PostCreateHook.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed