Repository: etcd-io/etcd-operator
Pull request #139: Update GetKubernetesResource's signiture and move it into utils package
Author: login: ahrtr
Labels: name: approved
name: size/L
Created: 2025-05-09T12:03:36Z | Merged: 2025-05-11T19:14:55Z

Followup to https://github.com/etcd-io/etcd-operator/pull/120#discussion_r2061531928

cc @ivanvc @jmhbnz @abdurrehman107 @hakman @ArkaSaha30 


Diff:
diff --git a/test/e2e/etcd_options_test.go b/test/e2e/etcd_options_test.go
index dd0b1a5d..55e05690 100644
--- a/test/e2e/etcd_options_test.go
+++ b/test/e2e/etcd_options_test.go
@@ -25,6 +25,7 @@ import (
 
 	appsv1 "k8s.io/api/apps/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtimeClient "sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/e2e-framework/klient/k8s"
 	"sigs.k8s.io/e2e-framework/klient/wait"
 	"sigs.k8s.io/e2e-framework/klient/wait/conditions"
@@ -33,6 +34,7 @@ import (
 	"sigs.k8s.io/e2e-framework/pkg/features"
 
 	ecv1alpha1 "go.etcd.io/etcd-operator/api/v1alpha1"
+	"go.etcd.io/etcd-operator/test/utils"
 )
 
 func TestEtcdOptions(t *testing.T) {
@@ -67,12 +69,7 @@ func TestEtcdOptions(t *testing.T) {
 		},
 	}
 
-	stsObj := &appsv1.StatefulSet{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      etcdClusterName,
-			Namespace: namespace,
-		},
-	}
+	objKey := runtimeClient.ObjectKeyFromObject(etcdCluster)
 
 	feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
 
@@ -95,7 +92,7 @@ func TestEtcdOptions(t *testing.T) {
 			wait.WithInterval(5 * time.Second),
 		}
 
-		if err := getKubernetesResource(ctx, etcdCluster, client, &ec, ops...); err != nil {
+		if err := utils.GetKubernetesResource(ctx, client, objKey, &ec, ops...); err != nil {
 			t.Fatalf("unable to fetch etcd cluster: %s", err)
 		}
 
@@ -113,7 +110,7 @@ func TestEtcdOptions(t *testing.T) {
 				wait.WithInterval(5 * time.Second),
 			}
 
-			if err := getKubernetesResource(ctx, stsObj, client, &sts, ops...); err != nil {
+			if err := utils.GetKubernetesResource(ctx, client, objKey, &sts, ops...); err != nil {
 				t.Fatal(err)
 			}
 			return ctx
@@ -126,11 +123,11 @@ func TestEtcdOptions(t *testing.T) {
 			client := cfg.Client()
 			var sts appsv1.StatefulSet
 
-			if err := getKubernetesResource(ctx, stsObj, client, &sts); err != nil {
+			if err := utils.GetKubernetesResource(ctx, client, objKey, &sts); err != nil {
 				t.Fatal(err)
 			}
 
-			etcdContainer, err := getContainerByName(sts.Spec.Template.Spec.Containers, "etcd")
+			etcdContainer, err := utils.GetContainerByName(sts.Spec.Template.Spec.Containers, "etcd")
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -148,7 +145,7 @@ func TestEtcdOptions(t *testing.T) {
 			client := cfg.Client()
 			var sts appsv1.StatefulSet
 
-			if err := getKubernetesResource(ctx, stsObj, client, &sts); err != nil {
+			if err := utils.GetKubernetesResource(ctx, client, objKey, &sts); err != nil {
 				t.Fatal(err)
 			}
 
diff --git a/test/e2e/utils.go b/test/e2e/utils.go
deleted file mode 100644
index 771d11f8..00000000
--- a/test/e2e/utils.go
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-Copyright 2025.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package e2e
-
-import (
-	"context"
-	"fmt"
-
-	corev1 "k8s.io/api/core/v1"
-	runtimeClient "sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/e2e-framework/klient"
-	"sigs.k8s.io/e2e-framework/klient/k8s"
-	"sigs.k8s.io/e2e-framework/klient/wait"
-	"sigs.k8s.io/e2e-framework/klient/wait/conditions"
-)
-
-func getContainerByName(containers []corev1.Container, name string) (corev1.Container, error) {
-	var container corev1.Container
-	for c := range containers {
-		if containers[c].Name == name {
-			container = containers[c]
-			return container, nil
-		}
-	}
-	return container, fmt.Errorf("container named %s not found", name)
-}
-
-// Gets K8S object from cluster, accepts wait options. Returns error if resource does not exist
-func getKubernetesResource(
-	ctx context.Context,
-	object k8s.Object,
-	client klient.Client,
-	outObj k8s.Object, options ...wait.Option) error {
-	objectKey := runtimeClient.ObjectKeyFromObject(object)
-
-	if err := wait.For(
-		conditions.New(client.Resources()).ResourceMatch(object,
-			func(object k8s.Object) bool {
-				err := client.Resources().Get(ctx, objectKey.Name, objectKey.Namespace, outObj)
-				return err == nil
-			}),
-		options...,
-	); err != nil {
-		return fmt.Errorf("unable to find %s: %s", &objectKey, err)
-	}
-	return nil
-}
diff --git a/test/utils/utils.go b/test/utils/utils.go
index 55784144..1eb48db2 100644
--- a/test/utils/utils.go
+++ b/test/utils/utils.go
@@ -27,6 +27,11 @@ import (
 	"path/filepath"
 	"strings"
 
+	corev1 "k8s.io/api/core/v1"
+	runtimeClient "sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/e2e-framework/klient"
+	"sigs.k8s.io/e2e-framework/klient/k8s"
+	"sigs.k8s.io/e2e-framework/klient/wait"
 	"sigs.k8s.io/e2e-framework/support/kind"
 )
 
@@ -245,6 +250,7 @@ func UncommentCode(filename, target, prefix string) error {
 	return os.WriteFile(filename, out.Bytes(), 0644)
 }
 
+// LoadContainerImageToKindCluster loads an image into the kind cluster.
 // LoadImage does not work when using podman, so LoadImageArchive is used instead
 // refer to https://github.com/kubernetes-sigs/kind/issues/2038
 func LoadContainerImageToKindCluster(ctx context.Context,
@@ -291,3 +297,32 @@ func useImageArchive(ctx context.Context, kindCluster *kind.Cluster) error {
 	}
 	return nil
 }
+
+// GetKubernetesResource retrieves a Kubernetes object from the cluster.
+// It accepts wait options and returns an error if the resource cannot be found within the given conditions.
+func GetKubernetesResource(ctx context.Context, client klient.Client, objKey runtimeClient.ObjectKey,
+	outObj k8s.Object, options ...wait.Option) error {
+	if err := wait.For(
+		func(context.Context) (done bool, err error) {
+			rerr := client.Resources().Get(ctx, objKey.Name, objKey.Namespace, outObj)
+			return rerr == nil, rerr
+		},
+		options...,
+	); err != nil {
+		return fmt.Errorf("unable to find %s: %w", objKey.String(), err)
+	}
+
+	return nil
+}
+
+// GetContainerByName retrieves a container by name, if found.
+func GetContainerByName(containers []corev1.Container, name string) (corev1.Container, error) {
+	var container corev1.Container
+	for c := range containers {
+		if containers[c].Name == name {
+			container = containers[c]
+			return container, nil
+		}
+	}
+	return container, fmt.Errorf("container named %s not found", name)
+}


Reviews:
user: login: ivanvc
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T23:20:58Z
user: login: ahrtr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-10T07:55:37Z
user: login: jmhbnz
body: LGTM - Thanks @ahrtr 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-11T00:18:12Z

Review comments:
diff_hunk: @@ -291,3 +297,32 @@ func useImageArchive(ctx context.Context, kindCluster *kind.Cluster) error {
 	}
 	return nil
 }
+
+// GetKubernetesResource retrieves a Kubernetes object from the cluster.
+// It accepts wait options and returns an error if the resource cannot be found within the given conditions.
+func GetKubernetesResource(ctx context.Context, client klient.Client, objKey runtimeClient.ObjectKey,
path: test/utils/utils.go
user: login: ivanvc
body: If this function returns an error if the resource is not found, I think it could have a better name, like `IsKubernetesResourcePresent(...)` or `doesKubernetesResourceExist(...)`. I just find conflicting `Get`, and the function returns not the object, but only an error.

I know this is just an implementation of the original function, but it could be a good opportunity for a rename.
created_at: 2025-05-09T23:20:58Z
author_association: MEMBER
diff_hunk: @@ -291,3 +297,32 @@ func useImageArchive(ctx context.Context, kindCluster *kind.Cluster) error {
 	}
 	return nil
 }
+
+// GetKubernetesResource retrieves a Kubernetes object from the cluster.
+// It accepts wait options and returns an error if the resource cannot be found within the given conditions.
+func GetKubernetesResource(ctx context.Context, client klient.Client, objKey runtimeClient.ObjectKey,
path: test/utils/utils.go
user: login: ahrtr
body: The returned object is `outObj` ( the 4th parameter)
created_at: 2025-05-10T07:55:33Z
author_association: MEMBER

PR comments:
user: login: k8s-ci-robot
created_at: 2025-05-11T00:18:15Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/etcd-io/etcd-operator/pull/139#" title="Author self-approved">ahrtr</a>*, *<a href="https://github.com/etcd-io/etcd-operator/pull/139#pullrequestreview-2831172121" title="Approved">jmhbnz</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=etcd-io%2Fetcd-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/etcd-io/etcd-operator/blob/main/OWNERS)~~ [ahrtr,jmhbnz]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: NONE