From e8eab3705832df5747b79e322f48ee11847af26c Mon Sep 17 00:00:00 2001 From: dhruv7539 Date: Sun, 22 Feb 2026 14:47:29 -0800 Subject: [PATCH] client-go: make fake Pods.GetLogs honor reactors Kubernetes-commit: 3d41b434c0e88e9f74e38fe654d7b5bfd77090d0 --- .../typed/core/v1/fake/fake_pod_expansion.go | 46 +++++++++++---- .../core/v1/fake/fake_pod_expansion_test.go | 59 +++++++++++++++++++ 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/kubernetes/typed/core/v1/fake/fake_pod_expansion.go b/kubernetes/typed/core/v1/fake/fake_pod_expansion.go index 3fbb89ad4..7e3560b94 100644 --- a/kubernetes/typed/core/v1/fake/fake_pod_expansion.go +++ b/kubernetes/typed/core/v1/fake/fake_pod_expansion.go @@ -17,16 +17,17 @@ limitations under the License. package fake import ( + "bytes" "context" "fmt" "io" "net/http" - "strings" v1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" restclient "k8s.io/client-go/rest" fakerest "k8s.io/client-go/rest/fake" @@ -55,20 +56,45 @@ func (c *fakePods) GetBinding(name string) (result *v1.Binding, err error) { return obj.(*v1.Binding), err } -func (c *fakePods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request { - action := core.GenericActionImpl{} - action.Verb = "get" - action.Namespace = c.Namespace() - action.Resource = c.Resource() - action.Subresource = "log" - action.Value = opts +// getPodLogsActionImpl carries the standard get action shape (including pod name) +// along with pod log options so reactors can inspect both. +type getPodLogsActionImpl struct { + core.GetActionImpl + Value interface{} +} + +func (a getPodLogsActionImpl) GetValue() interface{} { + return a.Value +} + +func (a getPodLogsActionImpl) DeepCopy() core.Action { + return getPodLogsActionImpl{ + GetActionImpl: a.GetActionImpl.DeepCopy().(core.GetActionImpl), + // Keep existing fake GenericAction semantics for value copying. + Value: a.Value, + } +} + +func (c *fakePods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request { + action := getPodLogsActionImpl{ + GetActionImpl: core.NewGetSubresourceAction(c.Resource(), c.Namespace(), "log", name), + Value: opts, + } + + obj, err := c.Fake.Invokes(action, &runtime.Unknown{Raw: []byte("fake logs")}) + logs := []byte("fake logs") + if unknown, ok := obj.(*runtime.Unknown); ok && unknown != nil { + logs = unknown.Raw + } - _, _ = c.Fake.Invokes(action, &v1.Pod{}) fakeClient := &fakerest.RESTClient{ Client: fakerest.CreateHTTPClient(func(request *http.Request) (*http.Response, error) { + if err != nil { + return nil, err + } resp := &http.Response{ StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader("fake logs")), + Body: io.NopCloser(bytes.NewReader(logs)), } return resp, nil }), diff --git a/kubernetes/typed/core/v1/fake/fake_pod_expansion_test.go b/kubernetes/typed/core/v1/fake/fake_pod_expansion_test.go index 03bf42a9c..ec3f89b47 100644 --- a/kubernetes/typed/core/v1/fake/fake_pod_expansion_test.go +++ b/kubernetes/typed/core/v1/fake/fake_pod_expansion_test.go @@ -19,10 +19,12 @@ package fake import ( "bytes" "context" + "errors" "io" "testing" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" cgtesting "k8s.io/client-go/testing" ) @@ -46,3 +48,60 @@ func TestFakePodsGetLogs(t *testing.T) { t.Fatal("Close response body:", err) } } + +func TestFakePodsGetLogsReactorError(t *testing.T) { + fake := &cgtesting.Fake{} + fp := newFakePods(&FakeCoreV1{Fake: fake}, "default") + expectedErr := errors.New("reactor get logs failure") + fake.PrependReactor("get", "pods/log", func(action cgtesting.Action) (bool, runtime.Object, error) { + getAction, ok := action.(cgtesting.GetAction) + if !ok { + t.Fatalf("expected GetAction, got %T", action) + } + if getAction.GetName() != "foo" { + t.Fatalf("expected pod name foo, got %q", getAction.GetName()) + } + genericAction, ok := action.(cgtesting.GenericAction) + if !ok { + t.Fatalf("expected GenericAction, got %T", action) + } + opts, ok := genericAction.GetValue().(*corev1.PodLogOptions) + if !ok { + t.Fatalf("expected *corev1.PodLogOptions, got %T", genericAction.GetValue()) + } + if opts.Container != "ctr" { + t.Fatalf("expected container ctr, got %q", opts.Container) + } + return true, nil, expectedErr + }) + + req := fp.GetLogs("foo", &corev1.PodLogOptions{Container: "ctr"}) + _, err := req.Stream(context.Background()) + if !errors.Is(err, expectedErr) { + t.Fatalf("expected stream error %v, got %v", expectedErr, err) + } +} + +func TestFakePodsGetLogsReactorResponse(t *testing.T) { + fake := &cgtesting.Fake{} + fp := newFakePods(&FakeCoreV1{Fake: fake}, "default") + expectedLogs := "reactor logs" + fake.PrependReactor("get", "pods/log", func(action cgtesting.Action) (bool, runtime.Object, error) { + return true, &runtime.Unknown{Raw: []byte(expectedLogs)}, nil + }) + + req := fp.GetLogs("foo", &corev1.PodLogOptions{}) + body, err := req.Stream(context.Background()) + if err != nil { + t.Fatalf("Stream pod logs: %v", err) + } + defer body.Close() + + logs, err := io.ReadAll(body) + if err != nil { + t.Fatalf("Read pod logs: %v", err) + } + if string(logs) != expectedLogs { + t.Fatalf("expected logs %q, got %q", expectedLogs, string(logs)) + } +}