diff --git a/go.mod b/go.mod index 462a4a0d3d8..6afa54b858f 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,6 @@ go 1.19 // Also, use our fork with fixes for unimplemented methods (required for Go 1.16). replace github.com/denisenkom/go-mssqldb => github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 -// Override k8s.io/client-go outdated dependency, which is an indirect dependency of grafana/loki. -// It's also present on grafana/loki's go.mod so we'll need till it gets updated. -replace k8s.io/client-go => k8s.io/client-go v0.25.0 - replace github.com/russellhaering/goxmldsig@v1.1.0 => github.com/russellhaering/goxmldsig v1.1.1 // Avoid using v2.0.0+incompatible Redigo used by dependencies as the latest maintained branch of Redigo is v1. @@ -24,6 +20,17 @@ replace github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503 // contains openapi encoder fixes. remove ASAP replace cuelang.org/go => github.com/sdboyer/cue v0.5.0-beta.2.0.20221218111347-341999f48bdb +// For some insane reason, client-go seems to have a broken v12.0.0 tag on it that forces us to +// hoist a replace statement. +replace k8s.io/client-go => k8s.io/client-go v0.25.3 + +require ( + k8s.io/api v0.25.3 // indirect + k8s.io/apiextensions-apiserver v0.25.3 + k8s.io/apimachinery v0.25.3 + k8s.io/client-go v12.0.0+incompatible +) + require ( cloud.google.com/go/storage v1.28.1 cuelang.org/go v0.5.0-beta.2 @@ -257,7 +264,6 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 gocloud.dev v0.25.0 - k8s.io/client-go v12.0.0+incompatible // gets replaced with v0.25.0 ) require ( @@ -268,7 +274,6 @@ require ( github.com/grafana/thema v0.0.0-20230122235053-b4b6714dd1c9 github.com/hmarr/codeowners v1.1.1 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f - k8s.io/apimachinery v0.25.3 ) require ( @@ -296,6 +301,7 @@ require ( github.com/emicklei/go-restful/v3 v3.8.0 // indirect github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect github.com/envoyproxy/protoc-gen-validate v0.6.7 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -333,7 +339,6 @@ require ( golang.org/x/term v0.3.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/api v0.25.3 // indirect k8s.io/klog/v2 v2.80.0 // indirect k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect diff --git a/go.sum b/go.sum index 2542f23cbee..da128ce1443 100644 --- a/go.sum +++ b/go.sum @@ -770,8 +770,9 @@ github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUork github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= @@ -1839,6 +1840,7 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1850,6 +1852,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -2856,6 +2859,7 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= @@ -3180,9 +3184,10 @@ k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s= k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= -k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ= k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI= +k8s.io/apiextensions-apiserver v0.25.3 h1:bfI4KS31w2f9WM1KLGwnwuVlW3RSRPuIsfNF/3HzR0k= +k8s.io/apiextensions-apiserver v0.25.3/go.mod h1:ZJqwpCkxIx9itilmZek7JgfUAM0dnTsA48I4krPqRmo= k8s.io/apimachinery v0.0.0-20190809020650-423f5d784010/go.mod h1:Waf/xTS2FGRrgXCkO5FP3XxTOWh0qLf2QhL1qFZZ/R8= k8s.io/apimachinery v0.17.5/go.mod h1:ioIo1G/a+uONV7Tv+ZmCbMG1/a3kVw5YcDdncd8ugQ0= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= @@ -3190,14 +3195,13 @@ k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRp k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc= k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= -k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= +k8s.io/client-go v0.25.3 h1:oB4Dyl8d6UbfDHD8Bv8evKylzs3BXzzufLiO27xuPs0= +k8s.io/client-go v0.25.3/go.mod h1:t39LPczAIMwycjcXkVc+CB+PZV69jQuNx4um5ORDjQA= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index bcb8943df5f..832c30f0231 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -6,6 +6,8 @@ name: "Dashboard" maturity: "experimental" description: "A Grafana dashboard." +crd: dummySchema: true + lineage: seqs: [ { schemas: [ diff --git a/kinds/gen.go b/kinds/gen.go index 4cf85e53624..a8dae5bb021 100644 --- a/kinds/gen.go +++ b/kinds/gen.go @@ -42,6 +42,9 @@ func main() { codegen.BaseCoreRegistryJenny(filepath.Join("pkg", "registry", "corekind"), kindsys.GoCoreKindParentPath), codegen.LatestMajorsOrXJenny(kindsys.TSCoreKindParentPath, codegen.TSTypesJenny{}), codegen.TSVeneerIndexJenny(filepath.Join("packages", "grafana-schema", "src")), + codegen.CRDTypesJenny(kindsys.GoCoreKindParentPath), + codegen.YamlCRDJenny(kindsys.GoCoreKindParentPath), + codegen.CRDKindRegistryJenny(filepath.Join("pkg", "registry", "corecrd")), codegen.DocsJenny(filepath.Join("docs", "sources", "developers", "kinds", "core")), ) diff --git a/pkg/codegen/generators.go b/pkg/codegen/generators.go index 02e2f21f6f1..8f1771b4c17 100644 --- a/pkg/codegen/generators.go +++ b/pkg/codegen/generators.go @@ -34,21 +34,27 @@ func ForLatestSchema(k kindsys.Kind) SchemaForGen { // file. func SlashHeaderMapper(maingen string) codejen.FileMapper { return func(f codejen.File) (codejen.File, error) { + var leader string // Never inject on certain filetypes, it's never valid switch filepath.Ext(f.RelativePath) { - case ".json", ".yml", ".yaml", ".md": + case ".json", ".md": return f, nil + case ".yml", ".yaml": + leader = "#" default: - buf := new(bytes.Buffer) - if err := tmpls.Lookup("gen_header.tmpl").Execute(buf, tvars_gen_header{ - MainGenerator: maingen, - Using: f.From, - }); err != nil { - return codejen.File{}, fmt.Errorf("failed executing gen header template: %w", err) - } - fmt.Fprint(buf, string(f.Data)) - f.Data = buf.Bytes() + leader = "//" } + + buf := new(bytes.Buffer) + if err := tmpls.Lookup("gen_header.tmpl").Execute(buf, tvars_gen_header{ + MainGenerator: maingen, + Using: f.From, + Leader: leader, + }); err != nil { + return codejen.File{}, fmt.Errorf("failed executing gen header template: %w", err) + } + fmt.Fprint(buf, string(f.Data)) + f.Data = buf.Bytes() return f, nil } } diff --git a/pkg/codegen/jenny_basecorereg.go b/pkg/codegen/jenny_basecorereg.go index 99522ca060b..1a685c6444e 100644 --- a/pkg/codegen/jenny_basecorereg.go +++ b/pkg/codegen/jenny_basecorereg.go @@ -33,11 +33,21 @@ func (gen *genBaseRegistry) JennyName() string { } func (gen *genBaseRegistry) Generate(kinds ...kindsys.Kind) (*codejen.File, error) { + cores := make([]kindsys.Core, 0, len(kinds)) + for _, d := range kinds { + if corekind, is := d.(kindsys.Core); is { + cores = append(cores, corekind) + } + } + if len(cores) == 0 { + return nil, nil + } + buf := new(bytes.Buffer) if err := tmpls.Lookup("kind_registry.tmpl").Execute(buf, tvars_kind_registry{ PackageName: filepath.Base(gen.path), KindPackagePrefix: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", gen.kindrelroot)), - Kinds: kinds, + Kinds: cores, }); err != nil { return nil, fmt.Errorf("failed executing kind registry template: %w", err) } diff --git a/pkg/codegen/jenny_crd_reg.go b/pkg/codegen/jenny_crd_reg.go new file mode 100644 index 00000000000..56bd409b453 --- /dev/null +++ b/pkg/codegen/jenny_crd_reg.go @@ -0,0 +1,61 @@ +package codegen + +import ( + "bytes" + "fmt" + "path/filepath" + + "github.com/grafana/codejen" + "github.com/grafana/grafana/pkg/kindsys" +) + +// CRDKindRegistryJenny generates a static registry of the CRD representations +// of core Grafana kinds, layered on top of the publicly consumable generated +// registry in pkg/corekinds. +// +// Path should be the relative path to the directory that will contain the +// generated registry. +func CRDKindRegistryJenny(path string) ManyToOne { + return &crdregjenny{ + path: path, + } +} + +type crdregjenny struct { + path string +} + +func (j *crdregjenny) JennyName() string { + return "CRDKindRegistryJenny" +} + +func (j *crdregjenny) Generate(kinds ...kindsys.Kind) (*codejen.File, error) { + cores := make([]kindsys.Core, 0, len(kinds)) + for _, d := range kinds { + if corekind, is := d.(kindsys.Core); is { + cores = append(cores, corekind) + } + } + if len(cores) == 0 { + return nil, nil + } + + buf := new(bytes.Buffer) + if err := tmpls.Lookup("core_crd_registry.tmpl").Execute(buf, tvars_kind_registry{ + PackageName: "corecrd", + KindPackagePrefix: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", kindsys.GoCoreKindParentPath)), + Kinds: cores, + }); err != nil { + return nil, fmt.Errorf("failed executing core crd registry template: %w", err) + } + + b, err := postprocessGoFile(genGoFile{ + path: j.path, + in: buf.Bytes(), + }) + if err != nil { + return nil, err + } + + return codejen.NewFile(filepath.Join(j.path, "registry_gen.go"), b, j), nil +} diff --git a/pkg/codegen/jenny_crd_types.go b/pkg/codegen/jenny_crd_types.go new file mode 100644 index 00000000000..fe8945f5c8c --- /dev/null +++ b/pkg/codegen/jenny_crd_types.go @@ -0,0 +1,51 @@ +package codegen + +import ( + "bytes" + "fmt" + "path/filepath" + + "github.com/grafana/codejen" + "github.com/grafana/grafana/pkg/kindsys" +) + +// CRDTypesJenny generates the OpenAPI CRD representation for a core +// structured kind that is expected by Kubernetes controller machinery. +func CRDTypesJenny(path string) OneToOne { + return crdTypesJenny{ + parentpath: path, + } +} + +type crdTypesJenny struct { + parentpath string +} + +func (j crdTypesJenny) JennyName() string { + return "CRDTypesJenny" +} + +func (j crdTypesJenny) Generate(kind kindsys.Kind) (*codejen.File, error) { + _, isCore := kind.(kindsys.Core) + _, isCustom := kind.(kindsys.Core) + if !(isCore || isCustom) { + return nil, nil + } + + buf := new(bytes.Buffer) + if err := tmpls.Lookup("core_crd_types.tmpl").Execute(buf, kind); err != nil { + return nil, fmt.Errorf("failed executing crd types template: %w", err) + } + + name := kind.Props().Common().MachineName + path := filepath.Join(j.parentpath, name, "crd", name+"_crd_gen.go") + b, err := postprocessGoFile(genGoFile{ + path: path, + in: buf.Bytes(), + }) + if err != nil { + return nil, err + } + + return codejen.NewFile(path, b, j), nil +} diff --git a/pkg/codegen/jenny_crd_yaml.go b/pkg/codegen/jenny_crd_yaml.go new file mode 100644 index 00000000000..69d8538bd9d --- /dev/null +++ b/pkg/codegen/jenny_crd_yaml.go @@ -0,0 +1,214 @@ +package codegen + +import ( + "bytes" + "fmt" + "path/filepath" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/ast" + "cuelang.org/go/encoding/openapi" + cueyaml "cuelang.org/go/pkg/encoding/yaml" + "github.com/grafana/codejen" + "github.com/grafana/grafana/pkg/kindsys" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" + "github.com/grafana/thema" + goyaml "gopkg.in/yaml.v3" +) + +// TODO this jenny is quite sloppy, having been quickly adapted from app-sdk. It needs love + +// YamlCRDJenny generates a representation of a core structured kind in YAML CRD form. +func YamlCRDJenny(path string) OneToOne { + return yamlCRDJenny{ + parentpath: path, + } +} + +type yamlCRDJenny struct { + parentpath string +} + +func (yamlCRDJenny) JennyName() string { + return "YamlCRDJenny" +} + +func (j yamlCRDJenny) Generate(k kindsys.Kind) (*codejen.File, error) { + kind, is := k.(kindsys.Core) + if !is { + return nil, nil + } + + props := kind.Def().Properties + lin := kind.Lineage() + + // We need to go through every schema, as they all have to be defined in the CRD + sch, err := lin.Schema(thema.SV(0, 0)) + if err != nil { + return nil, err + } + + resource := customResourceDefinition{ + APIVersion: "apiextensions.k8s.io/v1", + Kind: "CustomResourceDefinition", + Metadata: customResourceDefinitionMetadata{ + Name: fmt.Sprintf("%s.%s", props.PluralMachineName, props.CRD.Group), + }, + Spec: k8ssys.CustomResourceDefinitionSpec{ + Group: props.CRD.Group, + Scope: props.CRD.Scope, + Names: k8ssys.CustomResourceDefinitionSpecNames{ + Kind: props.Name, + Plural: props.PluralMachineName, + }, + Versions: make([]k8ssys.CustomResourceDefinitionSpecVersion, 0), + }, + } + latest := lin.Latest().Version() + + for sch != nil { + oapi, err := generateOpenAPI(sch, props) + if err != nil { + return nil, err + } + + vstr := versionString(sch.Version()) + if props.Maturity.Less(kindsys.MaturityStable) { + vstr = "v0-0alpha1" + } + + ver, err := valueToCRDSpecVersion(oapi, vstr, sch.Version() == latest) + if err != nil { + return nil, err + } + if props.CRD.DummySchema { + ver.Schema = map[string]any{ + "openAPIV3Schema": map[string]any{ + "type": "object", + "properties": map[string]any{ + "spec": map[string]any{ + "type": "object", + "x-kubernetes-preserve-unknown-fields": true, + }, + }, + "required": []any{ + "spec", + }, + }, + } + } + + resource.Spec.Versions = append(resource.Spec.Versions, ver) + sch = sch.Successor() + } + contents, err := goyaml.Marshal(resource) + if err != nil { + return nil, err + } + if props.CRD.DummySchema { + // Add a comment header for those with dummy schema + b := new(bytes.Buffer) + fmt.Fprintf(b, "# This CRD is generated with an empty schema body because Grafana's\n# code generators currently produce OpenAPI that Kubernetes will not\n# accept, despite being valid.\n\n%s", string(contents)) + contents = b.Bytes() + } + + return codejen.NewFile(filepath.Join(j.parentpath, props.MachineName, "crd", props.MachineName+".crd.yml"), contents, j), nil +} + +// customResourceDefinition differs from k8ssys.CustomResourceDefinition in that it doesn't use the metav1 +// TypeMeta and ObjectMeta, as those do not contain YAML tags and get improperly serialized to YAML. +// Since we don't need to use it with the kubernetes go-client, we don't need the extra functionality attached. +// +//nolint:lll +type customResourceDefinition struct { + Kind string `json:"kind,omitempty" yaml:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` + Metadata customResourceDefinitionMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"` + Spec k8ssys.CustomResourceDefinitionSpec `json:"spec"` +} + +type customResourceDefinitionMetadata struct { + Name string `json:"name,omitempty" yaml:"name" protobuf:"bytes,1,opt,name=name"` + // TODO: other fields as necessary for codegen +} + +type cueOpenAPIEncoded struct { + Components cueOpenAPIEncodedComponents `json:"components"` +} + +type cueOpenAPIEncodedComponents struct { + Schemas map[string]any `json:"schemas"` +} + +func valueToCRDSpecVersion(str string, name string, stored bool) (k8ssys.CustomResourceDefinitionSpecVersion, error) { + // Decode the bytes back into an object where we can trim the openAPI clutter out + // and grab just the schema as a map[string]any (which is what k8s wants) + back := cueOpenAPIEncoded{} + err := goyaml.Unmarshal([]byte(str), &back) + if err != nil { + return k8ssys.CustomResourceDefinitionSpecVersion{}, err + } + if len(back.Components.Schemas) != 1 { + // There should only be one schema here... + // TODO: this may change with subresources--but subresources should have defined names + return k8ssys.CustomResourceDefinitionSpecVersion{}, fmt.Errorf("version %s has multiple schemas", name) + } + var def map[string]any + for _, v := range back.Components.Schemas { + ok := false + def, ok = v.(map[string]any) + if !ok { + return k8ssys.CustomResourceDefinitionSpecVersion{}, + fmt.Errorf("error generating openapi schema - generated schema has invalid type") + } + } + + return k8ssys.CustomResourceDefinitionSpecVersion{ + Name: name, + Served: true, + Storage: stored, + Schema: map[string]any{ + "openAPIV3Schema": map[string]any{ + "properties": map[string]any{ + "spec": def, + }, + "required": []any{ + "spec", + }, + "type": "object", + }, + }, + }, nil +} + +func versionString(version thema.SyntacticVersion) string { + return fmt.Sprintf("v%d-%d", version[0], version[1]) +} + +// Hoisting this out of thema until we resolve the proper approach there +func generateOpenAPI(sch thema.Schema, props kindsys.CoreProperties) (string, error) { + ctx := sch.Underlying().Context() + v := ctx.CompileString(fmt.Sprintf("#%s: _", props.Name)) + defpath := cue.MakePath(cue.Def(props.Name)) + defsch := v.FillPath(defpath, sch.Underlying()) + + cfg := &openapi.Config{ + NameFunc: func(v cue.Value, path cue.Path) string { + if path.String() == defpath.String() { + return props.Name + } + return "" + }, + Info: ast.NewStruct( // doesn't matter, we're throwing it away + "title", ast.NewString(props.Name), + "version", ast.NewString("0.0"), + ), + } + + f, err := openapi.Generate(defsch, cfg) + if err != nil { + return "", err + } + + return cueyaml.Marshal(sch.Lineage().Runtime().Context().BuildFile(f)) +} diff --git a/pkg/codegen/jenny_gotypes.go b/pkg/codegen/jenny_gotypes.go index 57061ab4095..d1d83364a57 100644 --- a/pkg/codegen/jenny_gotypes.go +++ b/pkg/codegen/jenny_gotypes.go @@ -8,7 +8,7 @@ import ( "github.com/grafana/thema/encoding/openapi" ) -// GoTypesJenny creates a [OneToOne] that produces Go types for the provided +// GoTypesJenny is a [OneToOne] that produces Go types for the provided // [thema.Schema]. type GoTypesJenny struct { ApplyFuncs []dstutil.ApplyFunc diff --git a/pkg/codegen/tmpl.go b/pkg/codegen/tmpl.go index 7ca6f1b797a..dfe43657b1b 100644 --- a/pkg/codegen/tmpl.go +++ b/pkg/codegen/tmpl.go @@ -36,12 +36,12 @@ type ( MainGenerator string Using []codejen.NamedJenny From string + Leader string } tvars_kind_registry struct { - // Header tvars_autogen_header PackageName string KindPackagePrefix string - Kinds []kindsys.Kind + Kinds []kindsys.Core } tvars_coremodel_imports struct { PackageName string diff --git a/pkg/codegen/tmpl/core_crd_registry.tmpl b/pkg/codegen/tmpl/core_crd_registry.tmpl new file mode 100644 index 00000000000..a65b77378ea --- /dev/null +++ b/pkg/codegen/tmpl/core_crd_registry.tmpl @@ -0,0 +1,66 @@ +package {{ .PackageName }} + +import ( + "encoding/json" + "fmt" + + {{range .Kinds }} + {{ .Props.MachineName }} "{{ $.KindPackagePrefix }}/{{ .Props.MachineName }}/crd"{{end}} + "github.com/grafana/grafana/pkg/kindsys" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" + "github.com/grafana/grafana/pkg/registry/corekind" + "gopkg.in/yaml.v3" +) + +// Registry is a list of all of Grafana's core structured kinds, wrapped in a +// standard [k8ssys.CRD] interface that makes them usable for interactions +// with certain Kubernetes controller and apimachinery libraries. +// +// There are two access methods: individually via literal named methods, or as +// a slice returned from All() method. +// +// Prefer the individual named methods for use cases where the particular kind(s) +// that are needed are known to the caller. Prefer All() when performing operations +// generically across all kinds. +type Registry struct { + all [{{ len .Kinds }}]k8ssys.Kind +} + +{{range $i, $k := .Kinds }} +// {{ .Props.Name }} returns the [k8ssys.Kind] instance for the {{ .Props.Name }} kind. +func (r *Registry) {{ .Props.Name }}() k8ssys.Kind { + return r.all[{{ $i }}] +} +{{end}} + +func doNewRegistry(breg *corekind.Base) *Registry { + var err error + var b []byte + var kk k8ssys.Kind + reg := &Registry{} + +{{range $i, $k := .Kinds }} + kk = k8ssys.Kind{ + GrafanaKind: breg.{{ $k.Props.Name }}(), + Object: &{{ $k.Props.MachineName }}.{{ $k.Props.Name }}{}, + ObjectList: &{{ $k.Props.MachineName }}.{{ $k.Props.Name }}List{}, + } + // TODO Having the committed form on disk in YAML is worth doing this for now...but fix this silliness + map{{ $i }} := make(map[string]any) + err = yaml.Unmarshal({{ $k.Props.MachineName }}.CRDYaml, map{{ $i }}) + if err != nil { + panic(fmt.Sprintf("generated CRD YAML for {{ $k.Props.Name }} failed to unmarshal: %s", err)) + } + b, err = json.Marshal(map{{ $i }}) + if err != nil { + panic(fmt.Sprintf("could not re-marshal CRD JSON for {{ $k.Props.Name }}: %s", err)) + } + err = json.Unmarshal(b, &kk.Schema) + if err != nil { + panic(fmt.Sprintf("could not unmarshal CRD JSON for {{ $k.Props.Name }}: %s", err)) + } + reg.all[{{ $i }}] = kk +{{end}} + + return reg +} diff --git a/pkg/codegen/tmpl/core_crd_types.tmpl b/pkg/codegen/tmpl/core_crd_types.tmpl new file mode 100644 index 00000000000..0e6614c1853 --- /dev/null +++ b/pkg/codegen/tmpl/core_crd_types.tmpl @@ -0,0 +1,25 @@ +package crd + +import ( + _ "embed" + + "github.com/grafana/grafana/pkg/kinds/{{ .Props.MachineName }}" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" +) + +// The CRD YAML representation of the {{ .Props.Name }} kind. +// +//go:embed {{ .Props.MachineName }}.crd.yml +var CRDYaml []byte + +// {{ .Props.Name }} is the Go CRD representation of a single {{ .Props.Name }} object. +// It implements [runtime.Object], and is used in k8s scheme construction. +type {{ .Props.Name }} struct { + k8ssys.Base[{{ .Props.MachineName }}.{{ .Props.Name }}] +} + +// {{ .Props.Name }}List is the Go CRD representation of a list {{ .Props.Name }} objects. +// It implements [runtime.Object], and is used in k8s scheme construction. +type {{ .Props.Name }}List struct { + k8ssys.ListBase[{{ .Props.MachineName }}.{{ .Props.Name }}] +} diff --git a/pkg/codegen/tmpl/gen_header.tmpl b/pkg/codegen/tmpl/gen_header.tmpl index 2cc38283a2b..969e61caa7e 100644 --- a/pkg/codegen/tmpl/gen_header.tmpl +++ b/pkg/codegen/tmpl/gen_header.tmpl @@ -1,11 +1,11 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// {{ .MainGenerator }} -// Using jennies: +{{ .Leader }} Code generated - EDITING IS FUTILE. DO NOT EDIT. +{{ .Leader }} +{{ .Leader }} Generated by: +{{ .Leader }} {{ .MainGenerator }} +{{ .Leader }} Using jennies: {{- range .Using }} -// {{ .JennyName }} +{{ $.Leader }} {{ .JennyName }} {{- end }} -// -// Run 'make gen-cue' from repository root to regenerate. +{{ .Leader }} +{{ .Leader }} Run 'make gen-cue' from repository root to regenerate. diff --git a/pkg/kinds/dashboard/crd/dashboard.crd.yml b/pkg/kinds/dashboard/crd/dashboard.crd.yml new file mode 100644 index 00000000000..69bb59bbcaf --- /dev/null +++ b/pkg/kinds/dashboard/crd/dashboard.crd.yml @@ -0,0 +1,36 @@ +# Code generated - EDITING IS FUTILE. DO NOT EDIT. +# +# Generated by: +# kinds/gen.go +# Using jennies: +# YamlCRDJenny +# +# Run 'make gen-cue' from repository root to regenerate. + +# This CRD is generated with an empty schema body because Grafana's +# code generators currently produce OpenAPI that Kubernetes will not +# accept, despite being valid. + +kind: CustomResourceDefinition +apiVersion: apiextensions.k8s.io/v1 +metadata: + name: dashboards.dashboard.core.grafana.com +spec: + group: dashboard.core.grafana.com + versions: + - name: v0-0alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + properties: + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - spec + type: object + names: + kind: Dashboard + plural: dashboards + scope: Namespaced diff --git a/pkg/kinds/dashboard/crd/dashboard_crd_gen.go b/pkg/kinds/dashboard/crd/dashboard_crd_gen.go new file mode 100644 index 00000000000..d8d1970b22b --- /dev/null +++ b/pkg/kinds/dashboard/crd/dashboard_crd_gen.go @@ -0,0 +1,34 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CRDTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package crd + +import ( + _ "embed" + + "github.com/grafana/grafana/pkg/kinds/dashboard" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" +) + +// The CRD YAML representation of the Dashboard kind. +// +//go:embed dashboard.crd.yml +var CRDYaml []byte + +// Dashboard is the Go CRD representation of a single Dashboard object. +// It implements [runtime.Object], and is used in k8s scheme construction. +type Dashboard struct { + k8ssys.Base[dashboard.Dashboard] +} + +// DashboardList is the Go CRD representation of a list Dashboard objects. +// It implements [runtime.Object], and is used in k8s scheme construction. +type DashboardList struct { + k8ssys.ListBase[dashboard.Dashboard] +} diff --git a/pkg/kinds/librarypanel/crd/librarypanel.crd.yml b/pkg/kinds/librarypanel/crd/librarypanel.crd.yml new file mode 100644 index 00000000000..a3a31f0fe21 --- /dev/null +++ b/pkg/kinds/librarypanel/crd/librarypanel.crd.yml @@ -0,0 +1,120 @@ +# Code generated - EDITING IS FUTILE. DO NOT EDIT. +# +# Generated by: +# kinds/gen.go +# Using jennies: +# YamlCRDJenny +# +# Run 'make gen-cue' from repository root to regenerate. + +kind: CustomResourceDefinition +apiVersion: apiextensions.k8s.io/v1 +metadata: + name: librarypanels.librarypanel.core.grafana.com +spec: + group: librarypanel.core.grafana.com + versions: + - name: v0-0alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + description: + description: Panel description + type: string + folderUid: + description: Folder UID + type: string + meta: + description: Object storage metadata + properties: + connectedDashboards: + format: int64 + type: integer + created: + type: string + createdBy: + properties: + avatarUrl: + type: string + id: + format: int64 + type: integer + name: + type: string + required: + - id + - name + - avatarUrl + type: object + folderName: + type: string + folderUid: + type: string + updated: + type: string + updatedBy: + properties: + avatarUrl: + type: string + id: + format: int64 + type: integer + name: + type: string + required: + - id + - name + - avatarUrl + type: object + required: + - folderName + - folderUid + - connectedDashboards + - created + - updated + - createdBy + - updatedBy + type: object + model: + description: |- + TODO: should be the same panel schema defined in dashboard + Typescript: Omit; + type: object + name: + description: Panel name (also saved in the model) + minLength: 1 + type: string + schemaVersion: + description: Dashboard version when this was saved (zero if unknown) + maximum: 65535 + minimum: 0 + type: integer + type: + description: The panel type (from inside the model) + minLength: 1 + type: string + uid: + description: Library element UID + type: string + version: + description: panel version, incremented each time the dashboard is updated. + format: int64 + type: integer + required: + - uid + - name + - type + - version + - model + type: object + required: + - spec + type: object + names: + kind: LibraryPanel + plural: librarypanels + scope: Namespaced diff --git a/pkg/kinds/librarypanel/crd/librarypanel_crd_gen.go b/pkg/kinds/librarypanel/crd/librarypanel_crd_gen.go new file mode 100644 index 00000000000..1d6975a33d6 --- /dev/null +++ b/pkg/kinds/librarypanel/crd/librarypanel_crd_gen.go @@ -0,0 +1,34 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CRDTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package crd + +import ( + _ "embed" + + "github.com/grafana/grafana/pkg/kinds/librarypanel" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" +) + +// The CRD YAML representation of the LibraryPanel kind. +// +//go:embed librarypanel.crd.yml +var CRDYaml []byte + +// LibraryPanel is the Go CRD representation of a single LibraryPanel object. +// It implements [runtime.Object], and is used in k8s scheme construction. +type LibraryPanel struct { + k8ssys.Base[librarypanel.LibraryPanel] +} + +// LibraryPanelList is the Go CRD representation of a list LibraryPanel objects. +// It implements [runtime.Object], and is used in k8s scheme construction. +type LibraryPanelList struct { + k8ssys.ListBase[librarypanel.LibraryPanel] +} diff --git a/pkg/kinds/playlist/crd/playlist.crd.yml b/pkg/kinds/playlist/crd/playlist.crd.yml new file mode 100644 index 00000000000..192d8945ce8 --- /dev/null +++ b/pkg/kinds/playlist/crd/playlist.crd.yml @@ -0,0 +1,82 @@ +# Code generated - EDITING IS FUTILE. DO NOT EDIT. +# +# Generated by: +# kinds/gen.go +# Using jennies: +# YamlCRDJenny +# +# Run 'make gen-cue' from repository root to regenerate. + +kind: CustomResourceDefinition +apiVersion: apiextensions.k8s.io/v1 +metadata: + name: playlists.playlist.core.grafana.com +spec: + group: playlist.core.grafana.com + versions: + - name: v0-0alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + interval: + default: 5m + description: |- + Interval sets the time between switching views in a playlist. + FIXME: Is this based on a standardized format or what options are available? Can datemath be used? + type: string + items: + description: |- + The ordered list of items that the playlist will iterate over. + FIXME! This should not be optional, but changing it makes the godegen awkward + items: + properties: + title: + description: Title is an unused property -- it will be removed in the future + type: string + type: + description: Type of the item. + enum: + - dashboard_by_uid + - dashboard_by_id + - dashboard_by_tag + type: string + value: + description: |- + Value depends on type and describes the playlist item. + + - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This + is not portable as the numerical identifier is non-deterministic between different instances. + Will be replaced by dashboard_by_uid in the future. (deprecated) + - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All + dashboards behind the tag will be added to the playlist. + - dashboard_by_uid: The value is the dashboard UID + type: string + required: + - type + - value + type: object + type: array + name: + description: Name of the playlist. + type: string + uid: + description: |- + Unique playlist identifier. Generated on creation, either by the + creator of the playlist of by the application. + type: string + required: + - uid + - name + - interval + type: object + required: + - spec + type: object + names: + kind: Playlist + plural: playlists + scope: Namespaced diff --git a/pkg/kinds/playlist/crd/playlist_crd_gen.go b/pkg/kinds/playlist/crd/playlist_crd_gen.go new file mode 100644 index 00000000000..fc5c93217e4 --- /dev/null +++ b/pkg/kinds/playlist/crd/playlist_crd_gen.go @@ -0,0 +1,34 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CRDTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package crd + +import ( + _ "embed" + + "github.com/grafana/grafana/pkg/kinds/playlist" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" +) + +// The CRD YAML representation of the Playlist kind. +// +//go:embed playlist.crd.yml +var CRDYaml []byte + +// Playlist is the Go CRD representation of a single Playlist object. +// It implements [runtime.Object], and is used in k8s scheme construction. +type Playlist struct { + k8ssys.Base[playlist.Playlist] +} + +// PlaylistList is the Go CRD representation of a list Playlist objects. +// It implements [runtime.Object], and is used in k8s scheme construction. +type PlaylistList struct { + k8ssys.ListBase[playlist.Playlist] +} diff --git a/pkg/kinds/preferences/crd/preferences.crd.yml b/pkg/kinds/preferences/crd/preferences.crd.yml new file mode 100644 index 00000000000..5afabc84e8c --- /dev/null +++ b/pkg/kinds/preferences/crd/preferences.crd.yml @@ -0,0 +1,56 @@ +# Code generated - EDITING IS FUTILE. DO NOT EDIT. +# +# Generated by: +# kinds/gen.go +# Using jennies: +# YamlCRDJenny +# +# Run 'make gen-cue' from repository root to regenerate. + +kind: CustomResourceDefinition +apiVersion: apiextensions.k8s.io/v1 +metadata: + name: preferencess.preferences.core.grafana.com +spec: + group: preferences.core.grafana.com + versions: + - name: v0-0alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + homeDashboardUID: + description: UID for the home dashboard + type: string + language: + description: Selected language (beta) + type: string + queryHistory: + description: Explore query history preferences + properties: + homeTab: + description: 'one of: '''' | ''query'' | ''starred'';' + type: string + type: object + theme: + description: light, dark, empty is default + type: string + timezone: + description: |- + The timezone selection + TODO: this should use the timezone defined in common + type: string + weekStart: + description: day of the week (sunday, monday, etc) + type: string + type: object + required: + - spec + type: object + names: + kind: Preferences + plural: preferencess + scope: Namespaced diff --git a/pkg/kinds/preferences/crd/preferences_crd_gen.go b/pkg/kinds/preferences/crd/preferences_crd_gen.go new file mode 100644 index 00000000000..d2debdab9dd --- /dev/null +++ b/pkg/kinds/preferences/crd/preferences_crd_gen.go @@ -0,0 +1,34 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CRDTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package crd + +import ( + _ "embed" + + "github.com/grafana/grafana/pkg/kinds/preferences" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" +) + +// The CRD YAML representation of the Preferences kind. +// +//go:embed preferences.crd.yml +var CRDYaml []byte + +// Preferences is the Go CRD representation of a single Preferences object. +// It implements [runtime.Object], and is used in k8s scheme construction. +type Preferences struct { + k8ssys.Base[preferences.Preferences] +} + +// PreferencesList is the Go CRD representation of a list Preferences objects. +// It implements [runtime.Object], and is used in k8s scheme construction. +type PreferencesList struct { + k8ssys.ListBase[preferences.Preferences] +} diff --git a/pkg/kinds/publicdashboard/crd/publicdashboard.crd.yml b/pkg/kinds/publicdashboard/crd/publicdashboard.crd.yml new file mode 100644 index 00000000000..70fb2292b93 --- /dev/null +++ b/pkg/kinds/publicdashboard/crd/publicdashboard.crd.yml @@ -0,0 +1,56 @@ +# Code generated - EDITING IS FUTILE. DO NOT EDIT. +# +# Generated by: +# kinds/gen.go +# Using jennies: +# YamlCRDJenny +# +# Run 'make gen-cue' from repository root to regenerate. + +kind: CustomResourceDefinition +apiVersion: apiextensions.k8s.io/v1 +metadata: + name: publicdashboards.publicdashboard.core.grafana.com +spec: + group: publicdashboard.core.grafana.com + versions: + - name: v0-0alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + accessToken: + description: Unique public access token + type: string + annotationsEnabled: + description: Flag that indicates if annotations are enabled + type: boolean + dashboardUid: + description: Dashboard unique identifier referenced by this public dashboard + type: string + isEnabled: + description: Flag that indicates if the public dashboard is enabled + type: boolean + timeSelectionEnabled: + description: Flag that indicates if the time range picker is enabled + type: boolean + uid: + description: Unique public dashboard identifier + type: string + required: + - uid + - dashboardUid + - isEnabled + - annotationsEnabled + - timeSelectionEnabled + type: object + required: + - spec + type: object + names: + kind: PublicDashboard + plural: publicdashboards + scope: Namespaced diff --git a/pkg/kinds/publicdashboard/crd/publicdashboard_crd_gen.go b/pkg/kinds/publicdashboard/crd/publicdashboard_crd_gen.go new file mode 100644 index 00000000000..b0fcbd89a33 --- /dev/null +++ b/pkg/kinds/publicdashboard/crd/publicdashboard_crd_gen.go @@ -0,0 +1,34 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CRDTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package crd + +import ( + _ "embed" + + "github.com/grafana/grafana/pkg/kinds/publicdashboard" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" +) + +// The CRD YAML representation of the PublicDashboard kind. +// +//go:embed publicdashboard.crd.yml +var CRDYaml []byte + +// PublicDashboard is the Go CRD representation of a single PublicDashboard object. +// It implements [runtime.Object], and is used in k8s scheme construction. +type PublicDashboard struct { + k8ssys.Base[publicdashboard.PublicDashboard] +} + +// PublicDashboardList is the Go CRD representation of a list PublicDashboard objects. +// It implements [runtime.Object], and is used in k8s scheme construction. +type PublicDashboardList struct { + k8ssys.ListBase[publicdashboard.PublicDashboard] +} diff --git a/pkg/kinds/serviceaccount/crd/serviceaccount.crd.yml b/pkg/kinds/serviceaccount/crd/serviceaccount.crd.yml new file mode 100644 index 00000000000..c38aad58ab0 --- /dev/null +++ b/pkg/kinds/serviceaccount/crd/serviceaccount.crd.yml @@ -0,0 +1,94 @@ +# Code generated - EDITING IS FUTILE. DO NOT EDIT. +# +# Generated by: +# kinds/gen.go +# Using jennies: +# YamlCRDJenny +# +# Run 'make gen-cue' from repository root to regenerate. + +kind: CustomResourceDefinition +apiVersion: apiextensions.k8s.io/v1 +metadata: + name: serviceaccounts.serviceaccount.core.grafana.com +spec: + group: serviceaccount.core.grafana.com + versions: + - name: v0-0alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + accessControl: + additionalProperties: + type: boolean + description: AccessControl metadata associated with a given resource. + type: object + avatarUrl: + description: |- + AvatarUrl is the service account's avatar URL. It allows the frontend to display a picture in front + of the service account. + type: string + created: + description: Created indicates when the service account was created. + format: int64 + type: integer + id: + description: ID is the unique identifier of the service account in the database. + format: int64 + type: integer + isDisabled: + description: IsDisabled indicates if the service account is disabled. + type: boolean + login: + description: Login of the service account. + type: string + name: + description: Name of the service account. + type: string + orgId: + description: OrgId is the ID of an organisation the service account belongs to. + format: int64 + type: integer + role: + description: Role is the Grafana organization role of the service account which can be 'Viewer', 'Editor', 'Admin'. + enum: + - Admin + - Editor + - Viewer + type: string + teams: + description: Teams is a list of teams the service account belongs to. + items: + type: string + type: array + tokens: + description: |- + Tokens is the number of active tokens for the service account. + Tokens are used to authenticate the service account against Grafana. + format: int64 + type: integer + updated: + description: Updated indicates when the service account was updated. + format: int64 + type: integer + required: + - id + - orgId + - name + - login + - isDisabled + - role + - tokens + - avatarUrl + type: object + required: + - spec + type: object + names: + kind: ServiceAccount + plural: serviceaccounts + scope: Namespaced diff --git a/pkg/kinds/serviceaccount/crd/serviceaccount_crd_gen.go b/pkg/kinds/serviceaccount/crd/serviceaccount_crd_gen.go new file mode 100644 index 00000000000..f9ef3ae491c --- /dev/null +++ b/pkg/kinds/serviceaccount/crd/serviceaccount_crd_gen.go @@ -0,0 +1,34 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CRDTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package crd + +import ( + _ "embed" + + "github.com/grafana/grafana/pkg/kinds/serviceaccount" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" +) + +// The CRD YAML representation of the ServiceAccount kind. +// +//go:embed serviceaccount.crd.yml +var CRDYaml []byte + +// ServiceAccount is the Go CRD representation of a single ServiceAccount object. +// It implements [runtime.Object], and is used in k8s scheme construction. +type ServiceAccount struct { + k8ssys.Base[serviceaccount.ServiceAccount] +} + +// ServiceAccountList is the Go CRD representation of a list ServiceAccount objects. +// It implements [runtime.Object], and is used in k8s scheme construction. +type ServiceAccountList struct { + k8ssys.ListBase[serviceaccount.ServiceAccount] +} diff --git a/pkg/kinds/team/crd/team.crd.yml b/pkg/kinds/team/crd/team.crd.yml new file mode 100644 index 00000000000..d8d81f4dc7e --- /dev/null +++ b/pkg/kinds/team/crd/team.crd.yml @@ -0,0 +1,77 @@ +# Code generated - EDITING IS FUTILE. DO NOT EDIT. +# +# Generated by: +# kinds/gen.go +# Using jennies: +# YamlCRDJenny +# +# Run 'make gen-cue' from repository root to regenerate. + +kind: CustomResourceDefinition +apiVersion: apiextensions.k8s.io/v1 +metadata: + name: teams.team.core.grafana.com +spec: + group: team.core.grafana.com + versions: + - name: v0-0alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + properties: + spec: + properties: + accessControl: + additionalProperties: + type: boolean + description: AccessControl metadata associated with a given resource. + type: object + avatarUrl: + description: AvatarUrl is the team's avatar URL. + type: string + created: + description: Created indicates when the team was created. + format: int64 + type: integer + email: + description: Email of the team. + type: string + memberCount: + description: MemberCount is the number of the team members. + format: int64 + type: integer + name: + description: Name of the team. + type: string + orgId: + description: OrgId is the ID of an organisation the team belongs to. + format: int64 + type: integer + permission: + description: TODO - it seems it's a team_member.permission, unlikely it should belong to the team kind + enum: + - 0 + - 1 + - 2 + - 4 + type: integer + updated: + description: Updated indicates when the team was updated. + format: int64 + type: integer + required: + - orgId + - name + - memberCount + - permission + - created + - updated + type: object + required: + - spec + type: object + names: + kind: Team + plural: teams + scope: Namespaced diff --git a/pkg/kinds/team/crd/team_crd_gen.go b/pkg/kinds/team/crd/team_crd_gen.go new file mode 100644 index 00000000000..807a87ddba8 --- /dev/null +++ b/pkg/kinds/team/crd/team_crd_gen.go @@ -0,0 +1,34 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CRDTypesJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package crd + +import ( + _ "embed" + + "github.com/grafana/grafana/pkg/kinds/team" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" +) + +// The CRD YAML representation of the Team kind. +// +//go:embed team.crd.yml +var CRDYaml []byte + +// Team is the Go CRD representation of a single Team object. +// It implements [runtime.Object], and is used in k8s scheme construction. +type Team struct { + k8ssys.Base[team.Team] +} + +// TeamList is the Go CRD representation of a list Team objects. +// It implements [runtime.Object], and is used in k8s scheme construction. +type TeamList struct { + k8ssys.ListBase[team.Team] +} diff --git a/pkg/kindsys/k8ssys/crd.go b/pkg/kindsys/k8ssys/crd.go new file mode 100644 index 00000000000..ffbe469d9c1 --- /dev/null +++ b/pkg/kindsys/k8ssys/crd.go @@ -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 `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 , 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 +} diff --git a/pkg/kindsys/kindcats.cue b/pkg/kindsys/kindcats.cue index afc728b30fd..2eb4d2d1986 100644 --- a/pkg/kindsys/kindcats.cue +++ b/pkg/kindsys/kindcats.cue @@ -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) diff --git a/pkg/kindsys/props.go b/pkg/kindsys/props.go index 91e12adacfb..a314fcc46cf 100644 --- a/pkg/kindsys/props.go +++ b/pkg/kindsys/props.go @@ -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() {} diff --git a/pkg/kindsys/report.json b/pkg/kindsys/report.json index 38f2168cadb..e5cd4f7ba01 100644 --- a/pkg/kindsys/report.json +++ b/pkg/kindsys/report.json @@ -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 diff --git a/pkg/registry/corecrd/registry.go b/pkg/registry/corecrd/registry.go new file mode 100644 index 00000000000..8c81e72c1b3 --- /dev/null +++ b/pkg/registry/corecrd/registry.go @@ -0,0 +1,26 @@ +package corecrd + +import ( + "github.com/grafana/grafana/pkg/kindsys/k8ssys" + "github.com/grafana/grafana/pkg/registry/corekind" + "github.com/grafana/thema" +) + +// New constructs a new [Registry]. +// +// All calling code within grafana/grafana is expected to use Grafana's +// singleton [thema.Runtime], returned from [cuectx.GrafanaThemaRuntime]. If nil +// is passed, the singleton will be used. +func New(rt *thema.Runtime) *Registry { + breg := corekind.NewBase(rt) + return doNewRegistry(breg) +} + +// All returns a slice of all core Grafana CRDs in the registry. +// +// The returned slice is guaranteed to be alphabetically sorted by kind name. +func (r *Registry) All() []k8ssys.Kind { + all := make([]k8ssys.Kind, len(r.all)) + copy(all, r.all[:]) + return all +} diff --git a/pkg/registry/corecrd/registry_gen.go b/pkg/registry/corecrd/registry_gen.go new file mode 100644 index 00000000000..b7dd1d07a17 --- /dev/null +++ b/pkg/registry/corecrd/registry_gen.go @@ -0,0 +1,231 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CRDKindRegistryJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package corecrd + +import ( + "encoding/json" + "fmt" + + dashboard "github.com/grafana/grafana/pkg/kinds/dashboard/crd" + librarypanel "github.com/grafana/grafana/pkg/kinds/librarypanel/crd" + playlist "github.com/grafana/grafana/pkg/kinds/playlist/crd" + preferences "github.com/grafana/grafana/pkg/kinds/preferences/crd" + publicdashboard "github.com/grafana/grafana/pkg/kinds/publicdashboard/crd" + serviceaccount "github.com/grafana/grafana/pkg/kinds/serviceaccount/crd" + team "github.com/grafana/grafana/pkg/kinds/team/crd" + "github.com/grafana/grafana/pkg/kindsys/k8ssys" + "github.com/grafana/grafana/pkg/registry/corekind" + "gopkg.in/yaml.v3" +) + +// Registry is a list of all of Grafana's core structured kinds, wrapped in a +// standard [k8ssys.CRD] interface that makes them usable for interactions +// with certain Kubernetes controller and apimachinery libraries. +// +// There are two access methods: individually via literal named methods, or as +// a slice returned from All() method. +// +// Prefer the individual named methods for use cases where the particular kind(s) +// that are needed are known to the caller. Prefer All() when performing operations +// generically across all kinds. +type Registry struct { + all [7]k8ssys.Kind +} + +// Dashboard returns the [k8ssys.Kind] instance for the Dashboard kind. +func (r *Registry) Dashboard() k8ssys.Kind { + return r.all[0] +} + +// LibraryPanel returns the [k8ssys.Kind] instance for the LibraryPanel kind. +func (r *Registry) LibraryPanel() k8ssys.Kind { + return r.all[1] +} + +// Playlist returns the [k8ssys.Kind] instance for the Playlist kind. +func (r *Registry) Playlist() k8ssys.Kind { + return r.all[2] +} + +// Preferences returns the [k8ssys.Kind] instance for the Preferences kind. +func (r *Registry) Preferences() k8ssys.Kind { + return r.all[3] +} + +// PublicDashboard returns the [k8ssys.Kind] instance for the PublicDashboard kind. +func (r *Registry) PublicDashboard() k8ssys.Kind { + return r.all[4] +} + +// ServiceAccount returns the [k8ssys.Kind] instance for the ServiceAccount kind. +func (r *Registry) ServiceAccount() k8ssys.Kind { + return r.all[5] +} + +// Team returns the [k8ssys.Kind] instance for the Team kind. +func (r *Registry) Team() k8ssys.Kind { + return r.all[6] +} + +func doNewRegistry(breg *corekind.Base) *Registry { + var err error + var b []byte + var kk k8ssys.Kind + reg := &Registry{} + + kk = k8ssys.Kind{ + GrafanaKind: breg.Dashboard(), + Object: &dashboard.Dashboard{}, + ObjectList: &dashboard.DashboardList{}, + } + // TODO Having the committed form on disk in YAML is worth doing this for now...but fix this silliness + map0 := make(map[string]any) + err = yaml.Unmarshal(dashboard.CRDYaml, map0) + if err != nil { + panic(fmt.Sprintf("generated CRD YAML for Dashboard failed to unmarshal: %s", err)) + } + b, err = json.Marshal(map0) + if err != nil { + panic(fmt.Sprintf("could not re-marshal CRD JSON for Dashboard: %s", err)) + } + err = json.Unmarshal(b, &kk.Schema) + if err != nil { + panic(fmt.Sprintf("could not unmarshal CRD JSON for Dashboard: %s", err)) + } + reg.all[0] = kk + + kk = k8ssys.Kind{ + GrafanaKind: breg.LibraryPanel(), + Object: &librarypanel.LibraryPanel{}, + ObjectList: &librarypanel.LibraryPanelList{}, + } + // TODO Having the committed form on disk in YAML is worth doing this for now...but fix this silliness + map1 := make(map[string]any) + err = yaml.Unmarshal(librarypanel.CRDYaml, map1) + if err != nil { + panic(fmt.Sprintf("generated CRD YAML for LibraryPanel failed to unmarshal: %s", err)) + } + b, err = json.Marshal(map1) + if err != nil { + panic(fmt.Sprintf("could not re-marshal CRD JSON for LibraryPanel: %s", err)) + } + err = json.Unmarshal(b, &kk.Schema) + if err != nil { + panic(fmt.Sprintf("could not unmarshal CRD JSON for LibraryPanel: %s", err)) + } + reg.all[1] = kk + + kk = k8ssys.Kind{ + GrafanaKind: breg.Playlist(), + Object: &playlist.Playlist{}, + ObjectList: &playlist.PlaylistList{}, + } + // TODO Having the committed form on disk in YAML is worth doing this for now...but fix this silliness + map2 := make(map[string]any) + err = yaml.Unmarshal(playlist.CRDYaml, map2) + if err != nil { + panic(fmt.Sprintf("generated CRD YAML for Playlist failed to unmarshal: %s", err)) + } + b, err = json.Marshal(map2) + if err != nil { + panic(fmt.Sprintf("could not re-marshal CRD JSON for Playlist: %s", err)) + } + err = json.Unmarshal(b, &kk.Schema) + if err != nil { + panic(fmt.Sprintf("could not unmarshal CRD JSON for Playlist: %s", err)) + } + reg.all[2] = kk + + kk = k8ssys.Kind{ + GrafanaKind: breg.Preferences(), + Object: &preferences.Preferences{}, + ObjectList: &preferences.PreferencesList{}, + } + // TODO Having the committed form on disk in YAML is worth doing this for now...but fix this silliness + map3 := make(map[string]any) + err = yaml.Unmarshal(preferences.CRDYaml, map3) + if err != nil { + panic(fmt.Sprintf("generated CRD YAML for Preferences failed to unmarshal: %s", err)) + } + b, err = json.Marshal(map3) + if err != nil { + panic(fmt.Sprintf("could not re-marshal CRD JSON for Preferences: %s", err)) + } + err = json.Unmarshal(b, &kk.Schema) + if err != nil { + panic(fmt.Sprintf("could not unmarshal CRD JSON for Preferences: %s", err)) + } + reg.all[3] = kk + + kk = k8ssys.Kind{ + GrafanaKind: breg.PublicDashboard(), + Object: &publicdashboard.PublicDashboard{}, + ObjectList: &publicdashboard.PublicDashboardList{}, + } + // TODO Having the committed form on disk in YAML is worth doing this for now...but fix this silliness + map4 := make(map[string]any) + err = yaml.Unmarshal(publicdashboard.CRDYaml, map4) + if err != nil { + panic(fmt.Sprintf("generated CRD YAML for PublicDashboard failed to unmarshal: %s", err)) + } + b, err = json.Marshal(map4) + if err != nil { + panic(fmt.Sprintf("could not re-marshal CRD JSON for PublicDashboard: %s", err)) + } + err = json.Unmarshal(b, &kk.Schema) + if err != nil { + panic(fmt.Sprintf("could not unmarshal CRD JSON for PublicDashboard: %s", err)) + } + reg.all[4] = kk + + kk = k8ssys.Kind{ + GrafanaKind: breg.ServiceAccount(), + Object: &serviceaccount.ServiceAccount{}, + ObjectList: &serviceaccount.ServiceAccountList{}, + } + // TODO Having the committed form on disk in YAML is worth doing this for now...but fix this silliness + map5 := make(map[string]any) + err = yaml.Unmarshal(serviceaccount.CRDYaml, map5) + if err != nil { + panic(fmt.Sprintf("generated CRD YAML for ServiceAccount failed to unmarshal: %s", err)) + } + b, err = json.Marshal(map5) + if err != nil { + panic(fmt.Sprintf("could not re-marshal CRD JSON for ServiceAccount: %s", err)) + } + err = json.Unmarshal(b, &kk.Schema) + if err != nil { + panic(fmt.Sprintf("could not unmarshal CRD JSON for ServiceAccount: %s", err)) + } + reg.all[5] = kk + + kk = k8ssys.Kind{ + GrafanaKind: breg.Team(), + Object: &team.Team{}, + ObjectList: &team.TeamList{}, + } + // TODO Having the committed form on disk in YAML is worth doing this for now...but fix this silliness + map6 := make(map[string]any) + err = yaml.Unmarshal(team.CRDYaml, map6) + if err != nil { + panic(fmt.Sprintf("generated CRD YAML for Team failed to unmarshal: %s", err)) + } + b, err = json.Marshal(map6) + if err != nil { + panic(fmt.Sprintf("could not re-marshal CRD JSON for Team: %s", err)) + } + err = json.Unmarshal(b, &kk.Schema) + if err != nil { + panic(fmt.Sprintf("could not unmarshal CRD JSON for Team: %s", err)) + } + reg.all[6] = kk + + return reg +}