feat(ci): add CRD schema generation for kubeconform validation

Add scripts to extract OpenAPI v3 schemas from CRD definitions in all
kustomize overlays and write JSON schema files to ci/crd-schemas/ for
kubeconform validation. This allows kubeconform to validate CRD instances
(Elasticsearch, Kibana, CNPG Cluster, VictoriaMetrics, etc.) instead of
skipping or erroring on them.

- ci/generate-crd-schemas.py: extracts schemas from CRD YAML on stdin
- ci/generate-crd-schemas.sh: iterates overlays, pipes to Python script
- ci/validate-apps.sh, ci/validate-clusters.sh: add local schema-location fallback
- Makefile: add generate-schemas target
- ci/crd-schemas/: 67 generated schemas (cert-manager, cnpg, elastic, victoriametrics, argocd, gateway)
- .pre-commit-config.yaml: raise large-file limit to 2 MB for generated schemas

Run `make generate-schemas` to regenerate after CRD version changes.
This commit is contained in:
2026-05-30 23:09:34 +10:00
parent 1b781e0885
commit d21d9a92d3
73 changed files with 87982 additions and 1 deletions
@@ -0,0 +1,395 @@
{
"$schema": "http://json-schema.org/schema#",
"description": "ElasticsearchAutoscaler represents an ElasticsearchAutoscaler resource in a Kubernetes cluster.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore 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.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"metadata": {
"type": "object"
},
"spec": {
"description": "ElasticsearchAutoscalerSpec holds the specification of an Elasticsearch autoscaler resource.",
"properties": {
"elasticsearchRef": {
"description": "ElasticsearchRef is a reference to an Elasticsearch cluster that exists in the same namespace.",
"properties": {
"name": {
"description": "Name is the name of the Elasticsearch resource to scale automatically.",
"minLength": 1,
"type": "string"
}
},
"type": "object"
},
"policies": {
"items": {
"description": "AutoscalingPolicySpec holds a named autoscaling policy and the associated resources limits (cpu, memory, storage).",
"properties": {
"deciders": {
"additionalProperties": {
"additionalProperties": {
"type": "string"
},
"description": "DeciderSettings allow the user to tweak autoscaling deciders.\nThe map data structure complies with the <key,value> format expected by Elasticsearch.",
"type": "object"
},
"description": "Deciders allow the user to override default settings for autoscaling deciders.",
"type": "object"
},
"name": {
"description": "Name identifies the autoscaling policy in the autoscaling specification.",
"type": "string"
},
"resources": {
"description": "AutoscalingResources model the limits, submitted by the user, for the supported resources in an autoscaling policy.\nOnly the node count range is mandatory. For other resources, a limit range is required only\nif the Elasticsearch autoscaling capacity API returns a requirement for a given resource.\nFor example, the memory limit range is only required if the autoscaling API response contains a memory requirement.\nIf there is no limit range for a resource, and if that resource is not mandatory, then the resources in the NodeSets\nmanaged by the autoscaling policy are left untouched.",
"properties": {
"cpu": {
"description": "QuantityRange models a resource limit range for resources which can be expressed with resource.Quantity.",
"properties": {
"max": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "Max represents the upper limit for the resources managed by the autoscaler.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
},
"min": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "Min represents the lower limit for the resources managed by the autoscaler.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
},
"requestsToLimitsRatio": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "RequestsToLimitsRatio allows to customize Kubernetes resource Limit based on the Request.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
}
},
"required": [
"max",
"min"
],
"type": "object"
},
"memory": {
"description": "QuantityRange models a resource limit range for resources which can be expressed with resource.Quantity.",
"properties": {
"max": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "Max represents the upper limit for the resources managed by the autoscaler.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
},
"min": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "Min represents the lower limit for the resources managed by the autoscaler.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
},
"requestsToLimitsRatio": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "RequestsToLimitsRatio allows to customize Kubernetes resource Limit based on the Request.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
}
},
"required": [
"max",
"min"
],
"type": "object"
},
"nodeCount": {
"description": "NodeCountRange is used to model the minimum and the maximum number of nodes over all the NodeSets managed by the same autoscaling policy.",
"properties": {
"max": {
"description": "Max represents the maximum number of nodes in a tier.",
"format": "int32",
"type": "integer"
},
"min": {
"description": "Min represents the minimum number of nodes in a tier.",
"format": "int32",
"type": "integer"
}
},
"required": [
"max",
"min"
],
"type": "object"
},
"storage": {
"description": "QuantityRange models a resource limit range for resources which can be expressed with resource.Quantity.",
"properties": {
"max": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "Max represents the upper limit for the resources managed by the autoscaler.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
},
"min": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "Min represents the lower limit for the resources managed by the autoscaler.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
},
"requestsToLimitsRatio": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "RequestsToLimitsRatio allows to customize Kubernetes resource Limit based on the Request.",
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
}
},
"required": [
"max",
"min"
],
"type": "object"
}
},
"required": [
"nodeCount"
],
"type": "object"
},
"roles": {
"description": "An autoscaling policy must target a unique set of roles.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"resources"
],
"type": "object"
},
"type": "array"
},
"pollingPeriod": {
"description": "PollingPeriod is the period at which to synchronize with the Elasticsearch autoscaling API.",
"type": "string"
}
},
"required": [
"elasticsearchRef",
"policies"
],
"type": "object"
},
"status": {
"properties": {
"conditions": {
"description": "Conditions holds the current service state of the autoscaling controller.",
"items": {
"description": "Condition represents Elasticsearch resource's condition.\n**This API is in technical preview and may be changed or removed in a future release.**",
"properties": {
"lastTransitionTime": {
"format": "date-time",
"type": "string"
},
"message": {
"type": "string"
},
"status": {
"type": "string"
},
"type": {
"description": "ConditionType defines the condition of an Elasticsearch resource.",
"type": "string"
}
},
"required": [
"status",
"type"
],
"type": "object"
},
"type": "array"
},
"observedGeneration": {
"description": "ObservedGeneration is the last observed generation by the controller.",
"format": "int64",
"type": "integer"
},
"policies": {
"description": "AutoscalingPolicyStatuses is used to expose state messages to user or external system.",
"items": {
"properties": {
"lastModificationTime": {
"description": "LastModificationTime is the last time the resources have been updated, used by the cooldown algorithm.",
"format": "date-time",
"type": "string"
},
"name": {
"description": "Name is the name of the autoscaling policy",
"type": "string"
},
"nodeSets": {
"description": "NodeSetNodeCount holds the number of nodes for each nodeSet.",
"items": {
"description": "NodeSetNodeCount models the number of nodes expected in a given NodeSet.",
"properties": {
"name": {
"description": "Name of the Nodeset.",
"type": "string"
},
"nodeCount": {
"description": "NodeCount is the number of nodes, as computed by the autoscaler, expected in this NodeSet.",
"format": "int32",
"type": "integer"
}
},
"required": [
"name",
"nodeCount"
],
"type": "object"
},
"type": "array"
},
"resources": {
"description": "ResourcesSpecification holds the resource values common to all the nodeSets managed by a same autoscaling policy.\nOnly the resources managed by the autoscaling controller are saved in the Status.",
"properties": {
"limits": {
"additionalProperties": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
},
"description": "ResourceList is a set of (resource name, quantity) pairs.",
"type": "object"
},
"requests": {
"additionalProperties": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
"x-kubernetes-int-or-string": true
},
"description": "ResourceList is a set of (resource name, quantity) pairs.",
"type": "object"
}
},
"type": "object"
},
"state": {
"description": "PolicyStates may contain various messages regarding the current state of this autoscaling policy.",
"items": {
"properties": {
"messages": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"type": "string"
}
},
"required": [
"messages",
"type"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"name"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
},
"type": "object"
}