Skip to content

Conversation

mdbooth
Copy link
Contributor

@mdbooth mdbooth commented Sep 16, 2025

No description provided.

Copy link
Contributor

openshift-ci bot commented Sep 16, 2025

Hello @mdbooth! Some important instructions when contributing to openshift/api:
API design plays an important part in the user experience of OpenShift and as such API PRs are subject to a high level of scrutiny to ensure they follow our best practices. If you haven't already done so, please review the OpenShift API Conventions and ensure that your proposed changes are compliant. Following these conventions will help expedite the api review process for your PR.

@openshift-ci openshift-ci bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Sep 16, 2025
Copy link
Contributor

openshift-ci bot commented Sep 16, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign everettraven for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

// ensure that only a target CRD compatible with compatibilityCRD may be admitted.
// This field is required.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=1000000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The maximum request size supported is 1.5MiB, in the worst case, each rune will be exactly 1 byte, so the maximum length here should be

Suggested change
// +kubebuilder:validation:MaxLength=1000000
// +kubebuilder:validation:MaxLength=1572864

// metadata is the standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metav1.ObjectMeta `json:"metadata"`
metav1.ObjectMeta `json:"metadata,omitzero"`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter didn't warn me about that one!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure why, a bug to look into I guess 🤔

// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:XValidation:rule="!format.dns1123Subdomain().validate(self).hasValue()",message="a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character."
// +required
CRDRef string `json:"crdRef,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use Ref in openshift APIs. I would generally expect this to be more like a local object reference, so to be consistent with other APIs, WDYT of

crd:
  name:

Potentially, instead of crd, what about target?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly ok on dropping ref if that's the convention.

Are you channeling https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#object-references by any chance here?

It is okay to have the "{field}" component indicate the resource type. For example, "secretRef" when referencing a secret. However, this comes with the risk of the field being a misnomer in the case that the field is expanded to reference more than one type.

Could this ever reference a different type? It feels unlikely. That would likely require a different tool. With that in mind, I think crd is more explicit and therefore more obvious. This object is about adding requirements to a CRD, and the CRD is specified in the crd field.

Is there any circumstance when we might add additional information to a CRD reference? The only things I can think of are:

  • UID - this precise CRD and not its successors
  • APIVersion - CRD version v2beta1

So that's realistic, at least.

I'll change it to

  crd:
    name: 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had leant on target for a few reasons, yes, it's more generic, but also I wondered if it might have been more intuitive and idiomatic, I have seen many targetRef before across K8s APIs. We do talk about the field being a target in the godoc, so it felt like it made sense.

Comment on lines 46 to 47
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength:=253
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent use of := vs = at the end here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between the 2?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find := in the docs at all now I look for it. I'm assuming I've just cargo-culted this from somewhere and should stop.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kubebuilder supports both := and = on all of its markers. Upstream markers (e.g. +default) do not support :=, so we would prefer all markers use = and we shouldn't use :=.

Has lead to issues in the past where upstream markers have been ignored because of the := typo

Comment on lines 52 to 59
// creatorDescription is a string describing the owner of this CRDCompatibilityRequirement. It will be printed in any error or
// warning emitted by any of the CRDCompatibilityRequirement's webhooks. It should indicate to the recipient who they need to coordinate
// with in order to safely update the target CRD. The message emitted will be: "This requirement was added by <creatorDescription>".
// This field is required.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +required
CreatorDescription string `json:"creatorDescription,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered what a valid set of characters might look like for this field? Since this is being used as input to a logging output, I assume it would be bad if I tried to inject some json into this?

creatorDescription: "\" nastySomething:\"doBadThingToLogs\""

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming that any JSON logging library we're using escapes its output. If it doesn't we already have severe problems! We certainly don't want to ask the user to do their own escaping here.

It's a while since I wrote this, and I just re-read it with fresh eyes. I think it's usefully descriptive? I can't think how I would improve it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking along the lines of restricting the valid character set to [a-zA-Z0-9\ ] to prevent folks from putting any data into this string that we need to be worried about or even consider the safety element of

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That just feels like a footgun. For example, it would prevent us from using non-US ASCII characters in a string which is intended to describe people. I don't think we can usefully validate this string, and I think we can trust logging software to escape any possible byte sequence correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kubernetes is built on US ASCII already, all of our APIs, code and even user facing logging is also US ASCII. If we implement this validation, we can always relax it later if we were to get complaints. I'm open to a less restrictive set if you have a suggestion, but I don't think we should leave it unbounded

Comment on lines 46 to 48
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:XValidation:rule="!format.dns1123Subdomain().validate(self).hasValue()",message="a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All validations like this must be explained in prose inside the godoc

Suggested change
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:XValidation:rule="!format.dns1123Subdomain().validate(self).hasValue()",message="a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character."
// crdRef must be at most 253 characters in length and must consist only of lower-case alphanumeric characters, periods (.) and hyphens (-). Each period separated label must start and end with an alphanumeric character and be at most 63 characters in length.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:XValidation:rule="!format.dns1123Subdomain().validate(self).hasValue()",message="a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character."

Please apply similar feedback to other fields

Copy link
Contributor Author

@mdbooth mdbooth Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably we do this for UX, and in general I get it. However, in this case (and presumably many similar cases), I believe the additional text actually makes the UX worse because it's complex, distracting, and irrelevant. The 'correct' validation here is 'the name of a CRD'. This CRD may or may not exist yet, so we can't even validate it with a VAP. All we're trying to do here with this somewhat complex validation (which I presumably just copied from somewhere) is exclude a certain class of thing which is obviously wrong because it would fail k8s naming validation. If it excludes the valid name of a CRD, that would be a bug. If this ever did happen, the user would open a bug against the product because the field did not accept the name of their CRD, not because they mentally executed the pseudocode in the documentation1. Even if they did read the pseudocode, it might lead them to the incorrect belief that we only intend to support CRDs whose names conform to some arbitrary additional restrictions.

I can see that this kind of validation is useful when we're trying to guide the user to form correct input for something we're going to create, but here we're just referencing something which was validated elsewhere with rules we don't control. Our own validation is nothing more than a loose proxy to exclude a small class of obvious error.

Footnotes

  1. Which in any case hasn't corresponded to the actual validation for the last 18 months since we tweaked it but forgot to make the corresponding change to the pseudocode in the docs, but nobody noticed because we don't run tests against the docs2.

  2. A linter which uses AI to attempt to validate that godoc remains consistent with the code it describes 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validation markers are not documented in any user readable documentation by default. We document all validations in the text to explain what the valid values are, so that folks using oc explain or reading our generated API documentation in the product docs have a sense of what value input is.

While I get your point about us documenting a validation that isn't actually ours, we aim to be consistent in validating the field validations across all fields in our APIs, independent of whether its our own restriction, or something imposed upon us

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack. I'll also make it clear that it's a proxy validation, just in case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation of a CRD name is never going to change, I don't think we need to document that this is proxy validation

// CRDCompatibilityRequirementStatus defines the observed status of the CRD Compatibility Requirement.
// +kubebuilder:validation:MinProperties=1
type CRDCompatibilityRequirementStatus struct {
// conditions is a list of conditions and their status.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document valid condition types?

// +optional
// +listType=map
// +listMapKey=type
// +kubebuilder:validation:MaxItems=16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a MinItems 1 as well

// uid is the uid of the observed CRD.
// +kubebuilder:validation:MaxLength=36
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Format=uuid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, the API server won't actually validate this at admission time, so we should validate this ourselves with a CEL rule (please test)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm convinced I've written an API validation for this before, but I can't find it now 🤔

Is there a canned one you're aware of that's already battle hardened? It's not actually important, so if this requires anything more than a trivial amount of work I'll just remove the validation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it further: this is status. A validation failure here is only ever likely to result in a bug. I'm just going to remove it because it's unnecessary tight coupling.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though this is status, it should be validated correctly

// +kubebuilder:validation:Format=uuid
// +kubebuilder:validation:XValidation:rule="!format.uuid().validate(self).hasValue()",message="uid must be a valid UUID. It must consist only of lower-case hexadecimal digits, in 5 hyphenated blocks, where the blocks are of length 8-4-4-4-12 respectively."

// metadata is the standard list's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ListMeta `json:"metadata"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metav1.ListMeta `json:"metadata"`
metav1.ListMeta `json:"metadata,omitzero"`

@damdo
Copy link
Member

damdo commented Sep 16, 2025

/assign @damdo

@openshift-ci openshift-ci bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Oct 9, 2025
@chrischdi chrischdi force-pushed the crd-compatibility-checker branch from 6f108bd to a8e1698 Compare October 9, 2025 07:48

// CompatibilitySchema defines the schema used by crdSchemaValidation and objectSchemaValidation.
type CompatibilitySchema struct {
// crdYAML contains the complete YAML document of the CRD from transport config map.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mention transport config maps here: they're a detail of where CCAPIO specifically gets its data from. Other actors won't be using transport config maps.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agree :-)

ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty"`

// matchConditions defines the matchConditions field of the resulting ValidatingWebhookConfiguration.
// FIXME(chrischdi): should we embed this type? Or maintain our own copy of MatchCondition?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there's any OpenShift-specific prohibition on this, my strong recommendation is to embed. We're explicitly and deliberately proxying the behaviour of a kube api here. By embedding it we automatically inherit changes to it and we don't have to convert types internally. This requires us to trust that upstream will be a responsible steward of this API. I suspect that, in practise, they will be a more reliable steward.

A potential issue I see with this is pulling a newer API into an older release. However, this PR doesn't change go.mod so it seems it doesn't impose any version restrictions which don't already exist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upstream type is small, so copying it should not be too onerous. The upstream type also has no validation markers at present (though DV will fix that), which means that if we borrow the upstream type now, the schema generated for the type will change over time underneath us.

I would prefer we implement a copy type for now, at least until the upstream has sufficient validation markers that controller-tools will understand to implement the correct validation.

}

// CRDAdmitAction determines the action taken when a CRD is not compatible.
// +kubebuilder:validation:Enum=Enforce;Warn
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bikeshed: I was looking at VAPs where the verb is Deny. I wonder if we should switch Enforce to Deny.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for Deny from my side.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

@chrischdi chrischdi force-pushed the crd-compatibility-checker branch from 74f30c5 to 91af025 Compare October 9, 2025 15:02
@chrischdi chrischdi force-pushed the crd-compatibility-checker branch from 91af025 to 133e7a3 Compare October 9, 2025 17:41
// Each field path string must be between 1 and 8,192 characters in length.
// The list may contain at most 1,024 field paths.
// When not specified, all fields in the YAML will be validated.
// FIXME(chrischdi): explain the format for it. // FIXME(chrischdi): explain the format for it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(TODO) resolve

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a regex we can apply to give some early feedback on the format of the strings here?

Copy link
Contributor

openshift-ci bot commented Oct 9, 2025

@mdbooth: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/verify 133e7a3 link true /test verify

Full PR test history. Your PR dashboard.

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. I understand the commands that are listed here.

// Each field path string must be between 1 and 8,192 characters in length.
// The list may contain at most 1,024 field paths.
// When not specified, all fields in the YAML will be validated.
// FIXME(chrischdi): explain the format for it. // FIXME(chrischdi): explain the format for it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a regex we can apply to give some early feedback on the format of the strings here?

Action CRDAdmitAction `json:"action,omitempty"`

// namespaceSelector defines the namespaceSelector field of the resulting ValidatingWebhookConfiguration.
// When not specified, objects from all namespaces matching the objectSelector will be subject to validation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere we should explain what happens when neither namespace selector or object selector are specified

ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty"`

// matchConditions defines the matchConditions field of the resulting ValidatingWebhookConfiguration.
// FIXME(chrischdi): should we embed this type? Or maintain our own copy of MatchCondition?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upstream type is small, so copying it should not be too onerous. The upstream type also has no validation markers at present (though DV will fix that), which means that if we borrow the upstream type now, the schema generated for the type will change over time underneath us.

I would prefer we implement a copy type for now, at least until the upstream has sufficient validation markers that controller-tools will understand to implement the correct validation.

}

// CRDAdmitAction determines the action taken when a CRD is not compatible.
// +kubebuilder:validation:Enum=Enforce;Warn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

Conditions []metav1.Condition `json:"conditions,omitempty"`

// observedCRD documents the uid and generation of the CRD object when the current status was written.
// This field will not be emitted if the target CRD does not exist or could not be retrieved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistent language with other APIs

Suggested change
// This field will not be emitted if the target CRD does not exist or could not be retrieved.
// This field will be omitted if the target CRD does not exist or could not be retrieved.

// +optional
ObservedCRD ObservedCRD `json:"observedCRD,omitzero"`

// crdName is the name of the target CRD. The target CRD is not required to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not part of the observedCRD?

Why not have

observedCRD:
  name: ...

And then populate the UID and generation when it is actually observed?

)

// CRDCompatibilityRequirementStatus defines the observed status of the CRD Compatibility Requirement.
// +kubebuilder:validation:MinProperties=1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditions should be required? Or maybe the CRD name? Possibly both tbh

// When present, must be between 1 and 253 characters and conform to RFC 1123 subdomain format:
// lowercase alphanumeric characters, '-' or '.', starting and ending with alphanumeric characters.
// When not specified, the requirement applies to any CRD name discovered from the compatibility schema.
// This field is optional.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I wanted to work my way around the system, I could remove this field currently, how disastrous would that be?

We may want to add CEL to make this immutable and un-unsettable 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants