Skip to content

[K8s Contrib CRD] CRD conversion fails if enum is used #24

@Pythoner6

Description

@Pythoner6

If a CRD uses an enum, k8s.contrib.crd will fail, and with a rather unhelpful error message to the user:

–– Pkl Error ––
No member of union type matched value 'new Mapping { ["apiVersion"] = "apiextensions.k8s.io/v1"; ["kind"] = "CustomResourceDefinition"; ["metadata"] { ["annotations"] { ["controller-gen.kubebuilder.io/version"] = "v0.11.3"; ["helm.sh/resource-policy"] = "keep" }; ["name"] = "cephbucketnotifications.ceph.rook.io" }; ["spec"] { ["group"] = "ceph.rook.io"; ["names"] { ["kind"] = "CephBucketNotification"; ["listKind"] = "CephBucketNotificationList"; ["plural"] = "cephbucketnotifications"; ["singular"] = "cephbucketnotification" }; ["scope"] = "Namespaced"; ["versions"] { new Mapping { ["name"] = "v1"; ["schema"] { ["openAPIV3Schema"] { ["description"] = "CephBucketNotification represents a Bucket Notifications"; ["properties"] { ["apiVersion"] { ["description"] = "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources"; ["type"] = "string" }; ["kind"] { ["description"] = "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"; ["type"] = "string" }; ["metadata"] { ["type"] = "object" }; ["spec"] { ["description"] = "BucketNotificationSpec represent the spec of a Bucket Notification"; ["properties"] { ["events"] { ["description"] = "List of events that should trigger the notification"; ["items"] { ["description"] = "BucketNotificationSpec represent the event type of the bucket notification"; ["enum"] { "s3:ObjectCreated:*"; "s3:ObjectCreated:Put"; "s3:ObjectCreated:Post"; "s3:ObjectCreated:Copy"; "s3:ObjectCreated:CompleteMultipartUpload"; "s3:ObjectRemoved:*"; "s3:ObjectRemoved:Delete"; "s3:ObjectRemoved:DeleteMarkerCreated" }; ["type"] = "string" }; ["type"] = "array" }; ["filter"] { ["description"] = "Spec of notification filter"; ["properties"] { ["keyFilters"] { ["description"] = "Filters based on the object's key"; ["items"] { ["description"] = "NotificationKeyFilterRule represent a single key rule in the Notification Filter spec"; ["properties"] { ["name"] { ["description"] = "Name of the filter - prefix/suffix/regex"; ["enum"] { "prefix"; "suffix"; "regex" }; ["type"] = "string" }; ["value"] { ["description"] = "Value to filter on"; ["type"] = "string" } }; ["required"] { "name"; "value" }; ["type"] = "object" }; ["type"] = "array" }; ["metadataFilters"] { ["description"] = "Filters based on the object's metadata"; ["items"] { ["description"] = "NotificationFilterRule represent a single rule in the Notification Filter spec"; ["properties"] { ["name"] { ["description"] = "Name of the metadata or tag"; ["minLength"] = 1; ["type"] = "string" }; ["value"] { ["description"] = "Value to filter on"; ["type"] = "string" } }; ["required"] { "name"; "value" }; ["type"] = "object" }; ["type"] = "array" }; ["tagFilters"] { ["description"] = "Filters based on the object's tags"; ["items"] { ["description"] = "NotificationFilterRule represent a single rule in the Notification Filter spec"; ["properties"] { ["name"] { ["description"] = "Name of the metadata or tag"; ["minLength"] = 1; ["type"] = "string" }; ["value"] { ["description"] = "Value to filter on"; ["type"] = "string" } }; ["required"] { "name"; "value" }; ["type"] = "object" }; ["type"] = "array" } }; ["type"] = "object" }; ["topic"] { ["description"] = "The name of the topic associated with this notification"; ["minLength"] = 1; ["type"] = "string" } }; ["required"] { "topic" }; ["type"] = "object" }; ["status"] { ["description"] = "Status represents the status of an object"; ["properties"] { ["conditions"] { ["items"] { ["description"] = "Condition represents a status condition on any Rook-Ceph Custom Resource."; ["properties"] { ["lastHeartbeatTime"] { ["format"] = "date-time"; ["type"] = "string" }; ["lastTransitionTime"] { ["format"] = "date-time"; ["type"] = "string" }; ["message"] { ["type"] = "string" }; ["reason"] { ["description"] = "ConditionReason is a reason for a condition"; ["type"] = "string" }; ["status"] { ["type"] = "string" }; ["type"] { ["description"] = "ConditionType represent a resource's status"; ["type"] = "string" } }; ["type"] = "object" }; ["type"] = "array" }; ["observedGeneration"] { ["description"] = "ObservedGeneration is the latest generation observed by the controller."; ["format"] = "int64"; ["type"] = "integer" }; ["phase"] { ["type"] = "string" } }; ["type"] = "object"; ["x-kubernetes-preserve-unknown-fields"] = true } }; ["required"] { "metadata"; "spec" }; ["type"] = "object" } }; ["served"] = true; ["storage"] = true; ["subresources"] { ["status"] {} } } } } }'

36 | if (result is ConversionFailure) throw(result.message) else result
                                      ^^^^^^^^^^^^^^^^^^^^^
at pkl.experimental.deepToTyped.deepToTyped#apply.<function#1> (https://github.com/apple/pkl-pantry/blob/[email protected]/packages/pkl.experimental.deepToTyped/deepToTyped.pkl#L36-36)

30 | let (result =
     ^^^^^^^^^^^^^
at pkl.experimental.deepToTyped.deepToTyped#apply (https://github.com/apple/pkl-pantry/blob/[email protected]/packages/pkl.experimental.deepToTyped/deepToTyped.pkl#L30-36)

105 | deepToTyped.apply(ModuleGenerator.CRD, crd) as ModuleGenerator.CRD
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at k8s.contrib.crd.generate#crds.<function#1>[#1] (https://github.com/apple/pkl-pantry/blob/[email protected]/packages/k8s.contrib.crd/generate.pkl#L105-105)

101 | let (parser = new yaml.Parser { useMapping = true })
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at k8s.contrib.crd.generate#crds (https://github.com/apple/pkl-pantry/blob/[email protected]/packages/k8s.contrib.crd/generate.pkl#L101-108)

128 | for (_crd in crds) {
                   ^^^^
at k8s.contrib.crd.generate#modules (https://github.com/apple/pkl-pantry/blob/[email protected]/packages/k8s.contrib.crd/generate.pkl#L128-128)

142 | for (mod in modules) {
                  ^^^^^^^
at k8s.contrib.crd.generate#output.files (https://github.com/apple/pkl-pantry/blob/[email protected]/packages/k8s.contrib.crd/generate.pkl#L142-142)

Manually parsing and then calling deep to typed on the offending CRD (but without the type union used by k8s.contrib.crd) lead to a slightly better error message:

–– Pkl Error ––
Unsupported type for conversion: Any

36 | if (result is ConversionFailure) throw(result.message) else result
                                      ^^^^^^^^^^^^^^^^^^^^^
at pkl.experimental.deepToTyped.deepToTyped#apply.<function#1> (projectpackage://pkg.pkl-lang.org/pkl-pantry/[email protected]#/deepToTyped.pkl)

30 | let (result =
     ^^^^^^^^^^^^^
at pkl.experimental.deepToTyped.deepToTyped#apply (projectpackage://pkg.pkl-lang.org/pkl-pantry/[email protected]#/deepToTyped.pkl)

11 | crd: CustomResourceDefinition = deepToTyped.apply(CRD, parsed)
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at test#crd (file:///home/joseph/dev/nix-pkl/test.pkl, line 11)

106 | text = renderer.renderDocument(value)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/0.25.2/stdlib/base.pkl#L106)

Which eventually lead me to the offending property, which is enum as defined here https://github.com/apple/pkl-k8s/blob/main/generated-package/apiextensions-apiserver/pkg/apis/apiextensions/v1/CustomResourceDefinition.pkl#L272 (there's also two other properties in there that use the type Any: default and example)

An example minimal CRD which reproduces the issue:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
spec:
  group: foo.example.com
  names:
    kind: Foo
    plural: foos
  scope: Namespaced
  versions:
    - name: v1
      schema:
        openAPIV3Schema:
          properties:
            doesntwork:
              enum:
                - foo
              type: string
          type: object
      served: true
      storage: true

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions