-
-
Notifications
You must be signed in to change notification settings - Fork 379
Move ApiCapabilities into ApiResource and add Scope trait
#1038
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
ApiCapabilities into ApiResourceApiCapabilities into ApiResource and add Scope trait
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #1038 +/- ##
==========================================
- Coverage 72.29% 71.82% -0.47%
==========================================
Files 65 66 +1
Lines 4681 4699 +18
==========================================
- Hits 3384 3375 -9
- Misses 1297 1324 +27
|
|
Eyes on this would be appreciated @kube-rs/maintainers . Clarity on how to convert between scope requirement on dynamic and static types is needed for a good way forward on the new client api in #1037, but this should stand well on its own without that (even though it doesn't go all the way with subresources). |
nightkr
left a comment
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.
+1 on the overall concept, having to distinguish resource and caps was pretty weird and awkward.
| let mut ar = parse::parse_apiresource(res, &list.group_version).map_err( | ||
| |ParseGroupVersionError(s)| Error::Discovery(DiscoveryError::InvalidGroupVersion(s)), | ||
| )?; | ||
| let caps = parse::parse_apicapabilities(&list, &res.name)?; | ||
| return Ok((ar, caps)); | ||
| ar.subresources = parse::find_subresources(&list, &res.name)?; | ||
| return Ok(ar); |
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.
Would it make sense to reorganize this to:
struct ApiResourceMeta; // current ApiResource
struct ApiCapabilities; // current ApiCapabilities
struct ApiResource { // current (ApiResource, ApiCapabilities)
meta: ApiResourceMeta,
caps: ApiCapabilities,
}That way we could provide wrapper functions as needed on ApiResource to avoid having the user care about the distinction, but also statically guarantee that caps are actually available when required. We might even want to impl Deref<Target = ApiResourceMeta> for ApiResource to approximate subtyping...
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.
Sorry, s/caps/subresources/g.
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 that sounds like the nicest possible way to split the structs. It is still a bit awkward though:
- the upstream struct does not have a split like that
- we are not really able to split
APIResourceinto Caps/Info now (where does short_names go? is scope a capability?), and it might be even harder in the future if upstream adds more info - we probably need to push the distinction stuff into
ApiResourceMetainstead as a user could want to query the api with just the GVK + plural, so now they need a partialApiResourceMetaconstructed somehow
The distinction here might not be that big of a deal because if we provide clean paths to get fully speced out variants of ApiResource via kube-derive or discovery then that's the happy path for users. If people want to construct them manually, then they probably should care about the distinction.
(As for the subresource parts of ApiResource, I'm not entirely sure what is the best way to represent that inside ApiResource. Nesting ApiResource inside ApiResource to represent subresources is unnecessary (and leaves many fields unused). Hoping to experiment and clean up that part in the client api pr - for now have left it as it was)
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.
What do you think?
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.
After looking into it a bit more it feels like there was a useful distinction where ApiResource was "the information you need to start querying the API" and ApiCapabilities was "auxilary data that might be useful for a UI (like kubectl)". I think it makes sense to keep those types distinct to document whether ApiCapabilities is also available at some given point.
Where the old API (IMO) fell down was in that having to deal with ApiCapabilities ended up being a constant annoyance for people who only care about ApiResource.
APIResource is a slightly different beast, since it's always generated by the K8s API server, which always knows the canonical information for all of the fields. from_gvk isn't really an API that makes sense for the K8s type.
we are not really able to split APIResource into Caps/Info now (where does short_names go? is scope a capability?), and it might be even harder in the future if upstream adds more info
short_names is definitely an info/cap/whatever-we-end-up-calling-it. scope I could see going both ways, but I'm leaning towards info as well since if you already have the object (or know how to construct it) then you can infer the scope based on that. If you want to guide the user towards how to construct the object then you'll want an ApiResourceInfo.
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.
Yeah, I don't think Option-wrapping at runtime is great.
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.
Have put a capabilities member as Option<ApiCapabilities> directly on ApiResource now.
Have not included namespaced in there because namespaced is currently the one thing we can find through all forms of construction:
- kube-derive/discovery (exists by construction)
- ApiResource::erase (using the new Scope trait through Resource)
- ApiResource manual construction (if setting namespaced manually - although that's up to the user to get right in that case)
In fact, namespaced is now universally accessible so have made a Resource::is_namespaced method to help determine the namespace (which even works in the dynamic case).
Having namespaced within ApiCapabilities then we would need to partially construct it in ApiResource::erase and that means all fields within ApiCapabilities need to be option wrapped, which is not great for users of the dynamic_api and similar (multiple guaranteed unwraps necessary in their code). Currently it's now limited to a single unwrap in dynamic_api to access the caps:
// use discovery to get an AR
let caps = ar.capabilities.as_ref().expect("guaranteed because we used discovery");
caps.supports_operation(...); // can now do a check without option unwrapping from caps
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.
Have lifted the namespaced into capabilities struct by removing the option, and relying on default instead.
I think this is the least worst option, that still preserves a reasonable split (although the setters have been moved into ApiResource because we need to set namespaced from the root (it's the resource that can extract it) - but that's fine, these setters are only set by either kube-derive or hardcore manual users anyway).
So now ApiCapabilities has namespace/verbs/subresources/shortnames, and all of these will be set in the happy path (kube-derive/discovery). It's the happy path we want to be as frictionless as possible, so having options for just the manual path ends up mandating unwraps on the happy path which is not great.
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 we just keep talking past each other.
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.
Okay, I did a bit more digging.
I tried implementing the ApiResource/ApiResourceCore split to see how it'd go, and Deref/DerefMut helped hide it from most usage sites (aside from constructors): https://github.com/teozkr/kube-rs/tree/rm-caps-/typed.
However, this also highlit another problem with merging caps into ApiResource: ApiResource is currently embedded into ObjectRef<DynamicObject>, so any fields on it are also considered for ObjectRef's Eq/Hash implementations. This means that an ObjectRef<DynamicObject> constructed using a discovered type and an ObjectRef constructed by erasing a k8s_openapi type would not be considered equal, even if they refer to same version of the exact same Kubernetes object!.
Signed-off-by: clux <[email protected]>
Signed-off-by: clux <[email protected]>
Signed-off-by: clux <[email protected]>
Signed-off-by: clux <[email protected]>
Signed-off-by: clux <[email protected]>
Co-authored-by: Teo Klestrup Röijezon <[email protected]> Signed-off-by: Eirik A <[email protected]>
Signed-off-by: clux <[email protected]>
Signed-off-by: clux <[email protected]>
Signed-off-by: clux <[email protected]>
Signed-off-by: clux <[email protected]>
Signed-off-by: clux <[email protected]>
ApiCapabilities into ApiResource and add Scope traitApiCapabilities into ApiResource and add Scope trait
|
Going to put this on hold until 0.77. I have some ideas for it, but it's been a while since a release and will get that out the door first. |
Generally simplified code everywhere. The main edge cases worth documenting here is that
ApiResourcecan be constructed in multiple ways, and some of these will not yield all the fields on it.MergedMovedApiCapabilitiesdata intoApiResourceshortnamestoApiResourcefor Add shortnames field in kube::discovery::ApiResource #1002ApiCapabilitiesreturns (always constructed together withApiResource, so now returned as one)Scopetrait implemented for allk8s-openapi::ResourceScope+ ourDynamicResourceScopeResourcetrait to require the associated Scope type to implement the newScopetraitResource::is_namespacedusingScopetrait or dyn type inspectionApiResource::eraseto be able to determinenamespacedbool from theScopetraitApiResourcectors (removedApiResource::from_gvk_with_pluralin favour ofApiResource::new)ApiCapabilitiestoApiResourceApiResourceApiResource(todo in WIP: Core methods onClient#1037)