Repository: rh-ecosystem-edge/kernel-module-management
Pull request #1460: Updating image puller package with the new pod flow
Author: login: yevgeny-shnaidman
Labels: name: approved
name: lgtm
Created: 2025-04-30T07:16:20Z | Merged: 2025-04-30T08:29:13Z

This commit contains the following changes:
1. Removing image label - image will be obtained from the Spec directly
2. Adding pod type label - one-time pod that should be considered as Failed once the container fails on image pull, and run-until-success, that is considered as InProgess, in case container fails on image pull and there are build instructions
3. Adding GetPodStatus API - this api returns pod status based on the following:
   - the pod that fails to pull image stays in the Pending state, and the container status is updated with appropriate reason.So, if the pod is one-time pod, and container failed on image pull - status is Failed. Otherwise status is Running, since we let the pod restart the container with back-offs
4. Pod is created with restartPolicy Never, since if the pod managed to pull image - we consider it a success

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Improved reporting of pod image pull status with more precise status categories.
- **Bug Fixes**
	- Enhanced pod identification and filtering by using new label keys and pod spec fields.
- **Tests**
	- Expanded test coverage for pod status determination and updated tests to reflect new label usage and logic.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/internal/pod/imagepuller.go b/internal/pod/imagepuller.go
index becdfe613..3953d7dd1 100644
--- a/internal/pod/imagepuller.go
+++ b/internal/pod/imagepuller.go
@@ -12,6 +12,26 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
+type PullPodStatus string
+
+const (
+	PullImageFailed        PullPodStatus = "pullFailed"
+	PullImageSuccess       PullPodStatus = "pullSuccess"
+	PullImageInProcess     PullPodStatus = "pullInProcess"
+	PullImageUnexpectedErr PullPodStatus = "unexpectedError"
+
+	imagePullBackOffReason = "ImagePullBackOff"
+	errImagePullReason     = "ErrImagePull"
+
+	moduleImageLabelKey = "kmm.node.kubernetes.io/module-image-config"
+	pullPodTypeLabelKey = "kmm.node.kubernetes.io/pull-pod-type"
+
+	pullerContainerName = "puller"
+
+	pullPodTypeOneTime  = "one-time-pull"
+	pullPodUntilSuccess = "until-success"
+)
+
 //go:generate mockgen -source=imagepuller.go -package=pod -destination=mock_imagepuller.go
 
 type ImagePuller interface {
@@ -20,14 +40,9 @@ type ImagePuller interface {
 	ListPullPods(ctx context.Context, micObj *kmmv1beta1.ModuleImagesConfig) ([]v1.Pod, error)
 	GetPullPodForImage(pods []v1.Pod, image string) *v1.Pod
 	GetPullPodImage(pod v1.Pod) string
+	GetPullPodStatus(pod *v1.Pod) PullPodStatus
 }
 
-const (
-	moduleImageLabelKey = "kmm.node.kubernetes.io/module-image-config"
-	imageLabelKey       = "kmm.node.kubernetes.io/module-image"
-	pullerContainerName = "puller"
-)
-
 type imagePullerImpl struct {
 	client client.Client
 	scheme *runtime.Scheme
@@ -43,9 +58,9 @@ func NewImagePuller(client client.Client, scheme *runtime.Scheme) ImagePuller {
 func (ipi *imagePullerImpl) CreatePullPod(ctx context.Context, imageSpec *kmmv1beta1.ModuleImageSpec,
 	micObj *kmmv1beta1.ModuleImagesConfig) error {
 
-	restartPolicy := v1.RestartPolicyOnFailure
+	pullPodTypeLabeValue := pullPodUntilSuccess
 	if imageSpec.Build != nil || imageSpec.Sign != nil {
-		restartPolicy = v1.RestartPolicyNever
+		pullPodTypeLabeValue = pullPodTypeOneTime
 	}
 
 	imagePullSecrets := []v1.LocalObjectReference{}
@@ -59,7 +74,7 @@ func (ipi *imagePullerImpl) CreatePullPod(ctx context.Context, imageSpec *kmmv1b
 			Namespace:    micObj.Namespace,
 			Labels: map[string]string{
 				moduleImageLabelKey: micObj.Name,
-				imageLabelKey:       imageSpec.Image,
+				pullPodTypeLabelKey: pullPodTypeLabeValue,
 			},
 		},
 		Spec: v1.PodSpec{
@@ -70,7 +85,7 @@ func (ipi *imagePullerImpl) CreatePullPod(ctx context.Context, imageSpec *kmmv1b
 					Command: []string{"/bin/sh", "-c", "exit 0"},
 				},
 			},
-			RestartPolicy:    restartPolicy,
+			RestartPolicy:    v1.RestartPolicyNever,
 			ImagePullSecrets: imagePullSecrets,
 		},
 	}
@@ -92,7 +107,7 @@ func (ipi *imagePullerImpl) ListPullPods(ctx context.Context, micObj *kmmv1beta1
 
 	pl := v1.PodList{}
 
-	hl := client.HasLabels{imageLabelKey}
+	hl := client.HasLabels{pullPodTypeLabelKey}
 	ml := client.MatchingLabels{moduleImageLabelKey: micObj.Name}
 
 	ctrl.LoggerFrom(ctx).WithValues("mic name", micObj.Name).V(1).Info("Listing mic image Pods")
@@ -107,7 +122,7 @@ func (ipi *imagePullerImpl) ListPullPods(ctx context.Context, micObj *kmmv1beta1
 func (ipi *imagePullerImpl) GetPullPodForImage(pods []v1.Pod, image string) *v1.Pod {
 
 	for i, pod := range pods {
-		if image == pod.Labels[imageLabelKey] {
+		if image == pod.Spec.Containers[0].Image {
 			return &pods[i]
 		}
 	}
@@ -115,5 +130,38 @@ func (ipi *imagePullerImpl) GetPullPodForImage(pods []v1.Pod, image string) *v1.
 }
 
 func (ipi *imagePullerImpl) GetPullPodImage(pod v1.Pod) string {
-	return pod.Labels[imageLabelKey]
+	return pod.Spec.Containers[0].Image
+}
+
+func (ipi *imagePullerImpl) GetPullPodStatus(pod *v1.Pod) PullPodStatus {
+	switch pod.Status.Phase {
+	case v1.PodSucceeded:
+		return PullImageSuccess
+	case v1.PodFailed, v1.PodUnknown:
+		return PullImageUnexpectedErr
+	case v1.PodRunning:
+		return PullImageInProcess
+	case v1.PodPending:
+		// no container statuses yet, the pod is just starting to pull images
+		if pod.Status.ContainerStatuses == nil {
+			return PullImageInProcess
+		}
+
+		// no wating status, the pull process is still in progress
+		if pod.Status.ContainerStatuses[0].State.Waiting == nil {
+			return PullImageInProcess
+		}
+
+		pullPodType := pod.GetLabels()[pullPodTypeLabelKey]
+		// if pod is targeted to wait till the end - return the InProgress
+		if pullPodType == pullPodUntilSuccess {
+			return PullImageInProcess
+		}
+
+		if waitingReason := pod.Status.ContainerStatuses[0].State.Waiting.Reason; waitingReason == imagePullBackOffReason || waitingReason == errImagePullReason {
+			return PullImageFailed
+		}
+	}
+
+	return PullImageUnexpectedErr
 }
diff --git a/internal/pod/imagepuller_test.go b/internal/pod/imagepuller_test.go
index 59d1a0b4e..5caee1357 100644
--- a/internal/pod/imagepuller_test.go
+++ b/internal/pod/imagepuller_test.go
@@ -38,7 +38,7 @@ var _ = Describe("ListPullPods", func() {
 	}
 
 	It("list succeeded", func() {
-		hl := ctrlclient.HasLabels{imageLabelKey}
+		hl := ctrlclient.HasLabels{pullPodTypeLabelKey}
 		ml := ctrlclient.MatchingLabels{moduleImageLabelKey: testMic.Name}
 
 		clnt.EXPECT().List(context.Background(), gomock.Any(), ctrlclient.InNamespace(testMic.Namespace), hl, ml).DoAndReturn(
@@ -54,7 +54,7 @@ var _ = Describe("ListPullPods", func() {
 	})
 
 	It("list failed", func() {
-		hl := ctrlclient.HasLabels{imageLabelKey}
+		hl := ctrlclient.HasLabels{pullPodTypeLabelKey}
 		ml := ctrlclient.MatchingLabels{moduleImageLabelKey: testMic.Name}
 
 		clnt.EXPECT().List(context.Background(), gomock.Any(), ctrlclient.InNamespace(testMic.Namespace), hl, ml).Return(fmt.Errorf("some error"))
@@ -116,13 +116,21 @@ var _ = Describe("GetPullPodForImage", func() {
 
 	pullPods := []v1.Pod{
 		{
-			ObjectMeta: metav1.ObjectMeta{
-				Labels: map[string]string{imageLabelKey: "image 1"},
+			Spec: v1.PodSpec{
+				Containers: []v1.Container{
+					{
+						Image: "image 1",
+					},
+				},
 			},
 		},
 		{
-			ObjectMeta: metav1.ObjectMeta{
-				Labels: map[string]string{imageLabelKey: "image 2"},
+			Spec: v1.PodSpec{
+				Containers: []v1.Container{
+					{
+						Image: "image 2",
+					},
+				},
 			},
 		},
 	}
@@ -130,7 +138,7 @@ var _ = Describe("GetPullPodForImage", func() {
 	It("there is a pull pod for that image", func() {
 		res := ip.GetPullPodForImage(pullPods, "image 2")
 		Expect(res).ToNot(BeNil())
-		Expect(res.Labels[imageLabelKey]).To(Equal("image 2"))
+		Expect(res.Spec.Containers[0].Image).To(Equal("image 2"))
 	})
 
 	It("there is no pull pod for that image", func() {
@@ -170,7 +178,7 @@ var _ = Describe("CreatePullPod", func() {
 				Namespace:    testMic.Namespace,
 				Labels: map[string]string{
 					moduleImageLabelKey: "some name",
-					imageLabelKey:       "some image",
+					pullPodTypeLabelKey: pullPodUntilSuccess,
 				},
 			},
 			Spec: v1.PodSpec{
@@ -181,7 +189,7 @@ var _ = Describe("CreatePullPod", func() {
 						Command: []string{"/bin/sh", "-c", "exit 0"},
 					},
 				},
-				RestartPolicy:    v1.RestartPolicyOnFailure,
+				RestartPolicy:    v1.RestartPolicyNever,
 				ImagePullSecrets: []v1.LocalObjectReference{},
 			},
 		}
@@ -202,3 +210,82 @@ var _ = Describe("CreatePullPod", func() {
 		Expect(err).To(BeNil())
 	})
 })
+
+var _ = Describe("GetPullPodStatus", func() {
+	var (
+		ctrl *gomock.Controller
+		clnt *client.MockClient
+		ip   ImagePuller
+	)
+
+	BeforeEach(func() {
+		ctrl = gomock.NewController(GinkgoT())
+		clnt = client.NewMockClient(ctrl)
+		ip = NewImagePuller(clnt, scheme)
+	})
+
+	testPod := v1.Pod{
+		Status: v1.PodStatus{
+			Phase: v1.PodSucceeded,
+		},
+	}
+
+	It("check different phases except for PodPending", func() {
+		By("phase Succeeded")
+
+		res := ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageSuccess))
+
+		By("phase PodFailed")
+		testPod.Status.Phase = v1.PodFailed
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageUnexpectedErr))
+
+		By("phase PodUnknown")
+		testPod.Status.Phase = v1.PodUnknown
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageUnexpectedErr))
+
+		By("phase PodRunning")
+		testPod.Status.Phase = v1.PodRunning
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageInProcess))
+	})
+
+	It("check container statuses for PodPending", func() {
+		testPod.Status.Phase = v1.PodPending
+		By("container statuses missing")
+		testPod.Status.ContainerStatuses = nil
+		res := ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageInProcess))
+
+		By("container statuses waiting is nil")
+		testPod.Status.ContainerStatuses = []v1.ContainerStatus{v1.ContainerStatus{}}
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageInProcess))
+
+		By("container statuses waiting is not nil and pod is not one time pull pod")
+		testPod.SetLabels(map[string]string{pullPodTypeLabelKey: pullPodUntilSuccess})
+		testPod.Status.ContainerStatuses[0].State = v1.ContainerState{
+			Waiting: &v1.ContainerStateWaiting{},
+		}
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageInProcess))
+
+		By("container statuses waiting is not nil and pod is one time pull pod and reason is not image pull error")
+		testPod.SetLabels(map[string]string{pullPodTypeLabelKey: pullPodTypeOneTime})
+		testPod.Status.ContainerStatuses[0].State.Waiting.Reason = "some reason"
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageUnexpectedErr))
+
+		By("container statuses waiting is not nil and pod is one time pull pod and reason is ImagePullBackOff")
+		testPod.Status.ContainerStatuses[0].State.Waiting.Reason = imagePullBackOffReason
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageFailed))
+
+		By("container statuses waiting is not nil and pod is one time pull pod and reason is errImagePullReason")
+		testPod.Status.ContainerStatuses[0].State.Waiting.Reason = errImagePullReason
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageFailed))
+	})
+})
diff --git a/internal/pod/mock_imagepuller.go b/internal/pod/mock_imagepuller.go
index 703639640..69fdf717f 100644
--- a/internal/pod/mock_imagepuller.go
+++ b/internal/pod/mock_imagepuller.go
@@ -96,6 +96,20 @@ func (mr *MockImagePullerMockRecorder) GetPullPodImage(pod any) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullPodImage", reflect.TypeOf((*MockImagePuller)(nil).GetPullPodImage), pod)
 }
 
+// GetPullPodStatus mocks base method.
+func (m *MockImagePuller) GetPullPodStatus(pod *v1.Pod) PullPodStatus {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPullPodStatus", pod)
+	ret0, _ := ret[0].(PullPodStatus)
+	return ret0
+}
+
+// GetPullPodStatus indicates an expected call of GetPullPodStatus.
+func (mr *MockImagePullerMockRecorder) GetPullPodStatus(pod any) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullPodStatus", reflect.TypeOf((*MockImagePuller)(nil).GetPullPodStatus), pod)
+}
+
 // ListPullPods mocks base method.
 func (m *MockImagePuller) ListPullPods(ctx context.Context, micObj *v1beta1.ModuleImagesConfig) ([]v1.Pod, error) {
 	m.ctrl.T.Helper()


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>internal/pod/imagepuller.go (3)</summary><blockquote>

`61-64`: **Typo in local variable name reduces readability**

`pullPodTypeLabeValue` is missing the second “l” in “Label”. Even though the variable is local, a consistent naming style helps future readers.

```diff
-	pullPodTypeLabeValue := pullPodUntilSuccess
+	pullPodTypeLabelValue := pullPodUntilSuccess
```

---

`150-152`: **Minor spell-checking & comment clarity**

The inline comment has two small issues:

1. “wating” → “waiting”.
2. The comment could be clearer: “no waiting state set – pull still in progress”.

Not blocking, but worth tidying when you touch this area next.

---

`122-128`: **Function relies on the first container only**

`GetPullPodForImage` (and `GetPullPodImage`) assume exactly one container per pull pod. If additional init-containers or sidecars are introduced later, this logic will silently break.

Options:
1. Assert `len(pod.Spec.Containers)==1` and log/return unexpected status otherwise.
2. Iterate all containers and match on any image.

Not urgent today, but something to keep in mind as the feature evolves.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9b2f82df697d52c3548c4d4571a89cab25d124ee and 9ad02293956dea4d0f739400b500d60e64479159.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `internal/pod/imagepuller.go` (7 hunks)
* `internal/pod/imagepuller_test.go` (6 hunks)
* `internal/pod/mock_imagepuller.go` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>internal/pod/mock_imagepuller.go (2)</summary><blockquote>

<details>
<summary>internal/pod/imagepuller.go (1)</summary>

* `PullPodStatus` (15-15)

</details>
<details>
<summary>vendor/go.uber.org/mock/gomock/call.go (1)</summary>

* `Call` (25-45)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Analyze (go)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>internal/pod/mock_imagepuller.go (1)</summary>

`1-112`: **Generated file – no manual changes required**

`mock_imagepuller.go` is produced by `mockgen`; manual tweaks will be overwritten the next time mocks are regenerated.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-30T07:20:52Z

Review comments:
diff_hunk: @@ -107,13 +122,46 @@ func (ipi *imagePullerImpl) ListPullPods(ctx context.Context, micObj *kmmv1beta1
 func (ipi *imagePullerImpl) GetPullPodForImage(pods []v1.Pod, image string) *v1.Pod {
 
 	for i, pod := range pods {
-		if image == pod.Labels[imageLabelKey] {
+		if image == pod.Spec.Containers[0].Image {
 			return &pods[i]
 		}
 	}
 	return nil
 }
 
 func (ipi *imagePullerImpl) GetPullPodImage(pod v1.Pod) string {
-	return pod.Labels[imageLabelKey]
+	return pod.Spec.Containers[0].Image
+}
+
+func (ipi *imagePullerImpl) GetPullPodStatus(pod *v1.Pod) PullPodStatus {
+	switch pod.Status.Phase {
+	case v1.PodSucceeded:
+		return PullImageSuccess
+	case v1.PodFailed, v1.PodUnknown:
+		return PullImageUnexpectedErr
+	case v1.PodRunning:
+		return PullImageInProcess
+	case v1.PodPending:
+		// no container statuses yet, the pod is just starting to pull images
+		if pod.Status.ContainerStatuses == nil {
+			return PullImageInProcess
+		}
+
+		// no wating status, the pull process is still in progress
+		if pod.Status.ContainerStatuses[0].State.Waiting == nil {
+			return PullImageInProcess
+		}
path: internal/pod/imagepuller.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Possible `index out of range` panic when `ContainerStatuses` is an empty slice**

`ContainerStatuses` can be non-`nil` yet zero-length. Accessing `pod.Status.ContainerStatuses[0]` without a length check risks a runtime panic.

```diff
-	if pod.Status.ContainerStatuses == nil {
+	if pod.Status.ContainerStatuses == nil || len(pod.Status.ContainerStatuses) == 0 {
 		return PullImageInProcess
 	}

 	// no wating status, the pull process is still in progress
-	if pod.Status.ContainerStatuses[0].State.Waiting == nil {
+	if pod.Status.ContainerStatuses[0].State.Waiting == nil {
 		return PullImageInProcess
 	}
```

Consider adding unit coverage for the “empty slice” case (slice length 0).  




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        // no container statuses yet, the pod is just starting to pull images
        if pod.Status.ContainerStatuses == nil || len(pod.Status.ContainerStatuses) == 0 {
            return PullImageInProcess
        }

        // no wating status, the pull process is still in progress
        if pod.Status.ContainerStatuses[0].State.Waiting == nil {
            return PullImageInProcess
        }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-30T07:20:51Z
author_association: NONE
diff_hunk: @@ -202,3 +210,82 @@ var _ = Describe("CreatePullPod", func() {
 		Expect(err).To(BeNil())
 	})
 })
+
+var _ = Describe("GetPullPodStatus", func() {
+	var (
+		ctrl *gomock.Controller
+		clnt *client.MockClient
+		ip   ImagePuller
+	)
+
+	BeforeEach(func() {
+		ctrl = gomock.NewController(GinkgoT())
+		clnt = client.NewMockClient(ctrl)
+		ip = NewImagePuller(clnt, scheme)
+	})
+
+	testPod := v1.Pod{
+		Status: v1.PodStatus{
+			Phase: v1.PodSucceeded,
+		},
+	}
+
+	It("check different phases except for PodPending", func() {
+		By("phase Succeeded")
+
+		res := ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageSuccess))
+
+		By("phase PodFailed")
+		testPod.Status.Phase = v1.PodFailed
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageUnexpectedErr))
+
+		By("phase PodUnknown")
+		testPod.Status.Phase = v1.PodUnknown
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageUnexpectedErr))
+
+		By("phase PodRunning")
+		testPod.Status.Phase = v1.PodRunning
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageInProcess))
+	})
+
+	It("check container statuses for PodPending", func() {
+		testPod.Status.Phase = v1.PodPending
+		By("container statuses missing")
+		testPod.Status.ContainerStatuses = nil
+		res := ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageInProcess))
+
+		By("container statuses waiting is nil")
+		testPod.Status.ContainerStatuses = []v1.ContainerStatus{v1.ContainerStatus{}}
+		res = ip.GetPullPodStatus(&testPod)
+		Expect(res).To(Equal(PullImageInProcess))
+
path: internal/pod/imagepuller_test.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add test for `ContainerStatuses` length 0**

`GetPullPodStatus` now branches on `len(ContainerStatuses)==0`. A dedicated test would prevent regressions:

```go
By("container statuses empty slice")
testPod.Status.ContainerStatuses = []v1.ContainerStatus{}
res = ip.GetPullPodStatus(&testPod)
Expect(res).To(Equal(PullImageInProcess))
```
This complements the nil slice test and confirms the fix for the potential panic.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-30T07:20:51Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-30T07:16:27Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change refactors the pod image pulling logic by introducing a new `PullPodStatus` type to represent various pull pod states and adds new constants for pod label keys and types. The `ImagePuller` interface is extended with a `GetPullPodStatus` method to determine the status of a pull pod based on its phase and container status. The code now uses pod spec and status fields, along with new pod type labels, to track and report pull pod states more precisely, replacing the previous reliance on image labels. Corresponding tests and mocks are updated and expanded.

## Changes

| File(s)                                             | Change Summary |
|-----------------------------------------------------|---------------|
| internal/pod/imagepuller.go                         | Introduced `PullPodStatus` type and constants for pull pod states and label keys; extended `ImagePuller` interface with `GetPullPodStatus`; refactored pod creation, filtering, and status logic to use new labels and pod spec fields. |
| internal/pod/imagepuller_test.go                    | Updated tests to use new label keys and image identification logic; changed pod creation expectations; added comprehensive tests for `GetPullPodStatus` covering various pod phases and container status scenarios. |
| internal/pod/mock_imagepuller.go                    | Added mocked `GetPullPodStatus` method and recorder to `MockImagePuller` for use in tests. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant ImagePuller
    participant Pod

    Client->>ImagePuller: CreatePullPod(imageSpec)
    ImagePuller->>Pod: Create pod with pullPodTypeLabelKey
    Pod-->>ImagePuller: Pod object

    Client->>ImagePuller: GetPullPodStatus(pod)
    ImagePuller->>Pod: Inspect pod phase and container status
    ImagePuller-->>Client: Return PullPodStatus (Success/Failure/InProcess/UnexpectedErr)
```

## Possibly related PRs

- rh-ecosystem-edge/kernel-module-management#1384: Refactors the MIC reconciler to use the `pod.ImagePuller` interface, related by interface usage but not by the new status logic.
- rh-ecosystem-edge/kernel-module-management#1379: Reverts the entire `pod` package, which would undo the changes introduced here, including the new status tracking and label usage.

## Suggested labels

`approved`, `lgtm`

## Suggested reviewers

- mresvanis

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=rh-ecosystem-edge/kernel-module-management&utm_content=1460):

> ‼️ **IMPORTANT**
> Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: netlify[bot]
created_at: 2025-04-30T07:16:36Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *openshift-kmm* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 9ad02293956dea4d0f739400b500d60e64479159 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/openshift-kmm/deploys/6811ce473542470008e94116 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-1460--openshift-kmm.netlify.app](https://deploy-preview-1460--openshift-kmm.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTE0NjAtLW9wZW5zaGlmdC1rbW0ubmV0bGlmeS5hcHAifQ.NJods1LvNtXq70_owCo2mfVd0IdtS78eNjH9hscmJmI)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [openshift-kmm Preview](https://deploy-preview-1460--openshift-kmm.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/openshift-kmm/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-04-30T07:16:38Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/rh-ecosystem-edge/kernel-module-management/pull/1460#" title="Author self-approved">yevgeny-shnaidman</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=rh-ecosystem-edge%2Fkernel-module-management).

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/rh-ecosystem-edge/kernel-module-management/blob/main/OWNERS)~~ [yevgeny-shnaidman]

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
user: login: yevgeny-shnaidman
created_at: 2025-04-30T07:16:42Z
body: /assign @ybettan 
author_association: MEMBER
user: login: yevgeny-shnaidman
created_at: 2025-04-30T07:17:00Z
body: Fixes [1456](https://github.com/rh-ecosystem-edge/kernel-module-management/issues/1456)
author_association: MEMBER
user: login: ybettan
created_at: 2025-04-30T07:23:18Z
body: /lgtm
author_association: MEMBER
user: login: yevgeny-shnaidman
created_at: 2025-04-30T07:59:41Z
body: /override ci/prow/operator-upgrade
author_association: MEMBER
user: login: openshift-ci[bot]
created_at: 2025-04-30T07:59:46Z
body: @yevgeny-shnaidman: Overrode contexts on behalf of yevgeny-shnaidman: ci/prow/operator-upgrade

<details>

In response to [this](https://github.com/rh-ecosystem-edge/kernel-module-management/pull/1460#issuecomment-2841139965):

>/override ci/prow/operator-upgrade


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: NONE
user: login: yevgeny-shnaidman
created_at: 2025-04-30T08:07:08Z
body: /test e2e-hub
author_association: MEMBER