Schemas: Generate CRDs for core kinds (#62641)

Co-authored-by: sam boyer <sdboyer@grafana.com>
This commit is contained in:
Ryan McKinley
2023-02-01 09:08:26 -08:00
committed by GitHub
co-authored by sam boyer
parent b95eda045a
commit e70d623f90
34 changed files with 1748 additions and 34 deletions
+154
View File
@@ -0,0 +1,154 @@
package k8ssys
import (
"fmt"
"reflect"
"github.com/grafana/grafana/pkg/kindsys"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8schema "k8s.io/apimachinery/pkg/runtime/schema"
)
type Kind struct {
GrafanaKind kindsys.Kind
Object runtime.Object // singular type
ObjectList runtime.Object // list type
Schema apiextensionsv1.CustomResourceDefinition
}
// TODO this could probably be done in CUE/framework
func (crd Kind) GVK() k8schema.GroupVersionKind {
// TODO custom structured
props := crd.GrafanaKind.Props().(kindsys.CoreProperties)
gvk := k8schema.GroupVersionKind{
Group: fmt.Sprintf("%s.core.grafana.com", props.MachineName),
Kind: props.Name,
}
if props.Maturity.Less(kindsys.MaturityStable) {
gvk.Version = "v0-0alpha1"
} else {
gvk.Version = fmt.Sprintf("v%d-%d", props.CurrentVersion[0], props.CurrentVersion[1])
}
return gvk
}
// CustomResourceDefinitionList is the kubernetes-API-compliant representation of a list of CustomResourceDefinitions
type CustomResourceDefinitionList struct {
ListBase[CustomResourceDefinition]
}
// CustomResourceDefinition is the kubernetes-API-compliant representation of a Custom Resource Definition
type CustomResourceDefinition struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
Spec CustomResourceDefinitionSpec `json:"spec"`
}
// DeepCopyObject implements runtime.Object.
func (crd *CustomResourceDefinition) DeepCopyObject() runtime.Object {
return DeepCopyObject(crd)
}
// CustomResourceDefinitionSpec is the body or spec of a kubernetes Custom Resource Definition
type CustomResourceDefinitionSpec struct {
Group string `json:"group" yaml:"group"`
Versions []CustomResourceDefinitionSpecVersion `json:"versions" yaml:"versions"`
Names CustomResourceDefinitionSpecNames `json:"names" yaml:"names"`
Scope string `json:"scope" yaml:"scope"`
}
// CustomResourceDefinitionSpecVersion is the representation of a specific version of a CRD, as part of the overall spec
type CustomResourceDefinitionSpecVersion struct {
Name string `json:"name" yaml:"name"`
Served bool `json:"served" yaml:"served"`
Storage bool `json:"storage" yaml:"storage"`
Schema map[string]any `json:"schema" yaml:"schema"`
Subresources map[string]any `json:"subresources,omitempty" yaml:"subresources,omitempty"`
}
// CustomResourceDefinitionSpecNames is the struct representing the names (kind and plural) of a kubernetes CRD
type CustomResourceDefinitionSpecNames struct {
Kind string `json:"kind" yaml:"kind"`
Plural string `json:"plural" yaml:"plural"`
}
// Base is a struct which describes a basic CRD, and implements runtime.Object.
// SpecType should be the struct that represents the spec in the definition.
// It cannot be used on its own, as the name of the CRD in kubernetes must exactly match the name of struct.
// Instead, this struct can be used as a component of a new named struct, for examples:
//
// type MyCustomResource struct {
// crd.Base[MyCustomResourceSpec]
// }
type Base[SpecType any] struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SpecType `json:"spec"`
}
// DeepCopyObject is implemented for Base so it will implement runtime.Object.
// DeepCopyObject here just calls crd.DeepCopyObject on itself.
func (b *Base[T]) DeepCopyObject() runtime.Object {
return DeepCopyObject(b)
}
// ListBase is a struct which describes a list of CRDs, and implements runtime.Object.
// ItemType should be the CRD type being listed (NOT the model).
// It cannot be used on its own, as the struct name must exactly match `<name of kubernetes CRD>List`.
// Instead, this struct can be used as a component of a new named struct, for examples:
//
// type MyCustomResourceList struct {
// crd.Base[MyCustomResource]
// }
type ListBase[ItemType any] struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []ItemType `json:"items"`
}
// DeepCopyObject is implemented for Base so it will implement runtime.Object.
// DeepCopyObject here just calls crd.DeepCopyObject on itself.
func (b *ListBase[T]) DeepCopyObject() runtime.Object {
return DeepCopyObject(b)
}
// BaseStatus extends Base by including a Status subresource.
// This should be used if your kubernetes CRD includes the status subresource and you want to be able to view/modify it.
// Usage is identical to Base
type BaseStatus[SpecType, StatusType any] struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SpecType `json:"spec"`
Status StatusType `json:"status"`
}
// DeepCopyObject is implemented for Base so it will implement runtime.Object.
// DeepCopyObject here just calls crd.DeepCopyObject on itself.
func (b *BaseStatus[T, S]) DeepCopyObject() runtime.Object {
return DeepCopyObject(b)
}
// DeepCopyObject is an implementation of the receiver method required for implementing runtime.Object.
// It should be used in your own runtime.Object implementations if you do not wish to implement custom behavior.
// Example:
//
// func (c *CustomObject) DeepCopyObject() runtime.Object {
// return crd.DeepCopyObject(c)
// }
func DeepCopyObject(in any) runtime.Object {
val := reflect.ValueOf(in).Elem()
cpy := reflect.New(val.Type())
cpy.Elem().Set(val)
// Using the <obj>, <ok> for the type conversion ensures that it doesn't panic if it can't be converted
if obj, ok := cpy.Interface().(runtime.Object); ok {
return obj
}
// TODO: better return than nil?
return nil
}
+22
View File
@@ -100,6 +100,28 @@ Core: S=close({
lineage: { name: S.machineName }
lineageIsGroup: false
// crd contains properties specific to converting this kind to a Kubernetes CRD.
crd: {
// group is used as the CRD group name in the GVK.
group: "\(S.machineName).core.grafana.com"
// scope determines whether resources of this kind exist globally ("Cluster") or
// within Kubernetes namespaces.
scope: "Cluster" | *"Namespaced"
// dummySchema determines whether a dummy OpenAPI schema - where the schema is
// simply an empty, open object - should be generated for the kind.
//
// It is a goal that this option eventually be force dto false. Only set to
// true when Grafana's code generators produce OpenAPI that is rejected by
// Kubernetes' CRD validation.
dummySchema: bool | *false
// deepCopy determines whether a generic implementation of copying should be
// generated, or a passthrough call to a Go function.
// deepCopy: *"generic" | "passthrough"
}
})
nonEmptyString: string & strings.MinRunes(1)
+5
View File
@@ -23,6 +23,11 @@ type CommonProperties struct {
type CoreProperties struct {
CommonProperties
CurrentVersion thema.SyntacticVersion `json:"currentVersion"`
CRD struct {
Group string `json:"group"`
Scope string `json:"scope"`
DummySchema bool `json:"dummySchema"`
} `json:"crd"`
}
func (m CoreProperties) _private() {}
+70
View File
@@ -117,6 +117,11 @@
"apikey": {
"category": "core",
"codeowners": [],
"crd": {
"dummySchema": false,
"group": "",
"scope": ""
},
"currentVersion": [
0,
0
@@ -279,6 +284,11 @@
"grafana/plugins-platform-frontend",
"grafana/user-essentials"
],
"crd": {
"dummySchema": true,
"group": "dashboard.core.grafana.com",
"scope": "Namespaced"
},
"currentVersion": [
0,
0
@@ -369,6 +379,11 @@
"datasource": {
"category": "core",
"codeowners": [],
"crd": {
"dummySchema": false,
"group": "",
"scope": ""
},
"currentVersion": [
0,
0
@@ -478,6 +493,11 @@
"folder": {
"category": "core",
"codeowners": [],
"crd": {
"dummySchema": false,
"group": "",
"scope": ""
},
"currentVersion": [
0,
0
@@ -816,6 +836,11 @@
"grafana/plugins-platform-frontend",
"grafana/user-essentials"
],
"crd": {
"dummySchema": false,
"group": "librarypanel.core.grafana.com",
"scope": "Namespaced"
},
"currentVersion": [
0,
0
@@ -1181,6 +1206,11 @@
"grafana/plugins-platform-frontend",
"grafana/user-essentials"
],
"crd": {
"dummySchema": false,
"group": "playlist.core.grafana.com",
"scope": "Namespaced"
},
"currentVersion": [
0,
0
@@ -1252,6 +1282,11 @@
"grafana/plugins-platform-frontend",
"grafana/user-essentials"
],
"crd": {
"dummySchema": false,
"group": "preferences.core.grafana.com",
"scope": "Namespaced"
},
"currentVersion": [
0,
0
@@ -1323,6 +1358,11 @@
"grafana/plugins-platform-frontend",
"grafana/user-essentials"
],
"crd": {
"dummySchema": false,
"group": "publicdashboard.core.grafana.com",
"scope": "Namespaced"
},
"currentVersion": [
0,
0
@@ -1345,6 +1385,11 @@
"query": {
"category": "core",
"codeowners": [],
"crd": {
"dummySchema": false,
"group": "",
"scope": ""
},
"currentVersion": [
0,
0
@@ -1366,6 +1411,11 @@
"queryhistory": {
"category": "core",
"codeowners": [],
"crd": {
"dummySchema": false,
"group": "",
"scope": ""
},
"currentVersion": [
0,
0
@@ -1392,6 +1442,11 @@
"grafana/plugins-platform-frontend",
"grafana/user-essentials"
],
"crd": {
"dummySchema": false,
"group": "serviceaccount.core.grafana.com",
"scope": "Namespaced"
},
"currentVersion": [
0,
0
@@ -1513,6 +1568,11 @@
"grafana/plugins-platform-frontend",
"grafana/user-essentials"
],
"crd": {
"dummySchema": false,
"group": "team.core.grafana.com",
"scope": "Namespaced"
},
"currentVersion": [
0,
0
@@ -1651,6 +1711,11 @@
"thumb": {
"category": "core",
"codeowners": [],
"crd": {
"dummySchema": false,
"group": "",
"scope": ""
},
"currentVersion": [
0,
0
@@ -1694,6 +1759,11 @@
"user": {
"category": "core",
"codeowners": [],
"crd": {
"dummySchema": false,
"group": "",
"scope": ""
},
"currentVersion": [
0,
0