diff --git a/examples/scratch-env/go.mod b/examples/scratch-env/go.mod index 42c4edb9ba..5948da9989 100644 --- a/examples/scratch-env/go.mod +++ b/examples/scratch-env/go.mod @@ -47,12 +47,12 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.55.1-0.20260602153038-42abb857022c // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/term v0.43.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.15.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect @@ -72,3 +72,15 @@ require ( ) replace sigs.k8s.io/controller-runtime => ../.. + +replace ( + k8s.io/api => github.com/kubernetes/kubernetes/staging/src/k8s.io/api v0.0.0-20260721090818-fd0d12849f1d + k8s.io/apiextensions-apiserver => github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721090818-fd0d12849f1d + k8s.io/apimachinery => github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721090818-fd0d12849f1d + k8s.io/apiserver => github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20260721090818-fd0d12849f1d + k8s.io/client-go => github.com/kubernetes/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721090818-fd0d12849f1d + k8s.io/code-generator => github.com/kubernetes/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20260721090818-fd0d12849f1d + k8s.io/component-base => github.com/kubernetes/kubernetes/staging/src/k8s.io/component-base v0.0.0-20260721090818-fd0d12849f1d + k8s.io/kms => github.com/kubernetes/kubernetes/staging/src/k8s.io/kms v0.0.0-20260721090818-fd0d12849f1d + k8s.io/streaming => github.com/kubernetes/kubernetes/staging/src/k8s.io/streaming v0.0.0-20260721090818-fd0d12849f1d +) diff --git a/examples/scratch-env/go.sum b/examples/scratch-env/go.sum index eb43e9a75a..dcc6833d8b 100644 --- a/examples/scratch-env/go.sum +++ b/examples/scratch-env/go.sum @@ -77,6 +77,14 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubernetes/kubernetes/staging/src/k8s.io/api v0.0.0-20260721090818-fd0d12849f1d h1:PuicG2Qh2L8evmoHsXzbkBYHNuaD1r6VpnIbF2I3ng8= +github.com/kubernetes/kubernetes/staging/src/k8s.io/api v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:cmly/3k2PqLXcOTQwqmVnZD6h6JDCXiLbcJpKBMBugs= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721090818-fd0d12849f1d h1:ep5ylC/IjEHhujDGP5NHOwn5dUKM1X+WfZgZ/vstzCM= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:pADykcqbLWJnajJNh5FEXGTglseeXwXDy2Zj/i1rezE= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721090818-fd0d12849f1d h1:/0Iomty68aXyGNNbv5bTsY8Sg82Ax1Rujwmo4HKpIBk= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:g1CPtKFkdq02v7P6/JujPV0xFgx2NagrpfM5uBCtIMw= +github.com/kubernetes/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721090818-fd0d12849f1d h1:FNRiVs8MaQnJxuusNrA9dXDJiPfskP+QlXbZBGlKroI= +github.com/kubernetes/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:k338h8/YifXSfjKlaKXVTyO3e5dfuXb1VP9z+uJPogs= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -126,24 +134,24 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.55.1-0.20260602153038-42abb857022c h1:hzdRVvjwthcq46T3ybsDtJpg1iZZaW3zik9P3tsYJeo= -golang.org/x/net v0.55.1-0.20260602153038-42abb857022c/go.mod h1:Mo4aFsyVPXieTwCJ1g83a3xU8RY7PkHFQqQ7pkWnuM0= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= @@ -157,14 +165,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.37.0-alpha.3 h1:8RLadjrxX6Nqz8yF13ADHYolBZ4ynBeE3NfzWyiGnyQ= -k8s.io/api v0.37.0-alpha.3/go.mod h1:Pg8TFuiCNviZSgHzFT2II6ux8lbFQo02ObX9xOIuYrM= -k8s.io/apiextensions-apiserver v0.37.0-alpha.3 h1:BZM5iUxbTszd5oj+xt4LH3/sZVKORWJE1Ws3jm03dLk= -k8s.io/apiextensions-apiserver v0.37.0-alpha.3/go.mod h1:/vL2D5lHe/yN43jd8bQ7nypbN8QsVGI6A28GLB0ZnOA= -k8s.io/apimachinery v0.37.0-alpha.3 h1:zxta8QqejeD3A3rEYUC63wSp/kh7KNr35TsX1p3SYTE= -k8s.io/apimachinery v0.37.0-alpha.3/go.mod h1:Uh/MjLZyS9W1TeoRsrsZMf91DZn8Kwox2FEyjwJoy5Q= -k8s.io/client-go v0.37.0-alpha.3 h1:4p6rVMrCMjs8QJSS/z4UhWOps46iB24F+6klnPHfRHI= -k8s.io/client-go v0.37.0-alpha.3/go.mod h1:fr3/4H2dyns23ZKNGP0SgtE2p9gvI6N5aOqMAXBOlPQ= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974 h1:JVogoTvOj6gutlx8bUwGh0e8o8L4X8nDbTLyONmoVvk= diff --git a/go.mod b/go.mod index 12a4ee2f19..f0fdb64a73 100644 --- a/go.mod +++ b/go.mod @@ -15,9 +15,9 @@ require ( github.com/prometheus/client_model v0.6.2 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.1 - golang.org/x/mod v0.35.0 - golang.org/x/sync v0.20.0 - golang.org/x/sys v0.45.0 + golang.org/x/mod v0.37.0 + golang.org/x/sync v0.22.0 + golang.org/x/sys v0.47.0 gomodules.xyz/jsonpatch/v2 v2.4.0 gopkg.in/evanphx/json-patch.v4 v4.13.0 // Using v4 to match upstream k8s.io/api v0.37.0-alpha.3 @@ -89,15 +89,15 @@ require ( go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/net v0.55.1-0.20260602153038-42abb857022c // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/term v0.43.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/term v0.45.0 // indirect + golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.81.1 // indirect + google.golang.org/grpc v1.82.1 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/component-base v0.37.0-alpha.3 // indirect @@ -107,3 +107,15 @@ require ( sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect ) + +replace ( + k8s.io/api => github.com/kubernetes/kubernetes/staging/src/k8s.io/api v0.0.0-20260721090818-fd0d12849f1d + k8s.io/apiextensions-apiserver => github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721090818-fd0d12849f1d + k8s.io/apimachinery => github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721090818-fd0d12849f1d + k8s.io/apiserver => github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20260721090818-fd0d12849f1d + k8s.io/client-go => github.com/kubernetes/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721090818-fd0d12849f1d + k8s.io/code-generator => github.com/kubernetes/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20260721090818-fd0d12849f1d + k8s.io/component-base => github.com/kubernetes/kubernetes/staging/src/k8s.io/component-base v0.0.0-20260721090818-fd0d12849f1d + k8s.io/kms => github.com/kubernetes/kubernetes/staging/src/k8s.io/kms v0.0.0-20260721090818-fd0d12849f1d + k8s.io/streaming => github.com/kubernetes/kubernetes/staging/src/k8s.io/streaming v0.0.0-20260721090818-fd0d12849f1d +) diff --git a/go.sum b/go.sum index 20b37e9ce1..096b63b1e3 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,20 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubernetes/kubernetes/staging/src/k8s.io/api v0.0.0-20260721090818-fd0d12849f1d h1:PuicG2Qh2L8evmoHsXzbkBYHNuaD1r6VpnIbF2I3ng8= +github.com/kubernetes/kubernetes/staging/src/k8s.io/api v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:cmly/3k2PqLXcOTQwqmVnZD6h6JDCXiLbcJpKBMBugs= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721090818-fd0d12849f1d h1:ep5ylC/IjEHhujDGP5NHOwn5dUKM1X+WfZgZ/vstzCM= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:pADykcqbLWJnajJNh5FEXGTglseeXwXDy2Zj/i1rezE= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721090818-fd0d12849f1d h1:/0Iomty68aXyGNNbv5bTsY8Sg82Ax1Rujwmo4HKpIBk= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:g1CPtKFkdq02v7P6/JujPV0xFgx2NagrpfM5uBCtIMw= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20260721090818-fd0d12849f1d h1:+S/rluFH0zC3DINDBlsFGHPEGLWnvQgm3Q/9lnLZAlY= +github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:kPfkXPCPD6ac8OqzZpa/m1pKf5O3a/cai7nHJPDhcXA= +github.com/kubernetes/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721090818-fd0d12849f1d h1:FNRiVs8MaQnJxuusNrA9dXDJiPfskP+QlXbZBGlKroI= +github.com/kubernetes/kubernetes/staging/src/k8s.io/client-go v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:k338h8/YifXSfjKlaKXVTyO3e5dfuXb1VP9z+uJPogs= +github.com/kubernetes/kubernetes/staging/src/k8s.io/component-base v0.0.0-20260721090818-fd0d12849f1d h1:9zOZmn23l2rE2gYnwKQ4nXxz1x2jJq6okSDhcs7ZjfY= +github.com/kubernetes/kubernetes/staging/src/k8s.io/component-base v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:XPaCvVj1ZpMk1pxxwQ6YPSz7huzXm0Htf2Uwym6/SBk= +github.com/kubernetes/kubernetes/staging/src/k8s.io/streaming v0.0.0-20260721090818-fd0d12849f1d h1:YuRoMrP+ZMbpoZVQY/PpTJec2UvWfkfppIYze6Wb3wQ= +github.com/kubernetes/kubernetes/staging/src/k8s.io/streaming v0.0.0-20260721090818-fd0d12849f1d/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= @@ -196,24 +210,24 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.55.1-0.20260602153038-42abb857022c h1:hzdRVvjwthcq46T3ybsDtJpg1iZZaW3zik9P3tsYJeo= -golang.org/x/net v0.55.1-0.20260602153038-42abb857022c/go.mod h1:Mo4aFsyVPXieTwCJ1g83a3xU8RY7PkHFQqQ7pkWnuM0= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= @@ -222,8 +236,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1: google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -235,24 +249,10 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.37.0-alpha.3 h1:8RLadjrxX6Nqz8yF13ADHYolBZ4ynBeE3NfzWyiGnyQ= -k8s.io/api v0.37.0-alpha.3/go.mod h1:Pg8TFuiCNviZSgHzFT2II6ux8lbFQo02ObX9xOIuYrM= -k8s.io/apiextensions-apiserver v0.37.0-alpha.3 h1:BZM5iUxbTszd5oj+xt4LH3/sZVKORWJE1Ws3jm03dLk= -k8s.io/apiextensions-apiserver v0.37.0-alpha.3/go.mod h1:/vL2D5lHe/yN43jd8bQ7nypbN8QsVGI6A28GLB0ZnOA= -k8s.io/apimachinery v0.37.0-alpha.3 h1:zxta8QqejeD3A3rEYUC63wSp/kh7KNr35TsX1p3SYTE= -k8s.io/apimachinery v0.37.0-alpha.3/go.mod h1:Uh/MjLZyS9W1TeoRsrsZMf91DZn8Kwox2FEyjwJoy5Q= -k8s.io/apiserver v0.37.0-alpha.3 h1:qnxt+4JHVIVo390KVXsGyggYKbya05FHDZ3U3MLD8RA= -k8s.io/apiserver v0.37.0-alpha.3/go.mod h1:cAdtKXbCtWktatU8ugGNet8WPhlsEgc7xbDxULOUQY8= -k8s.io/client-go v0.37.0-alpha.3 h1:4p6rVMrCMjs8QJSS/z4UhWOps46iB24F+6klnPHfRHI= -k8s.io/client-go v0.37.0-alpha.3/go.mod h1:fr3/4H2dyns23ZKNGP0SgtE2p9gvI6N5aOqMAXBOlPQ= -k8s.io/component-base v0.37.0-alpha.3 h1:5g6BZuGPfxnu0Zn+13jsW+akc0wH/1pneChppl4nyu8= -k8s.io/component-base v0.37.0-alpha.3/go.mod h1:Ocb0GmFTg75YY2YQI1lX9VHixDUGL6iRy+5IWwCJR6s= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974 h1:JVogoTvOj6gutlx8bUwGh0e8o8L4X8nDbTLyONmoVvk= k8s.io/kube-openapi v0.0.0-20260618221249-bc653b64f974/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= -k8s.io/streaming v0.37.0-alpha.3 h1:6XwRAwOC3DEqgHQTsqHfqLtdJpYKHJ+HShPfEzs5cVM= -k8s.io/streaming v0.37.0-alpha.3/go.mod h1:jksMJuFO/NrJlKuDkIomVgAJnK+Yu9h8KTrkZ8ESiac= k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 h1:/YpDJ4vReG7ZmzSpBGxduXgywWkJU9zHubgJG03MT+Y= diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 2a6a6c6be0..40a2387381 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -96,6 +96,21 @@ type Informers interface { client.FieldIndexer } +// TypedObject is a client.Object that can be used with the type-safe informer API. +type TypedObject interface { + client.Object + comparable +} + +// GetTypedInformer fetches or constructs a type-safe informer for the given object. +func GetTypedInformer[object TypedObject](ctx context.Context, informers Informers, obj object, opts ...InformerGetOption) (TypedInformer[object], error) { + informer, err := informers.GetInformer(ctx, obj, opts...) + if err != nil { + return nil, err + } + return NewTypedInformer[object](informer), nil +} + // Informer allows you to interact with the underlying informer. type Informer interface { // AddEventHandler adds an event handler to the shared informer using the shared informer's resync diff --git a/pkg/cache/typed_informer.go b/pkg/cache/typed_informer.go new file mode 100644 index 0000000000..31e2bb70d0 --- /dev/null +++ b/pkg/cache/typed_informer.go @@ -0,0 +1,88 @@ +/* +Copyright 2026 The Kubernetes Authors. + +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 cache + +import ( + "fmt" + + toolscache "k8s.io/client-go/tools/cache" +) + +// TypedInformer adds type-safe variants for the non-type-safe methods in Informer. +type TypedInformer[object toolscache.Object] interface { + Informer + + // AddTypedEventHandler adds a type-safe event handler to the informer. + AddTypedEventHandler(handler toolscache.TypedResourceEventHandler[object], options ...toolscache.HandlerOptions) (toolscache.ResourceEventHandlerRegistration, error) + + // AddTypedIndexers adds type-safe indexers to the informer. + AddTypedIndexers(indexers toolscache.TypedIndexers[object]) error +} + +// NewTypedInformer wraps an Informer with type-safe event handler and indexer methods. +func NewTypedInformer[object toolscache.Object](informer Informer) TypedInformer[object] { + if typedInformer, ok := informer.(TypedInformer[object]); ok { + return typedInformer + } + return &typedInformer[object]{Informer: informer} +} + +type typedInformer[object toolscache.Object] struct { + Informer +} + +func (i *typedInformer[object]) AddTypedEventHandler(handler toolscache.TypedResourceEventHandler[object], options ...toolscache.HandlerOptions) (toolscache.ResourceEventHandlerRegistration, error) { + var o toolscache.HandlerOptions + switch len(options) { + case 0: + case 1: + o = options[0] + default: + return nil, fmt.Errorf("at most one HandlerOptions may be passed, got %d", len(options)) + } + return i.AddEventHandlerWithOptions(&typedResourceEventHandler[object]{handler: handler}, o) +} + +func (i *typedInformer[object]) AddTypedIndexers(indexers toolscache.TypedIndexers[object]) error { + return i.AddIndexers(toolscache.TypedIndexersToIndexers(indexers)) +} + +// typedResourceEventHandler adapts a TypedResourceEventHandler to a ResourceEventHandler. +type typedResourceEventHandler[object toolscache.Object] struct { + handler toolscache.TypedResourceEventHandler[object] +} + +func (h *typedResourceEventHandler[object]) OnAdd(obj any, isInInitialList bool) { + h.handler.OnAdd(obj.(object), isInInitialList) +} + +func (h *typedResourceEventHandler[object]) OnUpdate(oldObj, newObj any) { + h.handler.OnUpdate(oldObj.(object), newObj.(object)) +} + +func (h *typedResourceEventHandler[object]) OnDelete(obj any) { + if tombstone, ok := obj.(toolscache.DeletedFinalStateUnknown); ok { + var optionalObj object + if tombstone.Obj != nil { + optionalObj = tombstone.Obj.(object) + } + h.handler.OnDelete(toolscache.DeletedObject[object]{OptionalObj: optionalObj, FinalStateUnknown: &tombstone}) + return + } + + h.handler.OnDelete(toolscache.DeletedObject[object]{OptionalObj: obj.(object)}) +} diff --git a/pkg/cache/typed_informer_test.go b/pkg/cache/typed_informer_test.go new file mode 100644 index 0000000000..6613f64e3f --- /dev/null +++ b/pkg/cache/typed_informer_test.go @@ -0,0 +1,132 @@ +/* +Copyright 2026 The Kubernetes Authors. + +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 cache + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + toolscache "k8s.io/client-go/tools/cache" +) + +var _ = Describe("TypedInformer", func() { + It("adapts typed event handlers", func() { + informer := &recordingInformer{} + typedInformer := NewTypedInformer[*corev1.Pod](informer) + + var added *corev1.Pod + var initialList bool + var oldPod, newPod *corev1.Pod + var deleted toolscache.DeletedObject[*corev1.Pod] + _, err := typedInformer.AddTypedEventHandler(toolscache.TypedResourceEventHandlerDetailedFuncs[*corev1.Pod]{ + AddFunc: func(obj *corev1.Pod, isInInitialList bool) { + added = obj + initialList = isInInitialList + }, + UpdateFunc: func(oldObj, newObj *corev1.Pod) { + oldPod = oldObj + newPod = newObj + }, + DeleteFunc: func(obj toolscache.DeletedObject[*corev1.Pod]) { + deleted = obj + }, + }, toolscache.HandlerOptions{}) + Expect(err).NotTo(HaveOccurred()) + + pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod"}} + updatedPod := pod.DeepCopy() + updatedPod.ResourceVersion = "2" + informer.handler.OnAdd(pod, true) + informer.handler.OnUpdate(pod, updatedPod) + informer.handler.OnDelete(pod) + + Expect(added).To(BeIdenticalTo(pod)) + Expect(initialList).To(BeTrue()) + Expect(oldPod).To(BeIdenticalTo(pod)) + Expect(newPod).To(BeIdenticalTo(updatedPod)) + Expect(deleted.OptionalObj).To(BeIdenticalTo(pod)) + Expect(deleted.FinalStateUnknown).To(BeNil()) + + tombstone := toolscache.DeletedFinalStateUnknown{Key: "default/pod", Obj: pod} + informer.handler.OnDelete(tombstone) + Expect(deleted.OptionalObj).To(BeIdenticalTo(pod)) + Expect(deleted.FinalStateUnknown).NotTo(BeNil()) + Expect(*deleted.FinalStateUnknown).To(Equal(tombstone)) + + tombstone.Obj = nil + informer.handler.OnDelete(tombstone) + Expect(deleted.OptionalObj).To(BeNil()) + Expect(deleted.FinalStateUnknown).NotTo(BeNil()) + Expect(*deleted.FinalStateUnknown).To(Equal(tombstone)) + }) + + It("adapts typed event handler options", func() { + informer := &recordingInformer{} + typedInformer := NewTypedInformer[*corev1.Pod](informer) + + _, err := typedInformer.AddTypedEventHandler(toolscache.TypedResourceEventHandlerFuncs[*corev1.Pod]{}) + Expect(err).NotTo(HaveOccurred()) + Expect(informer.options).To(Equal(toolscache.HandlerOptions{})) + + resyncPeriod := time.Minute + resync := toolscache.HandlerOptions{ResyncPeriod: &resyncPeriod} + _, err = typedInformer.AddTypedEventHandler(toolscache.TypedResourceEventHandlerFuncs[*corev1.Pod]{}, resync) + Expect(err).NotTo(HaveOccurred()) + Expect(informer.options).To(Equal(resync)) + + _, err = typedInformer.AddTypedEventHandler(toolscache.TypedResourceEventHandlerFuncs[*corev1.Pod]{}, toolscache.HandlerOptions{}, toolscache.HandlerOptions{}) + Expect(err).To(MatchError("at most one HandlerOptions may be passed, got 2")) + }) + + It("adapts typed indexers", func() { + informer := &recordingInformer{} + typedInformer := NewTypedInformer[*corev1.Pod](informer) + + err := typedInformer.AddTypedIndexers(toolscache.TypedIndexers[*corev1.Pod]{ + "node": func(pod *corev1.Pod) ([]string, error) { + return []string{pod.Spec.NodeName}, nil + }, + }) + Expect(err).NotTo(HaveOccurred()) + + values, err := informer.indexers["node"](&corev1.Pod{Spec: corev1.PodSpec{NodeName: "node-a"}}) + Expect(err).NotTo(HaveOccurred()) + Expect(values).To(Equal([]string{"node-a"})) + }) +}) + +type recordingInformer struct { + Informer + handler toolscache.ResourceEventHandler + options toolscache.HandlerOptions + indexers toolscache.Indexers +} + +func (i *recordingInformer) AddEventHandlerWithOptions(handler toolscache.ResourceEventHandler, options toolscache.HandlerOptions) (toolscache.ResourceEventHandlerRegistration, error) { + i.handler = handler + i.options = options + return nil, nil +} + +func (i *recordingInformer) AddIndexers(indexers toolscache.Indexers) error { + i.indexers = indexers + return nil +} diff --git a/pkg/internal/recorder/recorder_integration_test.go b/pkg/internal/recorder/recorder_integration_test.go index 1f3950af50..de085af175 100644 --- a/pkg/internal/recorder/recorder_integration_test.go +++ b/pkg/internal/recorder/recorder_integration_test.go @@ -45,7 +45,8 @@ var _ = Describe("recorder", func() { Expect(err).NotTo(HaveOccurred()) By("Creating the Controller") - deprecatedRecorder := cm.GetEventRecorderFor("test-deprecated-recorder") //nolint:staticcheck // testing deprecated API + //lint:ignore SA1019 testing deprecated API + deprecatedRecorder := cm.GetEventRecorderFor("test-deprecated-recorder") recorder := cm.GetEventRecorder("test-recorder") instance, err := controller.New("foo-controller", cm, controller.Options{ Reconciler: reconcile.Func( diff --git a/pkg/internal/source/event_handler.go b/pkg/internal/source/event_handler.go index 57a5cf98a1..ff692deb83 100644 --- a/pkg/internal/source/event_handler.go +++ b/pkg/internal/source/event_handler.go @@ -18,24 +18,20 @@ package internal import ( "context" - "fmt" - "k8s.io/client-go/tools/cache" + toolscache "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" - logf "sigs.k8s.io/controller-runtime/pkg/internal/log" "sigs.k8s.io/controller-runtime/pkg/predicate" ) -var log = logf.RuntimeLog.WithName("source").WithName("EventHandler") - -var _ cache.ResourceEventHandler = &EventHandler[client.Object, any]{} +var _ toolscache.TypedResourceEventHandler[client.Object] = &EventHandler[client.Object, any]{} // NewEventHandler creates a new EventHandler. -func NewEventHandler[object any, request comparable]( +func NewEventHandler[object toolscache.Object, request comparable]( ctx context.Context, queue workqueue.TypedRateLimitingInterface[request], handler handler.TypedEventHandler[object, request], @@ -49,7 +45,7 @@ func NewEventHandler[object any, request comparable]( } // EventHandler adapts a handler.EventHandler interface to a cache.ResourceEventHandler interface. -type EventHandler[object any, request comparable] struct { +type EventHandler[object toolscache.Object, request comparable] struct { // ctx stores the context that created the event handler // that is used to propagate cancellation signals to each handler function. ctx context.Context @@ -60,20 +56,12 @@ type EventHandler[object any, request comparable] struct { } // OnAdd creates CreateEvent and calls Create on EventHandler. -func (e *EventHandler[object, request]) OnAdd(obj any, isInInitialList bool) { +func (e *EventHandler[object, request]) OnAdd(obj object, isInInitialList bool) { c := event.TypedCreateEvent[object]{ + Object: obj, IsInInitialList: isInInitialList, } - // Pull Object out of the object - if o, ok := obj.(object); ok { - c.Object = o - } else { - log.Error(nil, "OnAdd missing Object", - "object", obj, "type", fmt.Sprintf("%T", obj)) - return - } - for _, p := range e.predicates { if !p.Create(c) { return @@ -87,25 +75,8 @@ func (e *EventHandler[object, request]) OnAdd(obj any, isInInitialList bool) { } // OnUpdate creates UpdateEvent and calls Update on EventHandler. -func (e *EventHandler[object, request]) OnUpdate(oldObj, newObj any) { - u := event.TypedUpdateEvent[object]{} - - if o, ok := oldObj.(object); ok { - u.ObjectOld = o - } else { - log.Error(nil, "OnUpdate missing ObjectOld", - "object", oldObj, "type", fmt.Sprintf("%T", oldObj)) - return - } - - // Pull Object out of the object - if o, ok := newObj.(object); ok { - u.ObjectNew = o - } else { - log.Error(nil, "OnUpdate missing ObjectNew", - "object", newObj, "type", fmt.Sprintf("%T", newObj)) - return - } +func (e *EventHandler[object, request]) OnUpdate(oldObj, newObj object) { + u := event.TypedUpdateEvent[object]{ObjectOld: oldObj, ObjectNew: newObj} for _, p := range e.predicates { if !p.Update(u) { @@ -120,39 +91,10 @@ func (e *EventHandler[object, request]) OnUpdate(oldObj, newObj any) { } // OnDelete creates DeleteEvent and calls Delete on EventHandler. -func (e *EventHandler[object, request]) OnDelete(obj any) { - d := event.TypedDeleteEvent[object]{} - - // Deal with tombstone events by pulling the object out. Tombstone events wrap the object in a - // DeleteFinalStateUnknown struct, so the object needs to be pulled out. - // Copied from sample-controller - // This should never happen if we aren't missing events, which we have concluded that we are not - // and made decisions off of this belief. Maybe this shouldn't be here? - var ok bool - if _, ok = obj.(client.Object); !ok { - // If the object doesn't have Metadata, assume it is a tombstone object of type DeletedFinalStateUnknown - tombstone, ok := obj.(cache.DeletedFinalStateUnknown) - if !ok { - log.Error(nil, "Error decoding objects. Expected cache.DeletedFinalStateUnknown", - "type", fmt.Sprintf("%T", obj), - "object", obj) - return - } - - // Set DeleteStateUnknown to true - d.DeleteStateUnknown = true - - // Set obj to the tombstone obj - obj = tombstone.Obj - } - - // Pull Object out of the object - if o, ok := obj.(object); ok { - d.Object = o - } else { - log.Error(nil, "OnDelete missing Object", - "object", obj, "type", fmt.Sprintf("%T", obj)) - return +func (e *EventHandler[object, request]) OnDelete(obj toolscache.DeletedObject[object]) { + d := event.TypedDeleteEvent[object]{ + Object: obj.OptionalObj, + DeleteStateUnknown: obj.FinalStateUnknown != nil, } for _, p := range e.predicates { diff --git a/pkg/internal/source/internal_test.go b/pkg/internal/source/internal_test.go index bebdc63fef..a2f2ed7cd1 100644 --- a/pkg/internal/source/internal_test.go +++ b/pkg/internal/source/internal_test.go @@ -32,8 +32,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/controller/controllertest" "sigs.k8s.io/controller-runtime/pkg/predicate" ) @@ -81,6 +79,7 @@ var _ = Describe("Internal", func() { Describe("EventHandler", func() { var pod, newPod *corev1.Pod + var deletedPod cache.DeletedObject[client.Object] BeforeEach(func() { pod = &corev1.Pod{ @@ -90,6 +89,7 @@ var _ = Describe("Internal", func() { } newPod = pod.DeepCopy() newPod.Labels = map[string]string{"foo": "bar"} + deletedPod = cache.DeletedObject[client.Object]{OptionalObj: pod} }) It("should create a CreateEvent", func(ctx SpecContext) { @@ -140,14 +140,6 @@ var _ = Describe("Internal", func() { Expect(set).To(BeTrue()) }) - It("should not call Create EventHandler if the object is not a runtime.Object", func() { - instance.OnAdd(&metav1.ObjectMeta{}, false) - }) - - It("should not call Create EventHandler if the object does not have metadata", func() { - instance.OnAdd(FooRuntimeObject{}, false) - }) - It("should create an UpdateEvent", func(ctx SpecContext) { funcs.UpdateFunc = func(ctx context.Context, evt event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() @@ -197,22 +189,12 @@ var _ = Describe("Internal", func() { Expect(set).To(BeTrue()) }) - It("should not call Update EventHandler if the object is not a runtime.Object", func() { - instance.OnUpdate(&metav1.ObjectMeta{}, &corev1.Pod{}) - instance.OnUpdate(&corev1.Pod{}, &metav1.ObjectMeta{}) - }) - - It("should not call Update EventHandler if the object does not have metadata", func() { - instance.OnUpdate(FooRuntimeObject{}, &corev1.Pod{}) - instance.OnUpdate(&corev1.Pod{}, FooRuntimeObject{}) - }) - It("should create a DeleteEvent", func() { funcs.DeleteFunc = func(ctx context.Context, evt event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() Expect(evt.Object).To(Equal(pod)) } - instance.OnDelete(pod) + instance.OnDelete(deletedPod) }) It("should used Predicates to filter DeleteEvents", func(ctx SpecContext) { @@ -220,14 +202,14 @@ var _ = Describe("Internal", func() { instance = internal.NewEventHandler(ctx, &controllertest.Queue{}, setfuncs, []predicate.Predicate{ predicate.Funcs{DeleteFunc: func(event.DeleteEvent) bool { return false }}, }) - instance.OnDelete(pod) + instance.OnDelete(deletedPod) Expect(set).To(BeFalse()) set = false instance = internal.NewEventHandler(ctx, &controllertest.Queue{}, setfuncs, []predicate.Predicate{ predicate.Funcs{DeleteFunc: func(event.DeleteEvent) bool { return true }}, }) - instance.OnDelete(pod) + instance.OnDelete(deletedPod) Expect(set).To(BeTrue()) set = false @@ -235,7 +217,7 @@ var _ = Describe("Internal", func() { predicate.Funcs{DeleteFunc: func(event.DeleteEvent) bool { return true }}, predicate.Funcs{DeleteFunc: func(event.DeleteEvent) bool { return false }}, }) - instance.OnDelete(pod) + instance.OnDelete(deletedPod) Expect(set).To(BeFalse()) set = false @@ -243,7 +225,7 @@ var _ = Describe("Internal", func() { predicate.Funcs{DeleteFunc: func(event.DeleteEvent) bool { return false }}, predicate.Funcs{DeleteFunc: func(event.DeleteEvent) bool { return true }}, }) - instance.OnDelete(pod) + instance.OnDelete(deletedPod) Expect(set).To(BeFalse()) set = false @@ -251,18 +233,10 @@ var _ = Describe("Internal", func() { predicate.Funcs{DeleteFunc: func(event.DeleteEvent) bool { return true }}, predicate.Funcs{DeleteFunc: func(event.DeleteEvent) bool { return true }}, }) - instance.OnDelete(pod) + instance.OnDelete(deletedPod) Expect(set).To(BeTrue()) }) - It("should not call Delete EventHandler if the object is not a runtime.Object", func() { - instance.OnDelete(&metav1.ObjectMeta{}) - }) - - It("should not call Delete EventHandler if the object does not have metadata", func() { - instance.OnDelete(FooRuntimeObject{}) - }) - It("should create a DeleteEvent from a tombstone", func() { tombstone := cache.DeletedFinalStateUnknown{ Obj: pod, @@ -273,17 +247,7 @@ var _ = Describe("Internal", func() { Expect(evt.DeleteStateUnknown).Should(BeTrue()) } - instance.OnDelete(tombstone) - }) - - It("should ignore tombstone objects without meta", func() { - tombstone := cache.DeletedFinalStateUnknown{Obj: Foo{}} - instance.OnDelete(tombstone) - }) - It("should ignore objects without meta", func() { - instance.OnAdd(Foo{}, false) - instance.OnUpdate(Foo{}, Foo{}) - instance.OnDelete(Foo{}) + instance.OnDelete(cache.DeletedObject[client.Object]{OptionalObj: pod, FinalStateUnknown: &tombstone}) }) }) @@ -318,12 +282,3 @@ var _ = Describe("Internal", func() { }) }) }) - -type Foo struct{} - -var _ runtime.Object = FooRuntimeObject{} - -type FooRuntimeObject struct{} - -func (FooRuntimeObject) GetObjectKind() schema.ObjectKind { return nil } -func (FooRuntimeObject) DeepCopyObject() runtime.Object { return nil } diff --git a/pkg/internal/source/kind.go b/pkg/internal/source/kind.go index 286d8094b7..5ddcc2fdfb 100644 --- a/pkg/internal/source/kind.go +++ b/pkg/internal/source/kind.go @@ -41,7 +41,7 @@ import ( var logKind = logf.RuntimeLog.WithName("source").WithName("Kind") // Kind is used to provide a source of events originating inside the cluster from Watches (e.g. Pod Create). -type Kind[object client.Object, request comparable] struct { +type Kind[object cache.TypedObject, request comparable] struct { // Type is the type of object to watch. e.g. &v1.Pod{} Type object @@ -77,7 +77,7 @@ func (ks *Kind[object, request]) Start(ctx context.Context, queue workqueue.Type ks.startedErr = make(chan error, 1) // Buffer chan to not leak goroutines if WaitForSync isn't called go func() { var ( - i cache.Informer + i cache.TypedInformer[object] lastErr error ) @@ -85,7 +85,7 @@ func (ks *Kind[object, request]) Start(ctx context.Context, queue workqueue.Type // an error or the specified context is cancelled or expired. if err := wait.PollUntilContextCancel(ctx, 10*time.Second, true, func(ctx context.Context) (bool, error) { // Lookup the Informer from the Cache and add an EventHandler which populates the Queue - i, lastErr = ks.Cache.GetInformer(ctx, ks.Type) + i, lastErr = cache.GetTypedInformer(ctx, ks.Cache, ks.Type) if lastErr != nil { kindMatchErr := &meta.NoKindMatchError{} switch { @@ -109,7 +109,7 @@ func (ks *Kind[object, request]) Start(ctx context.Context, queue workqueue.Type return } - handlerRegistration, err := i.AddEventHandlerWithOptions(NewEventHandler(ctx, queue, ks.Handler, ks.Predicates), toolscache.HandlerOptions{ + handlerRegistration, err := i.AddTypedEventHandler(NewEventHandler(ctx, queue, ks.Handler, ks.Predicates), toolscache.HandlerOptions{ Logger: &logKind, }) if err != nil { diff --git a/pkg/source/source.go b/pkg/source/source.go index 8e17ad7577..845b2f62a1 100644 --- a/pkg/source/source.go +++ b/pkg/source/source.go @@ -73,7 +73,7 @@ type TypedSyncingSource[request comparable] interface { } // Kind creates a KindSource with the given cache provider. -func Kind[object client.Object]( +func Kind[object cache.TypedObject]( cache cache.Cache, obj object, handler handler.TypedEventHandler[object, reconcile.Request], @@ -83,7 +83,7 @@ func Kind[object client.Object]( } // TypedKind creates a KindSource with the given cache provider. -func TypedKind[object client.Object, request comparable]( +func TypedKind[object cache.TypedObject, request comparable]( cache cache.Cache, obj object, handler handler.TypedEventHandler[object, request], @@ -266,9 +266,9 @@ func (cs *channel[object, request]) syncLoop(ctx context.Context) { // TypedInformer is used to provide a source of events originating inside the cluster from Watches using generic // event handlers and predicates. -type TypedInformer[object any, request comparable] struct { +type TypedInformer[object toolscache.Object, request comparable] struct { // Informer is the controller-runtime Informer - Informer cache.Informer + Informer cache.TypedInformer[object] Handler handler.TypedEventHandler[object, request] Predicates []predicate.TypedPredicate[object] } @@ -289,7 +289,7 @@ func (is *TypedInformer[object, request]) Start(ctx context.Context, queue workq return errors.New("must specify Informer.Handler") } - _, err := is.Informer.AddEventHandlerWithOptions(internal.NewEventHandler(ctx, queue, is.Handler, is.Predicates), toolscache.HandlerOptions{ + _, err := is.Informer.AddTypedEventHandler(internal.NewEventHandler(ctx, queue, is.Handler, is.Predicates), toolscache.HandlerOptions{ Logger: &logInformer, }) if err != nil { diff --git a/pkg/source/source_integration_test.go b/pkg/source/source_integration_test.go index afe39c3bcf..185dc7ba63 100644 --- a/pkg/source/source_integration_test.go +++ b/pkg/source/source_integration_test.go @@ -201,14 +201,14 @@ var _ = Describe("Source", func() { Describe("Informer", func() { var c chan struct{} var rs *appsv1.ReplicaSet - var depInformer toolscache.SharedIndexInformer + var depInformer toolscache.TypedSharedIndexInformer[*appsv1.ReplicaSet] var informerFactory kubeinformers.SharedInformerFactory var stopTest chan struct{} BeforeEach(func() { stopTest = make(chan struct{}) informerFactory = kubeinformers.NewSharedInformerFactory(clientset, time.Second*30) - depInformer = informerFactory.Apps().V1().ReplicaSets().Informer() + depInformer = informerFactory.Apps().V1().ReplicaSets().TypedInformer() informerFactory.Start(stopTest) Eventually(depInformer.HasSynced).Should(BeTrue()) @@ -247,10 +247,10 @@ var _ = Describe("Source", func() { workqueue.TypedRateLimitingQueueConfig[reconcile.Request]{ Name: "test", }) - instance := &source.Informer{ + instance := &source.TypedInformer[*appsv1.ReplicaSet, reconcile.Request]{ Informer: depInformer, - Handler: handler.Funcs{ - CreateFunc: func(ctx context.Context, evt event.CreateEvent, q2 workqueue.TypedRateLimitingInterface[reconcile.Request]) { + Handler: handler.TypedFuncs[*appsv1.ReplicaSet, reconcile.Request]{ + CreateFunc: func(ctx context.Context, evt event.TypedCreateEvent[*appsv1.ReplicaSet], q2 workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() var err error rs, err := clientset.AppsV1().ReplicaSets("default").Get(ctx, rs.Name, metav1.GetOptions{}) @@ -260,15 +260,15 @@ var _ = Describe("Source", func() { Expect(evt.Object).To(Equal(rs)) close(c) }, - UpdateFunc: func(context.Context, event.UpdateEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { + UpdateFunc: func(context.Context, event.TypedUpdateEvent[*appsv1.ReplicaSet], workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() Fail("Unexpected UpdateEvent") }, - DeleteFunc: func(context.Context, event.DeleteEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { + DeleteFunc: func(context.Context, event.TypedDeleteEvent[*appsv1.ReplicaSet], workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() Fail("Unexpected DeleteEvent") }, - GenericFunc: func(context.Context, event.GenericEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { + GenericFunc: func(context.Context, event.TypedGenericEvent[*appsv1.ReplicaSet], workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() Fail("Unexpected GenericEvent") }, @@ -295,12 +295,12 @@ var _ = Describe("Source", func() { workqueue.TypedRateLimitingQueueConfig[reconcile.Request]{ Name: "test", }) - instance := &source.Informer{ + instance := &source.TypedInformer[*appsv1.ReplicaSet, reconcile.Request]{ Informer: depInformer, - Handler: handler.Funcs{ - CreateFunc: func(ctx context.Context, evt event.CreateEvent, q2 workqueue.TypedRateLimitingInterface[reconcile.Request]) { + Handler: handler.TypedFuncs[*appsv1.ReplicaSet, reconcile.Request]{ + CreateFunc: func(ctx context.Context, evt event.TypedCreateEvent[*appsv1.ReplicaSet], q2 workqueue.TypedRateLimitingInterface[reconcile.Request]) { }, - UpdateFunc: func(ctx context.Context, evt event.UpdateEvent, q2 workqueue.TypedRateLimitingInterface[reconcile.Request]) { + UpdateFunc: func(ctx context.Context, evt event.TypedUpdateEvent[*appsv1.ReplicaSet], q2 workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() var err error rs2, err := clientset.AppsV1().ReplicaSets("default").Get(ctx, rs.Name, metav1.GetOptions{}) @@ -313,11 +313,11 @@ var _ = Describe("Source", func() { close(c) }, - DeleteFunc: func(context.Context, event.DeleteEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { + DeleteFunc: func(context.Context, event.TypedDeleteEvent[*appsv1.ReplicaSet], workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() Fail("Unexpected DeleteEvent") }, - GenericFunc: func(context.Context, event.GenericEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { + GenericFunc: func(context.Context, event.TypedGenericEvent[*appsv1.ReplicaSet], workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() Fail("Unexpected GenericEvent") }, @@ -339,20 +339,20 @@ var _ = Describe("Source", func() { workqueue.TypedRateLimitingQueueConfig[reconcile.Request]{ Name: "test", }) - instance := &source.Informer{ + instance := &source.TypedInformer[*appsv1.ReplicaSet, reconcile.Request]{ Informer: depInformer, - Handler: handler.Funcs{ - CreateFunc: func(context.Context, event.CreateEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { + Handler: handler.TypedFuncs[*appsv1.ReplicaSet, reconcile.Request]{ + CreateFunc: func(context.Context, event.TypedCreateEvent[*appsv1.ReplicaSet], workqueue.TypedRateLimitingInterface[reconcile.Request]) { }, - UpdateFunc: func(context.Context, event.UpdateEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { + UpdateFunc: func(context.Context, event.TypedUpdateEvent[*appsv1.ReplicaSet], workqueue.TypedRateLimitingInterface[reconcile.Request]) { }, - DeleteFunc: func(ctx context.Context, evt event.DeleteEvent, q2 workqueue.TypedRateLimitingInterface[reconcile.Request]) { + DeleteFunc: func(ctx context.Context, evt event.TypedDeleteEvent[*appsv1.ReplicaSet], q2 workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() Expect(q2).To(Equal(q)) Expect(evt.Object.GetName()).To(Equal(rs.Name)) close(c) }, - GenericFunc: func(context.Context, event.GenericEvent, workqueue.TypedRateLimitingInterface[reconcile.Request]) { + GenericFunc: func(context.Context, event.TypedGenericEvent[*appsv1.ReplicaSet], workqueue.TypedRateLimitingInterface[reconcile.Request]) { defer GinkgoRecover() Fail("Unexpected GenericEvent") }, diff --git a/pkg/webhook/conversion/conversion_test.go b/pkg/webhook/conversion/conversion_test.go index 046ab44ced..2f5a715770 100644 --- a/pkg/webhook/conversion/conversion_test.go +++ b/pkg/webhook/conversion/conversion_test.go @@ -363,7 +363,7 @@ var _ = Describe("NewHubSpokeConverter", func() { It("should return error if hub is not registered in the scheme", func() { _, err := conversion.NewHubSpokeConverter(&appsv1.Deployment{})(scheme) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("failed to create hub spoke converter: failed to get GroupVersionKind for hub: no kind is registered for the type v1.Deployment in scheme \"pkg/runtime/scheme.go:111\"")) + Expect(err.Error()).To(ContainSubstring("failed to create hub spoke converter: failed to get GroupVersionKind for hub: no kind is registered for the type v1.Deployment")) }) It("should return error if spoke is not registered in the scheme", func() { @@ -373,7 +373,7 @@ var _ = Describe("NewHubSpokeConverter", func() { func(context.Context, *appsv1.Deployment, *jobsv2.ExternalJob) error { return nil }), )(scheme) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("failed to create hub spoke converter for ExternalJob: failed to get GroupVersionKind for spoke converter: no kind is registered for the type v1.Deployment in scheme \"pkg/runtime/scheme.go:111\"")) + Expect(err.Error()).To(ContainSubstring("failed to create hub spoke converter for ExternalJob: failed to get GroupVersionKind for spoke converter: no kind is registered for the type v1.Deployment")) }) It("should return error if spoke does not have the same GroupKind as the hub", func() {