-
Notifications
You must be signed in to change notification settings - Fork 1.2k
✨ Fakeclient: Add apply support #2981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Skipping CI for Draft Pull Request. |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: alvaroaleman The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
The Kubernetes project currently lacks enough contributors to adequately respond to all PRs. This bot triages PRs according to the following rules:
You can:
Please send feedback to sig-contributor-experience at kubernetes/community. /lifecycle stale |
/lifecycle frozen |
@alvaroaleman: The In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
The This bot removes
You can:
Please send feedback to sig-contributor-experience at kubernetes/community. /remove-lifecycle frozen |
ac5705d
to
b8202b9
Compare
b8202b9
to
71e305a
Compare
type multiTypeConverter struct { | ||
upstream []managedfields.TypeConverter | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Providing this composite type converter seems fine.
We have a different use case for a composite type converter in the apiserver for admission: https://github.com/kubernetes/kubernetes/blob/25e11cd1c143ef136418c33bfbbbd4f24e32e529/staging/src/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/typeconverter.go#L37. But the use case is different, it takes a single static type converter and an openapi client, instead of multiple underlying type converters, so it is not useful here.
I'd like to ensure we keep this implementation unexported. Maybe add some godoc to that effect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep makes sense, will do that
@@ -119,6 +123,7 @@ type ClientBuilder struct { | |||
withStatusSubresource []client.Object | |||
objectTracker testing.ObjectTracker | |||
interceptorFuncs *interceptor.Funcs | |||
typeConverters []managedfields.TypeConverter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can this simply be typeConverter managedfields.TypeConverter
? When multiTypeConverter
is needed, it should implement the managedfields.TypeConverter
interface and so can be initialized and used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is on the builder and often times it will be needed to have more than one, as both in-tree and CRs are used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good approach. Left some minor feedback then LGTM.
7d429f9
to
e140397
Compare
e2e2905
to
46e8e54
Compare
d6585af
to
e087e01
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
pkg/client/fake/client.go
Outdated
// * managedfields.NewDeducedTypeConverter(), | ||
// | ||
// Be aware that the behavior of the `NewDeducedTypeConverter` might not match the behavior of the | ||
// Kubernetes APIServer, it is recommended to provide a type converter for your types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is recommended to provide a type converter for your types
Do we have an example somewhere on how to write one? (e.g. via unit tests, I don't see this func called there)
I think a unit test for this would be good and it also works as an example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They get generated alongside the applyconfigs, I have added a comment to that effect. As we don't have custom applyconfigs in this repo, I've added a test that uses the client-go type converter.
@@ -228,6 +237,29 @@ func (f *ClientBuilder) WithInterceptorFuncs(interceptorFuncs interceptor.Funcs) | |||
return f | |||
} | |||
|
|||
// WithTypeConverters sets the type converters for the fake client. The list is ordered and the first | |||
// non-erroring converter is used. A type converter must be provided for all types the client is used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under which exact circumstances must these be provided?
Only if SSA with custom types is used with the fake client? (based on the unit test it looks like Unstructured also works without setting TypeConverters)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is always requiered if the FieldManagedObjectTracker is used, it seems to error out on all of its methods if the typeConverter doesn't handle a given type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm missing something.
So if I'm using the fake client today with my own CRDs, this will now break if I don't start setting type converters for them? This would break a lot of people
(FieldManagedObjectTracker is always used if objectTracker is not explicitly set, right?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I'm using the fake client today with my own CRDs, this will now break if I don't start setting type converters for them? This would break a lot of people
No it will not break. SSA might not work correctly due to the inferencing type converter, but that is all.
pkg/client/fake/client.go
Outdated
f.typeConverters = []managedfields.TypeConverter{ | ||
// Use corresponding scheme to ensure the converter error | ||
// for types it can't handle. | ||
clientgoapplyconfigurations.NewTypeConverter(scheme.Scheme), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal is to use a scheme that only contains clientgo types, right?
I wonder if we should use another scheme. This is a package global variable and people might have added all kinds of types to it (I've seen that in the past.. :))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then we have to deal with keeping that scheme in sync with client-go, right? I.E. update it every time a new type is added there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could it be that we can do ~ the following https://github.com/kubernetes/client-go/blob/master/kubernetes/scheme/register.go#L159-L162 (with a local scheme that we create)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nice, yeah that works, updated
pkg/client/fake/client.go
Outdated
@@ -523,6 +583,10 @@ func (t versionedTracker) updateObject(gvr schema.GroupVersionResource, obj runt | |||
} | |||
|
|||
func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { | |||
if err := c.addToSchemeIfUnknownAndUnstructured(obj); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like also if Partial, we should rename the func somehow
Maybe addUnstructuredAndPartialToSchemeIfUnknown
or
addToSchemeIfUnknownAndUnstructuredOrPartial
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if patch.Type() != types.ApplyPatchType { | ||
return err | ||
} | ||
oldObj = &unstructured.Unstructured{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine to ignore all errors in this case or could/should we only ignore certain errors? (like "not found")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to specifically only ignore NotFound
pkg/client/fake/client.go
Outdated
ns := action.GetNamespace() | ||
gvr := action.GetResource() | ||
|
||
obj, err := tracker.Get(gvr, ns, action.GetName()) | ||
if err != nil { | ||
if action.GetPatchType() == types.ApplyPatchType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine to ignore all errors in this case or could/should we only ignore certain errors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to specifically only ignore NotFound
pkg/client/fake/typeconverter.go
Outdated
return res, nil | ||
} | ||
|
||
return nil, fmt.Errorf("failed to convert Object to Typed: %w", kerrors.NewAggregate(errs)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, fmt.Errorf("failed to convert Object to Typed: %w", kerrors.NewAggregate(errs)) | |
return nil, fmt.Errorf("failed to convert Object to TypedValue: %w", kerrors.NewAggregate(errs)) |
nit: Or drop the Value suffix in the error in l.59
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -2554,6 +2538,51 @@ var _ = Describe("Fake client", func() { | |||
wg.Wait() | |||
}) | |||
|
|||
It("supports server-side apply of a client-go resource", func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have test coverage that tests that managedFields are returned? (and only if the flag is set)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
pkg/client/fake/client_test.go
Outdated
|
||
Expect(cl.Patch(context.Background(), obj, client.Apply)).To(Succeed()) | ||
|
||
result := obj.DeepCopy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Maybe just deepcopy in l.2569 so you don't need l.2574
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Nice! Thx for working on this. My comments are mostly around improving test coverage and godoc a bit |
@tomasaschan @fsommar could you please find a different place for your comments that are not about feedback to the code changes in here to avoid polluting this? Thanks! |
ddb6d57
to
c33ac89
Compare
This change adds apply support into the fake client. This relies on the upstream support for this which is implemented in a new [FieldManagedObjectTracker][0]. In order to support many types, a custom `multiTypeConverter` is added. [0]: https://github.com/kubernetes/kubernetes/blob/4dc7a48ac6fb631a84e1974772bf7b8fd0bb9c59/staging/src/k8s.io/client-go/testing/fixture.go#L643
This change adds apply support into the fake client.
This relies on the upstream support for this which is implemented in a new FieldManagedObjectTracker. In order to support many types, a custom
multiTypeConverter
is added.The
FieldManagedObjectTracker
results inManagedFields
being set after any operations. As that would be a very breaking change, the fake client will by default unset them and allows to optionally leave them.Based on all existing tests still passing, I believe this change is fully backwards-compatible (But depends on breaking changes such as a very new client-go and apimachinery and #3228).
Fixes #2341