Skip to content
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

dhall-openapi generates invalid JSONSchemaProps for nested properties #2127

Closed
WillSewell opened this issue Jan 5, 2021 · 1 comment
Closed

Comments

@WillSewell
Copy link
Collaborator

WillSewell commented Jan 5, 2021

There are a number of recursive properties in io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps. See OpenAPI definition:

"io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps": {
  "description": "JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).",
  "properties": {
    "$ref": {
      "type": "string"
    },
    "$schema": {
      "type": "string"
    },
    "additionalItems": {
      "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaPropsOrBool"
    },
    "additionalProperties": {
      "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaPropsOrBool"
    },
    "allOf": {
      "items": {
        "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"
      },
      "type": "array"
    },
    "anyOf": {
      "items": {
        "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"
      },
      "type": "array"
    },
    "default": {
      "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSON",
      "description": "default is a default value for undefined object fields. Defaulting is a beta feature under the CustomResourceDefaulting feature gate. Defaulting requires spec.preserveUnknownFields to be false."
    },
    "definitions": {
      "additionalProperties": {
        "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"
      },
      "type": "object"
    },
    "dependencies": {
      "additionalProperties": {
        "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaPropsOrStringArray"
      },
      "type": "object"
    },
    "description": {
      "type": "string"
    },
    "enum": {
      "items": {
        "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSON"
      },
      "type": "array"
    },
    "example": {
      "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSON"
    },
    "exclusiveMaximum": {
      "type": "boolean"
    },
    "exclusiveMinimum": {
      "type": "boolean"
    },
    "externalDocs": {
      "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.ExternalDocumentation"
    },
    "format": {
      "type": "string"
    },
    "id": {
      "type": "string"
    },
    "items": {
      "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaPropsOrArray"
    },
    "maxItems": {
      "format": "int64",
      "type": "integer"
    },
    "maxLength": {
      "format": "int64",
      "type": "integer"
    },
    "maxProperties": {
      "format": "int64",
      "type": "integer"
    },
    "maximum": {
      "format": "double",
      "type": "number"
    },
    "minItems": {
      "format": "int64",
      "type": "integer"
    },
    "minLength": {
      "format": "int64",
      "type": "integer"
    },
    "minProperties": {
      "format": "int64",
      "type": "integer"
    },
    "minimum": {
      "format": "double",
      "type": "number"
    },
    "multipleOf": {
      "format": "double",
      "type": "number"
    },
    "not": {
      "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"
    },
    "nullable": {
      "type": "boolean"
    },
    "oneOf": {
      "items": {
        "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"
      },
      "type": "array"
    },
    "pattern": {
      "type": "string"
    },
    "patternProperties": {
      "additionalProperties": {
        "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"
      },
      "type": "object"
    },
    "properties": {
      "additionalProperties": {
        "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"
      },
      "type": "object"
    },
    "required": {
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "title": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "uniqueItems": {
      "type": "boolean"
    },
    "x-kubernetes-embedded-resource": {
      "description": "x-kubernetes-embedded-resource defines that the value is an embedded Kubernetes runtime.Object, with TypeMeta and ObjectMeta. The type must be object. It is allowed to further restrict the embedded object. kind, apiVersion and metadata are validated automatically. x-kubernetes-preserve-unknown-fields is allowed to be true, but does not have to be if the object is fully specified (up to kind, apiVersion, metadata).",
      "type": "boolean"
    },
    "x-kubernetes-int-or-string": {
      "description": "x-kubernetes-int-or-string specifies that this value is either an integer or a string. If this is true, an empty type is allowed and type as child of anyOf is permitted if following one of the following patterns:\n\n1) anyOf:\n   - type: integer\n   - type: string\n2) allOf:\n   - anyOf:\n     - type: integer\n     - type: string\n   - ... zero or more",
      "type": "boolean"
    },
    "x-kubernetes-list-map-keys": {
      "description": "x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used as the index of the map.\n\nThis tag MUST only be used on lists that have the \"x-kubernetes-list-type\" extension set to \"map\". Also, the values specified for this attribute must be a scalar typed field of the child structure (no nesting is supported).",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "x-kubernetes-list-type": {
      "description": "x-kubernetes-list-type annotates an array to further describe its topology. This extension must only be used on lists and may have 3 possible values:\n\n1) `atomic`: the list is treated as a single entity, like a scalar.\n     Atomic lists will be entirely replaced when updated. This extension\n     may be used on any type of list (struct, scalar, ...).\n2) `set`:\n     Sets are lists that must not have multiple items with the same value. Each\n     value must be a scalar, an object with x-kubernetes-map-type `atomic` or an\n     array with x-kubernetes-list-type `atomic`.\n3) `map`:\n     These lists are like maps in that their elements have a non-index key\n     used to identify them. Order is preserved upon merge. The map tag\n     must only be used on a list with elements of type object.\nDefaults to atomic for arrays.",
      "type": "string"
    },
    "x-kubernetes-preserve-unknown-fields": {
      "description": "x-kubernetes-preserve-unknown-fields stops the API server decoding step from pruning fields which are not specified in the validation schema. This affects fields recursively, but switches back to normal pruning behaviour if nested properties or additionalProperties are specified in the schema. This can either be true or undefined. False is forbidden.",
      "type": "boolean"
    }
  },
  "type": "object"
}

This allows the properties (for example) to be arbitrarily nested. For example, the example given on https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ nests properties two levels deep.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.stable.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: stable.example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
    - name: v1
      # Each version can be enabled/disabled by Served flag.
      served: true
      # One and only one version must be marked as the storage version.
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
  # either Namespaced or Cluster
  scope: Namespaced
  names:
    # plural name to be used in the URL: /apis/<group>/<version>/<plural>
    plural: crontabs
    # singular name to be used as an alias on the CLI and for display
    singular: crontab
    # kind is normally the CamelCased singular type. Your resource manifests use this.
    kind: CronTab
    # shortNames allow shorter string to match your resource on the CLI
    shortNames:
    - ct

However dhall-openapi generates a type for these properties of Optional (List { mapKey : Text, mapValue : Text }).

https://github.com/dhall-lang/dhall-kubernetes/blob/a4126b7f8f0c0935e4d86f0f596176c41efbe6fe/1.17/types/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps.dhall#L36

This means that there are valid CRDs that are not possible to represent with the dhall-kubernetes types.

I'm assuming the reason dhall-openapi generates List { mapKey : Text, mapValue : Text } is due to the trickiness of representing recursive types in the language.

As someone new to the language I'm not really sure what the solution to this problem is, so I would be interested in hearing your thoughts.

@WillSewell
Copy link
Collaborator Author

WillSewell commented Jan 6, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant