Description
Note: This basically a reincarnation of the original topic of #2318. Since that issue has a lot of comments already, and a lot of them are just bot commands, I wanted to start over with a summary of what was discussed at KubeCon EU 22.
Motivation
New uses of corev1.ObjectReference
are discouraged since about two years now. In my opinion we should go even further and try to get rid of corev1.ObjectReference
completely. This is technically a breaking change (but it should be discussed how severe it actually is), so we at least have to wait until corev1beta2
. Even though it might take a while until we get there, we should start preparing for it now by either deciding on which existing types we want to use, or which custom reference types we want to add to the API.
We should also try to come up with a somewhat exhaustive lists of the types we need, and add all of the relevant types, even if they are unused for now. This avoids having multiple separate discussions when the need for a new type comes up, and makes designing a consistent API much easier. In addition, new APIs can directly choose the appropriate reference type, making adoption much simpler.
There are several reasons not to use corev1.ObjectReference
(some also mentioned in the comment discouraging the use of it):
- We ignore both
ResourceVersion
andFieldPath
entirely. In addition we usually do not respectUID
. While the latter might be useful in a status, it's usually not necessary to have. - We do not need the
Namespace
field in a lot of cases. In fact, it even requires to write a lot of code just to keep it.
Whenever resources are only allowed to reference objects in the same namespace, we have defaulting and validation code in place to insure this (I think there are few webhooks that do only this). For uses this is only apparent when applying the resources to the API, leading to a bad UX. In the code, the field is usually ignored, as the namespace matches the object anyway. In case the validating webhooks are not working, this could lead to unexpected behaviour. - In a lot of cases we could replace
APIVersion
withgroup
. This becomes especially relevant when trying to optimise for GitOps: When there is a version in the reference, that version has to be upgraded in Git whenever the API version of the referenced object changes. Since the idea of serving multiple versions is that the client can decide which version to use, we should do so, and get rid of it where we can.
Proposal
I propose to add 6 new reference types.
In addition, we should add functions and methods to convert between them and to and from corev1.ObjectReference
. This way the types are easier to adopt, both within cluster-api, as well as by providers and other tools.
type ObjectReference struct {
Group string `json:"group"`
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
// Add these for all types
func NewObjectReference(ref v1.ObjectReference) ObjectReference {}
func (ObjectReference) ToV1Reference() v1.ObjectReference
func (LocalObjectReference) ToNamespaced(namespace string) ObjectReference {}
func (ObjectReference) ToLocal() LocalObjectReference {}
type LocalObjectReference struct {
Group string `json:"group"`
Kind string `json:"kind" `
Name string `json:"name"`
}
func (PinnedObjectReference) Unpin() ObjectReference {}
func (ObjectReference) Pin(uuid string) PinnedObjectReference {}
type PinnedObjectReference struct {
Group string `json:"group"`
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace"`
UID types.UID `json:"uid"`
}
func (PinnedLocalObjectReference) Unpin() LocalObjectReference {}
func (LocalObjectReference) Pin(uuid string) PinnedLocalObjectReference {}
type PinnedLocalObjectReference struct {
Group string `json:"group"`
Kind string `json:"kind"`
Name string `json:"name"`
UID types.UID `json:"uid"`
}
func (VersionedLocalObjectReference) ToUnversioned() LocalObjectReference {}
func (LocalObjectReference) ToVersioned(version string) VersionedLocalObjectReference {}
type VersionedLocalObjectReference struct {
Group string `json:"group"`
Version string `json:"version"`
Kind string `json:"kind" `
Name string `json:"name"`
}
Alternatives
@vincepri mentioned that he would prefer using other core types if possible, instead of implementing our own. Currently there are three other types available:
LocalObjectReference
- only withname
TypedLocalObjectReference
- only withname
,apigroup
andkind
SecretReference
- only withname
andnamespace
Only the TypedLocalObjectReference
seems to cover our needs, as we require typed references in most cases due to the modularity of Cluster API. We might be able to use the LocalObjectReference
in some cases, e.g. for the References to Cluster
.
Iirc the main motivation to use corev1
types was twofold:
- We should try to avoid having to maintain additional types
Since these types are only used as part of our API resources, and not as individual ones, I don't think it's a big deal to add them. - It is confusing/impractical for users to have to deal with our custom types
For the aforementioned reasons I think the custom types improve user experience, and at the same time make our code more maintainable and easier to follow. Right now a lot of internal tools acceptcorev1.ObjectReference
, but ignore most parameters (without documentation). By using custom types, utilities can accept structs that only contain fields that they support.
Using the conversion functions it is easy to get acorev1.ObjectReference
when required, or convert from one. Users have to import our API package anyway, so they have the conversion methods and functions available if they are part of that package.
Is replacing existing references a breaking change?
Yes. We are removing fields from the API that were present before, so it certainly is a breaking API change.
But I'd like to argue that the impact of that breaking change is so small, that we can justify to make it anyway, at least when moving to v1beta2.
First of all, the change does not remove any features. In fact, it does not change any behavior at all. All removed fields are either completely unused, made useless by validation (namespaces) or not very useful (version part of apiversion).
Replacing apiversion
with group
is the only thing that makes this change really noticeable as it requires a small amount of manual migration. If we stick with apiversion
the change won't be noticed by most users when using yaml or json.
In code you will of course have to replace object references with the new custom types. But converting to the newer API will require changing the import path, and having to change a few struct names is not that much effort. Even when corev1.ObjectReference
is used heavily, the conversion functions make it easy to convert between them, and it can be documented which fields will be dropped, making it much clearer what is going on.
The only case where it does actually have an impact, is when users use fields of the references for their own purposes, e.g. reconciling them using a custom controller. In addition, users that explicitly set the namespace field will need to stop doing so.
/area api
/kind cleanup
/kind api-change