schema: Generate Go and Typescript from Thema coremodels (#49193)

* Add go code generator for coremodels

* Just generate the entire coremodel for now

Maybe we'll need more flexibility as more coremodels are added, but for
now this is fine.

* Add note on type comment about stability, grodkit

* Remove local replace directive for thema

* Generate typescript from coremodel

* Update pkg/coremodel/dashboard/addenda.go

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>

* Update cuetsy to new release

* Update thema to latest

* Fix enum generation for FieldColorModeId

* Put main generated object at the end of the file

* Tweaks to generated Go output

* Retweak back to var

* Add generated coremodel test

* Remove local replace statement again

* Add Make target and call into cuetsy cmd from gen

* Rename and comment linsrc for readability

* Move key codegen bits into reusable package

* Move body of cuetsifier into codegen pkg

Also genericize the diffing output into reusable WriteDiffer.

* Refactor coremodel generator to use WriteDiffer

* Add gen-cue step to CI

* Whip all the codegen automation into shape

* Add simplistic coremodel canonicality controls

* Remove erroneously committed test

* Bump thema version

* Remove dead code

* Improve wording of non-canonicality comment

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
sam boyer 2022-05-26 21:21:37 -04:00 committed by GitHub
parent a641949a05
commit be06d37a20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 2445 additions and 698 deletions

View File

@ -254,6 +254,14 @@ steps:
- validate-scuemata
image: grafana/build-container:1.5.4
name: ensure-cuetsified
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
with its inputs.'
- '# The following command will fail if running code generators produces any diff
in output.'
- CODEGEN_VERIFY=1 make gen-cue
image: grafana/build-container:1.5.4
name: verify-gen-cue
- commands:
- . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss
--build-id ${DRONE_BUILD_NUMBER} --variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64,armv6
@ -941,6 +949,14 @@ steps:
- validate-scuemata
image: grafana/build-container:1.5.4
name: ensure-cuetsified
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
with its inputs.'
- '# The following command will fail if running code generators produces any diff
in output.'
- CODEGEN_VERIFY=1 make gen-cue
image: grafana/build-container:1.5.4
name: verify-gen-cue
- commands:
- ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --sign
depends_on:
@ -1581,6 +1597,14 @@ steps:
- validate-scuemata
image: grafana/build-container:1.5.4
name: ensure-cuetsified
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
with its inputs.'
- '# The following command will fail if running code generators produces any diff
in output.'
- CODEGEN_VERIFY=1 make gen-cue
image: grafana/build-container:1.5.4
name: verify-gen-cue
- commands:
- ./bin/grabpl package --jobs 8 --edition oss --sign ${DRONE_TAG}
depends_on:
@ -2178,6 +2202,14 @@ steps:
- validate-scuemata
image: grafana/build-container:1.5.4
name: ensure-cuetsified
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
with its inputs.'
- '# The following command will fail if running code generators produces any diff
in output.'
- CODEGEN_VERIFY=1 make gen-cue
image: grafana/build-container:1.5.4
name: verify-gen-cue
- commands:
- ./bin/grabpl build-backend --jobs 8 --edition enterprise2 ${DRONE_TAG}
depends_on:
@ -3391,6 +3423,14 @@ steps:
- validate-scuemata
image: grafana/build-container:1.5.4
name: ensure-cuetsified
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
with its inputs.'
- '# The following command will fail if running code generators produces any diff
in output.'
- CODEGEN_VERIFY=1 make gen-cue
image: grafana/build-container:1.5.4
name: verify-gen-cue
- commands:
- ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --sign
depends_on:
@ -3933,6 +3973,14 @@ steps:
- validate-scuemata
image: grafana/build-container:1.5.4
name: ensure-cuetsified
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
with its inputs.'
- '# The following command will fail if running code generators produces any diff
in output.'
- CODEGEN_VERIFY=1 make gen-cue
image: grafana/build-container:1.5.4
name: verify-gen-cue
- commands:
- ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --build-id ${DRONE_BUILD_NUMBER}
--variants linux-amd64
@ -4667,6 +4715,6 @@ kind: secret
name: gcp_upload_artifacts_key
---
kind: signature
hmac: a697f4b02af2da0fc6c0d751e40e59db542541d6024e5f0c2b062c7060d635ab
hmac: fb64e50c271012ad1cc9b7ad7e7bd037736d6e3471d24f73774e8fd61472600c
...

1
.gitignore vendored
View File

@ -158,6 +158,7 @@ compilation-stats.json
# auto generated Go files
*_gen.go
!pkg/services/featuremgmt/toggles_gen.go
!pkg/coremodel/**/*_gen.go
# Auto-generated localisation files
public/locales/_build/

View File

@ -83,6 +83,11 @@ clean-api-spec:
##@ Building
gen-cue: ## Do all CUE/Thema code generation
@echo "generate code from .cue files"
go generate ./pkg/framework/coremodel
go generate ./public/app/plugins
gen-go: $(WIRE)
@echo "generate go files"
$(WIRE) gen -tags $(WIRE_TAGS) ./pkg/server ./pkg/cmd/grafana-cli/runner

20
go.mod
View File

@ -50,7 +50,7 @@ require (
github.com/google/wire v0.5.0
github.com/gorilla/websocket v1.4.2
github.com/gosimple/slug v1.9.0
github.com/grafana/cuetsy v0.0.1
github.com/grafana/cuetsy v0.0.2
github.com/grafana/grafana-aws-sdk v0.10.3
github.com/grafana/grafana-azure-sdk-go v1.2.0
github.com/grafana/grafana-plugin-sdk-go v0.134.0
@ -101,12 +101,12 @@ require (
go.opentelemetry.io/otel/exporters/jaeger v1.0.0
go.opentelemetry.io/otel/sdk v1.6.3
go.opentelemetry.io/otel/trace v1.6.3
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
golang.org/x/time v0.0.0-20220411224347-583f2d630306
golang.org/x/tools v0.1.10
gonum.org/v1/gonum v0.11.0
google.golang.org/api v0.74.0
@ -147,7 +147,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
github.com/deepmap/oapi-codegen v1.8.2 // indirect
github.com/deepmap/oapi-codegen v1.10.1
github.com/dennwc/varint v1.0.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
@ -164,7 +164,7 @@ require (
github.com/go-openapi/loads v0.20.2
github.com/go-openapi/runtime v0.19.29 // indirect
github.com/go-openapi/spec v0.20.4
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-openapi/validate v0.20.2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/status v1.1.0 // indirect
@ -182,7 +182,7 @@ require (
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-msgpack v0.5.5 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4
github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 // indirect
@ -245,6 +245,7 @@ require (
github.com/Azure/go-autorest/autorest/adal v0.9.17
github.com/armon/go-radix v1.0.0
github.com/blugelabs/bluge v0.1.9
github.com/getkin/kin-openapi v0.94.0
github.com/golang-migrate/migrate/v4 v4.7.0
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f
github.com/grafana/thema v0.0.0-20220523183731-72aebd14e751
@ -289,7 +290,6 @@ require (
github.com/coreos/go-semver v0.3.0 // indirect
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect
github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect
github.com/getkin/kin-openapi v0.94.0 // indirect
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@ -298,15 +298,19 @@ require (
github.com/imdario/mergo v0.3.12 // indirect
github.com/klauspost/compress v1.15.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labstack/echo/v4 v4.7.2 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pierrec/lz4/v4 v4.1.8 // indirect
github.com/segmentio/asm v1.1.1 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.3 // indirect
go.opentelemetry.io/proto/otlp v0.15.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
k8s.io/api v0.22.5 // indirect
k8s.io/apimachinery v0.22.5 // indirect
k8s.io/klog/v2 v2.30.0 // indirect

59
go.sum
View File

@ -768,9 +768,13 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
github.com/deepmap/oapi-codegen v1.10.1 h1:xybuJUR6D8l7P+LAuxOm5SD7nTlFKHWvOPl31q+DDVs=
github.com/deepmap/oapi-codegen v1.10.1/go.mod h1:TvVmDQlUkFli9gFij/gtW1o+tFBr4qCHyv2zG+R0YZY=
github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE=
github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=
github.com/denverdino/aliyungo v0.0.0-20170926055100-d3308649c661/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
@ -881,7 +885,6 @@ github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8
github.com/ema/qdisc v0.0.0-20190904071900-b82c76788043/go.mod h1:ix4kG2zvdUd8kEKSW0ZTr1XLks0epFpI4j745DXxlNE=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/proto v1.6.15 h1:XbpwxmuOPrdES97FrSfpyy67SSCV/wBIKXqgJzh6hNw=
github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw=
github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
@ -969,6 +972,7 @@ github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
@ -1121,8 +1125,9 @@ github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72P
github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
@ -1139,8 +1144,11 @@ github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v8 v8.0.0-beta.10.0.20200905143926-df7fe4e2ce72/go.mod h1:CJP1ZIHwhosNYwIdaHPZK9vHsM3+roNBaZ7U9Of1DXc=
@ -1196,6 +1204,7 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/gocql/gocql v0.0.0-20200121121104-95d072f1b5bb/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
github.com/gocql/gocql v0.0.0-20200228163523-cd4b606dd2fb/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
@ -1293,6 +1302,7 @@ github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
@ -1438,8 +1448,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
github.com/grafana/cuetsy v0.0.1 h1:HEoYBiEb8zRq70kc0WSVG+bZ85W8nQ8BQr4l7f2kl0s=
github.com/grafana/cuetsy v0.0.1/go.mod h1:h8fQHb+IHbLjJ6UpaAuRtd8qV6D2JEm+j9rSk3G2Dik=
github.com/grafana/cuetsy v0.0.2 h1:0vbU+MNznzxFyaUtbqZ6UGiOrm4HotPQGLyc121xGC8=
github.com/grafana/cuetsy v0.0.2/go.mod h1:7OoEYb42s7PbSYtNUTy1DoCeJ3LAOTafsZbndvikTj8=
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f h1:FvvSVEbnGeM2bUivGmsiXTi8URJyBU7TcFEEoRe5wWI=
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f/go.mod h1:uPG2nyK4CtgNDmWv7qyzYcdI+S90kHHRWvHnBtEMBXM=
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA=
@ -1868,7 +1878,11 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/laher/mergefs v0.1.1 h1:nV2bTS57vrmbMxeR6uvJpI8LyGl3QHj4bLBZO3aUV58=
github.com/laher/mergefs v0.1.1/go.mod h1:FSY1hYy94on4Tz60waRMGdO1awwS23BacqJlqf9lJ9Q=
github.com/lann/builder v0.0.0-20150808151131-f22ce00fd939/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
@ -1878,8 +1892,17 @@ github.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkO
github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4=
github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/ragel-machinery v0.0.0-20181214104525-299bdde78165/go.mod h1:WZxr2/6a/Ar9bMDc2rN/LJrE/hF6bXE4LPyDSIxwAfg=
github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx v1.2.23/go.mod h1:sAXjRwzSvCN6soO4RLoWWm1bVPpb8iOuv0IYfH8OWd8=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -1930,6 +1953,7 @@ github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHef
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM=
@ -2396,7 +2420,6 @@ github.com/prometheus/prometheus v1.8.2-0.20211011171444-354d8d2ecfac/go.mod h1:
github.com/prometheus/statsd_exporter v0.20.0/go.mod h1:YL3FWCG8JBBtaUSxAg4Gz2ZYu22bS84XM89ZQXXTWmQ=
github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc h1:gSVONBi2HWMFXCa9jFdYvYk7IwW/mTLxWOF7rXS4LO0=
github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc/go.mod h1:KbKfKPy2I6ecOIGA9apfheFv14+P3RSmmQvshofQyMY=
github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b h1:zd/2RNzIRkoGGMjE+YIsZ85CnDIz672JK2F3Zl4vux4=
github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78=
@ -2612,14 +2635,10 @@ github.com/thanos-io/thanos v0.13.1-0.20210224074000-659446cab117/go.mod h1:kdqF
github.com/thanos-io/thanos v0.13.1-0.20210226164558-03dace0a1aa1/go.mod h1:gMCy4oCteKTT7VuXVvXLTPGzzjovX1VPE5p+HgL1hyU=
github.com/thanos-io/thanos v0.13.1-0.20210401085038-d7dff0c84d17/go.mod h1:zU8KqE+6A+HksK4wiep8e/3UvCZLm+Wrw9AqZGaAm9k=
github.com/thanos-io/thanos v0.22.0/go.mod h1:SZDWz3phcUcBr4MYFoPFRvl+Z9Nbi45HlwQlwSZSt+Q=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/tinylru v1.0.2/go.mod h1:HDVL7TsWeezQ4g44Um84TOVBMFcq7Xa9giqNc805KJ8=
@ -2658,8 +2677,10 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@ -2674,6 +2695,7 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae h1:oyiy3uBj1F4O3AaFh7hUGBrJjAssJhKyAbwxtkslxqo=
@ -2971,8 +2993,11 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -3036,6 +3061,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -3132,8 +3158,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8=
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -3174,8 +3199,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -3338,6 +3364,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -3352,6 +3379,7 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@ -3384,8 +3412,9 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -3,4 +3,4 @@
*
* @packageDocumentation
*/
export * from './schema/graph.gen';
export * from './schema/mudball.gen';

View File

@ -0,0 +1,222 @@
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from repository root.
//
// Derived from the Thema lineage at pkg/coremodel/dashboard
// This model is a WIP and not yet canonical. Consequently, its members are
// not exported to exclude it from grafana-schema's public API surface.
interface AnnotationQuery {
builtIn: number;
datasource: {};
enable: boolean;
hide?: boolean;
iconColor?: string;
name?: string;
rawQuery?: string;
showIn: number;
target?: {};
type: string;
}
const defaultAnnotationQuery: Partial<AnnotationQuery> = {
builtIn: 0,
enable: true,
hide: false,
showIn: 0,
type: 'dashboard',
};
interface VariableModel {
label?: string;
name: string;
type: VariableType;
}
interface DashboardLink {
asDropdown: boolean;
icon?: string;
includeVars: boolean;
keepTime: boolean;
tags: string[];
targetBlank: boolean;
title: string;
tooltip?: string;
type: DashboardLinkType;
url?: string;
}
const defaultDashboardLink: Partial<DashboardLink> = {
asDropdown: false,
includeVars: false,
keepTime: false,
tags: [],
targetBlank: false,
};
type DashboardLinkType = 'link' | 'dashboards';
type VariableType = 'query' | 'adhoc' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system';
enum FieldColorModeId {
ContinuousGrYlRd = 'continuous-GrYlRd',
Fixed = 'fixed',
PaletteClassic = 'palette-classic',
PaletteSaturated = 'palette-saturated',
Thresholds = 'thresholds',
}
type FieldColorSeriesByMode = 'min' | 'max' | 'last';
interface FieldColor {
fixedColor?: string;
mode: FieldColorModeId | string;
seriesBy?: FieldColorSeriesByMode;
}
interface Threshold {
color: string;
state?: string;
value?: number;
}
enum ThresholdsMode {
Absolute = 'absolute',
Percentage = 'percentage',
}
interface ThresholdsConfig {
mode: ThresholdsMode;
steps: Threshold[];
}
const defaultThresholdsConfig: Partial<ThresholdsConfig> = {
steps: [],
};
interface Transformation {
id: string;
options: {};
}
enum DashboardCursorSync {
Crosshair = 1,
Off = 0,
Tooltip = 2,
}
const defaultDashboardCursorSync: DashboardCursorSync = DashboardCursorSync.Off;
interface Panel {
datasource?: {};
description?: string;
fieldConfig: {
defaults: {};
overrides: {
matcher: {
id: string;
};
properties: {
id: string;
}[];
}[];
};
gridPos?: {
h: number;
w: number;
x: number;
y: number;
};
id?: number;
interval?: string;
links?: DashboardLink[];
maxDataPoints?: number;
options: {};
pluginVersion?: string;
repeat?: string;
repeatDirection: 'h' | 'v';
tags?: string[];
targets?: {}[];
thresholds?: any[];
timeFrom?: string;
timeRegions?: any[];
timeShift?: string;
title?: string;
transformations: Transformation[];
transparent: boolean;
type: string;
}
const defaultPanel: Partial<Panel> = {
links: [],
repeatDirection: 'h',
tags: [],
targets: [],
thresholds: [],
timeRegions: [],
transformations: [],
transparent: false,
};
interface Dashboard {
annotations?: {
list: AnnotationQuery[];
};
description?: string;
editable: boolean;
fiscalYearStartMonth?: number;
gnetId?: string;
graphTooltip: DashboardCursorSync;
id?: number;
links?: DashboardLink[];
liveNow?: boolean;
panels?: Panel | {
type: 'graph';
} | {
type: 'heatmap';
} | {
type: 'row';
collapsed: boolean;
id: number;
panels: Panel | {
type: 'graph';
} | {
type: 'heatmap';
}[];
}[];
refresh?: string | false;
schemaVersion: number;
style: 'light' | 'dark';
tags?: string[];
templating?: {
list: VariableModel[];
};
time?: {
from: string;
to: string;
};
timepicker?: {
collapse: boolean;
enable: boolean;
hidden: boolean;
refresh_intervals: string[];
};
timezone?: 'browser' | 'utc' | '';
title?: string;
uid?: string;
version?: number;
weekStart?: string;
}
const defaultDashboard: Partial<Dashboard> = {
editable: true,
graphTooltip: DashboardCursorSync.Off,
links: [],
panels: [],
schemaVersion: 36,
style: 'dark',
tags: [],
timezone: 'browser',
};

View File

@ -1,89 +0,0 @@
package schema
AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(kind="enum")
VisibilityMode: "auto" | "never" | "always" @cuetsy(kind="enum")
GraphDrawStyle: "line" | "bars" | "points" @cuetsy(kind="enum")
LineInterpolation: "linear" | "smooth" | "stepBefore" | "stepAfter" @cuetsy(kind="enum")
ScaleDistribution: "linear" | "log" | "ordinal" @cuetsy(kind="enum")
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(kind="enum")
StackingMode: "none" | "normal" | "percent" @cuetsy(kind="enum")
GraphTransform: "constant" | "negative-Y" @cuetsy(kind="enum")
BarAlignment: -1 | 0 | 1 @cuetsy(kind="enum",memberNames="Before|Center|After")
ScaleOrientation: 0 | 1 @cuetsy(kind="enum",memberNames="Horizontal|Vertical")
ScaleDirection: 1 | 1 | -1 | -1 @cuetsy(kind="enum",memberNames="Up|Right|Down|Left")
LineStyle: {
fill?: "solid" | "dash" | "dot" | "square"
dash?: [...number]
} @cuetsy(kind="interface")
LineConfig: {
lineColor?: string
lineWidth?: number
lineInterpolation?: LineInterpolation
lineStyle?: LineStyle
// Indicate if null values should be treated as gaps or connected.
// When the value is a number, it represents the maximum delta in the
// X axis that should be considered connected. For timeseries, this is milliseconds
spanNulls?: bool | number
} @cuetsy(kind="interface")
BarConfig: {
barAlignment?: BarAlignment
barWidthFactor?: number
barMaxWidth?: number
} @cuetsy(kind="interface")
FillConfig: {
fillColor?: string
fillOpacity?: number
fillBelowTo?: string
} @cuetsy(kind="interface")
PointsConfig: {
showPoints?: VisibilityMode
pointSize?: number
pointColor?: string
pointSymbol?: string
} @cuetsy(kind="interface")
ScaleDistributionConfig: {
type: ScaleDistribution
log?: number
} @cuetsy(kind="interface")
AxisConfig: {
axisPlacement?: AxisPlacement
axisLabel?: string
axisWidth?: number
axisSoftMin?: number
axisSoftMax?: number
axisGridShow?: bool
scaleDistribution?: ScaleDistributionConfig
} @cuetsy(kind="interface")
HideSeriesConfig: {
tooltip: bool
legend: bool
viz: bool
} @cuetsy(kind="interface")
StackingConfig: {
mode?: StackingMode
group?: string
} @cuetsy(kind="interface")
StackableFieldConfig: {
stacking?: StackingConfig
} @cuetsy(kind="interface")
HideableFieldConfig: {
hideFrom?: HideSeriesConfig
} @cuetsy(kind="interface")
GraphTresholdsStyleMode: "off" | "line" | "area" | "line+area" | "series" @cuetsy(kind="enum",memberNames="Off|Line|Area|LineAndArea|Series")
GraphThresholdsStyleConfig: {
mode: GraphTresholdsStyleMode
} @cuetsy(kind="interface")
GraphFieldConfig: {
LineConfig
FillConfig
PointsConfig
AxisConfig
BarConfig
StackableFieldConfig
HideableFieldConfig
drawStyle?: GraphDrawStyle
gradientMode?: GraphGradientMode
thresholdsStyle?: GraphThresholdsStyleConfig
transform?: GraphTransform
} @cuetsy(kind="interface")

View File

@ -1,15 +0,0 @@
package schema
LegendPlacement: "bottom" | "right" @cuetsy(kind="type")
LegendDisplayMode: "list" | "table" | "hidden" @cuetsy(kind="enum")
VizLegendOptions: {
displayMode: LegendDisplayMode
placement: LegendPlacement
asTable?: bool
isVisible?: bool
sortBy?: string
sortDesc?: bool
calcs: [...string]
} @cuetsy(kind="interface")

View File

@ -1,19 +1,149 @@
package schema
// Use this file as a big TODO list - if it's still in here, it's a TODO to
// separate it out into a discrete file.
// TODO break this up into individual files. Current limitation on this is codegen logic, imports, dependencies
// TODO docs
AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(kind="enum")
// TODO docs
VisibilityMode: "auto" | "never" | "always" @cuetsy(kind="enum")
// TODO docs
GraphDrawStyle: "line" | "bars" | "points" @cuetsy(kind="enum")
// TODO docs
GraphTransform: "constant" | "negative-Y" @cuetsy(kind="enum",memberNames="Constant|NegativeY")
// TODO docs
LineInterpolation: "linear" | "smooth" | "stepBefore" | "stepAfter" @cuetsy(kind="enum")
// TODO docs
ScaleDistribution: "linear" | "log" | "ordinal" @cuetsy(kind="enum")
// TODO docs
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(kind="enum")
// TODO docs
StackingMode: "none" | "normal" | "percent" @cuetsy(kind="enum")
// TODO docs
BarAlignment: -1 | 0 | 1 @cuetsy(kind="enum",memberNames="Before|Center|After")
// TODO docs
ScaleOrientation: 0 | 1 @cuetsy(kind="enum",memberNames="Horizontal|Vertical")
// TODO docs
ScaleDirection: 1 | 1 | -1 | -1 @cuetsy(kind="enum",memberNames="Up|Right|Down|Left")
// TODO docs
LineStyle: {
fill?: "solid" | "dash" | "dot" | "square"
dash?: [...number]
} @cuetsy(kind="interface")
// TODO docs
LineConfig: {
lineColor?: string
lineWidth?: number
lineInterpolation?: LineInterpolation
lineStyle?: LineStyle
// Indicate if null values should be treated as gaps or connected.
// When the value is a number, it represents the maximum delta in the
// X axis that should be considered connected. For timeseries, this is milliseconds
spanNulls?: bool | number
} @cuetsy(kind="interface")
// TODO docs
BarConfig: {
barAlignment?: BarAlignment
barWidthFactor?: number
barMaxWidth?: number
} @cuetsy(kind="interface")
// TODO docs
FillConfig: {
fillColor?: string
fillOpacity?: number
fillBelowTo?: string
} @cuetsy(kind="interface")
// TODO docs
PointsConfig: {
showPoints?: VisibilityMode
pointSize?: number
pointColor?: string
pointSymbol?: string
} @cuetsy(kind="interface")
// TODO docs
ScaleDistributionConfig: {
type: ScaleDistribution
log?: number
} @cuetsy(kind="interface")
// TODO docs
AxisConfig: {
axisPlacement?: AxisPlacement
axisLabel?: string
axisWidth?: number
axisSoftMin?: number
axisSoftMax?: number
axisGridShow?: bool
scaleDistribution?: ScaleDistributionConfig
} @cuetsy(kind="interface")
// TODO docs
HideSeriesConfig: {
tooltip: bool
legend: bool
viz: bool
} @cuetsy(kind="interface")
// TODO docs
StackingConfig: {
mode?: StackingMode
group?: string
} @cuetsy(kind="interface")
// TODO docs
StackableFieldConfig: {
stacking?: StackingConfig
} @cuetsy(kind="interface")
// TODO docs
HideableFieldConfig: {
hideFrom?: HideSeriesConfig
} @cuetsy(kind="interface")
// TODO docs
GraphTresholdsStyleMode: "off" | "line" | "area" | "line+area" | "series" @cuetsy(kind="enum",memberNames="Off|Line|Area|LineAndArea|Series")
// TODO docs
GraphThresholdsStyleConfig: {
mode: GraphTresholdsStyleMode
} @cuetsy(kind="interface")
// TODO docs
LegendPlacement: "bottom" | "right" @cuetsy(kind="type")
// TODO docs
LegendDisplayMode: "list" | "table" | "hidden" @cuetsy(kind="enum")
// TODO docs
TableSortByFieldState: {
displayName: string
desc?: bool
} @cuetsy(kind="interface")
// TODO docs
SingleStatBaseOptions: {
OptionsWithTextFormatting
reduceOptions: ReduceDataOptions
orientation: VizOrientation
} @cuetsy(kind="interface")
// TODO copy back to appropriate place
// TODO docs
ReduceDataOptions: {
// If true show each row value
values?: bool
@ -24,30 +154,100 @@ ReduceDataOptions: {
// Which fields to show. By default this is only numeric fields
fields?: string
} @cuetsy(kind="interface")
// TODO copy back to appropriate place
// TODO docs
VizOrientation: "auto" | "vertical" | "horizontal" @cuetsy(kind="enum")
// TODO copy back to appropriate place
// TODO docs
OptionsWithTooltip: {
// FIXME this field is non-optional in the corresponding TS type
tooltip?: VizTooltipOptions
tooltip: VizTooltipOptions
} @cuetsy(kind="interface")
// TODO copy back to appropriate place
// TODO docs
OptionsWithLegend: {
// FIXME this field is non-optional in the corresponding TS type
legend?: VizLegendOptions
legend: VizLegendOptions
} @cuetsy(kind="interface")
// TODO copy back to appropriate place
// TODO docs
OptionsWithTextFormatting: {
text?: VizTextDisplayOptions
} @cuetsy(kind="interface")
// TODO copy back to appropriate place
// TODO docs
BigValueColorMode: "value" | "background" | "none" @cuetsy(kind="enum")
// TODO copy back to appropriate place
// TODO docs
BigValueGraphMode: "none" | "line" | "area" @cuetsy(kind="enum")
// TODO copy back to appropriate place
// TODO docs
BigValueJustifyMode: "auto" | "center" @cuetsy(kind="enum")
// TODO copy back to appropriate place
// TODO does cuetsy handle underscores the expected way?
// TODO docs
BigValueTextMode: "auto" | "value" | "value_and_name" | "name" | "none" @cuetsy(kind="enum",memberNames="Auto|Value|ValueAndName|Name|None")
// TODO copy back to appropriate place
// TODO -- should not be table specific!
// TODO docs
FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(kind="type")
// TODO docs
TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image")
// TODO docs
VizTextDisplayOptions: {
// Explicit title text size
titleSize?: number
// Explicit value text size
valueSize?: number
} @cuetsy(kind="interface")
// TODO docs
TooltipDisplayMode: "single" | "multi" | "none" @cuetsy(kind="enum")
// TODO docs
SortOrder: "asc" | "desc" | "none" @cuetsy(kind="enum",memberNames="Ascending|Descending|None")
// TODO docs
GraphFieldConfig: {
LineConfig
FillConfig
PointsConfig
AxisConfig
BarConfig
StackableFieldConfig
HideableFieldConfig
drawStyle?: GraphDrawStyle
gradientMode?: GraphGradientMode
thresholdsStyle?: GraphThresholdsStyleConfig
transform?: GraphTransform
} @cuetsy(kind="interface")
// TODO docs
VizLegendOptions: {
displayMode: LegendDisplayMode
placement: LegendPlacement
asTable?: bool
isVisible?: bool
sortBy?: string
sortDesc?: bool
calcs: [...string]
} @cuetsy(kind="interface")
// TODO docs
BarGaugeDisplayMode: "basic" | "lcd" | "gradient" @cuetsy(kind="enum")
// TODO docs
TableFieldOptions: {
width?: number
minWidth?: number
align: FieldTextAlignment | *"auto"
displayMode: TableCellDisplayMode | *"auto"
hidden?: bool // ?? default is missing or false ??
inspect: bool | *false
filterable?: bool
} @cuetsy(kind="interface")
// TODO docs
VizTooltipOptions: {
mode: TooltipDisplayMode
sort: SortOrder
} @cuetsy(kind="interface")

View File

@ -1,8 +1,11 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy from all the files in this directory,
// then hand-edited for correctness. It will be fully auto-generated Soon™.
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export enum AxisPlacement {
Auto = 'auto',
Bottom = 'bottom',
@ -78,6 +81,10 @@ export interface LineStyle {
fill?: 'solid' | 'dash' | 'dot' | 'square';
}
export const defaultLineStyle: Partial<LineStyle> = {
dash: [],
};
export interface LineConfig {
lineColor?: string;
lineInterpolation?: LineInterpolation;
@ -176,6 +183,10 @@ export interface ReduceDataOptions {
values?: boolean;
}
export const defaultReduceDataOptions: Partial<ReduceDataOptions> = {
calcs: [],
};
export enum VizOrientation {
Auto = 'auto',
Horizontal = 'horizontal',
@ -250,14 +261,7 @@ export enum SortOrder {
None = 'none',
}
export interface GraphFieldConfig
extends LineConfig,
FillConfig,
PointsConfig,
AxisConfig,
BarConfig,
StackableFieldConfig,
HideableFieldConfig {
export interface GraphFieldConfig extends LineConfig, FillConfig, PointsConfig, AxisConfig, BarConfig, StackableFieldConfig, HideableFieldConfig {
drawStyle?: GraphDrawStyle;
gradientMode?: GraphGradientMode;
thresholdsStyle?: GraphThresholdsStyleConfig;
@ -274,6 +278,10 @@ export interface VizLegendOptions {
sortDesc?: boolean;
}
export const defaultVizLegendOptions: Partial<VizLegendOptions> = {
calcs: [],
};
export enum BarGaugeDisplayMode {
Basic = 'basic',
Gradient = 'gradient',
@ -281,16 +289,16 @@ export enum BarGaugeDisplayMode {
}
export interface TableFieldOptions {
align: string;
align: FieldTextAlignment;
displayMode: TableCellDisplayMode;
inspect: boolean;
filterable?: boolean;
hidden?: boolean;
inspect: boolean;
minWidth?: number;
width?: number;
filterable?: boolean;
}
export const defaultTableFieldOptions: TableFieldOptions = {
export const defaultTableFieldOptions: Partial<TableFieldOptions> = {
align: 'auto',
displayMode: TableCellDisplayMode.Auto,
inspect: false,

View File

@ -1,15 +0,0 @@
package schema
// TODO -- should not be table specific!
FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(kind="type")
TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image")
TableFieldOptions: {
width?: number
minWidth?: number
align: FieldTextAlignment | *"auto"
displayMode: TableCellDisplayMode | *"auto"
hidden?: bool // ?? default is missing or false ??
filterable?: bool
} @cuetsy(kind="interface")

View File

@ -1,8 +0,0 @@
package schema
VizTextDisplayOptions: {
// Explicit title text size
titleSize?: number
// Explicit value text size
valueSize?: number
} @cuetsy(kind="interface")

View File

@ -1,9 +0,0 @@
package schema
TooltipDisplayMode: "single" | "multi" | "none" @cuetsy(kind="enum")
SortOrder: "asc" | "desc" | "none" @cuetsy(kind="enum")
VizTooltipOptions: {
mode: TooltipDisplayMode
sort: SortOrder
} @cuetsy(kind="interface")

View File

@ -1,365 +1,30 @@
package commands
import (
"bytes"
gerrors "errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"text/template"
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/errors"
cload "cuelang.org/go/cue/load"
"cuelang.org/go/cue/parser"
"github.com/google/go-cmp/cmp"
"github.com/grafana/cuetsy"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
"github.com/grafana/grafana/pkg/schema/load"
"github.com/grafana/grafana/pkg/codegen"
)
// FIXME almost this whole file is a sloppy, one-off hack that just goes around actually making
// the API we need. Parts need to be factored out appropriately.
var ctx = cuecontext.New()
// The only import statement we currently allow in any models.cue file
const allowedImport = "github.com/grafana/grafana/packages/grafana-schema/src/schema"
var importMap = map[string]string{
allowedImport: "@grafana/schema",
}
// Hard-coded list of paths to skip. Remove a particular file as we're ready
// to rely on the TypeScript auto-generated by cuetsy for that particular file.
var skipPaths = []string{
"public/app/plugins/panel/barchart/models.cue",
"public/app/plugins/panel/canvas/models.cue",
"public/app/plugins/panel/histogram/models.cue",
"public/app/plugins/panel/heatmap-new/models.cue",
"public/app/plugins/panel/candlestick/models.cue",
"public/app/plugins/panel/state-timeline/models.cue",
"public/app/plugins/panel/status-history/models.cue",
"public/app/plugins/panel/table/models.cue",
"public/app/plugins/panel/timeseries/models.cue",
// All the cue files in this dir have to be individually excluded, even
// though the generator currently smooshes them all together
"packages/grafana-schema/src/schema/graph.cue",
"packages/grafana-schema/src/schema/legend.cue",
"packages/grafana-schema/src/schema/mudball.cue",
"packages/grafana-schema/src/schema/table.cue",
"packages/grafana-schema/src/schema/text.cue",
"packages/grafana-schema/src/schema/tooltip.cue",
}
const prefix = "/"
//nolint: gocyclo
// TODO remove this whole thing
func (cmd Command) generateTypescript(c utils.CommandLine) error {
root := c.String("grafana-root")
if root == "" {
return gerrors.New("must provide path to the root of a Grafana repository checkout")
}
var fspaths load.BaseLoadPaths
var err error
fspaths.BaseCueFS, err = populateMapFSFromRoot(paths.BaseCueFS, root, "")
if err != nil {
return err
}
fspaths.DistPluginCueFS, err = populateMapFSFromRoot(paths.DistPluginCueFS, root, "")
if err != nil {
return err
}
overlay, err := defaultOverlay(fspaths)
wd, err := codegen.CuetsifyPlugins(ctx, root)
if err != nil {
return err
}
// Prep the cue load config
clcfg := &cload.Config{
Overlay: overlay,
// FIXME these module paths won't work for things not under our cue.mod - AKA third-party plugins
// ModuleRoot: prefix,
Module: "github.com/grafana/grafana",
if c.Bool("diff") {
return wd.Verify()
}
// FIXME hardcoding paths to exclude is not the way to handle this
excl := map[string]bool{
"cue.mod": true,
"cue/scuemata": true,
"packages/grafana-schema/src/scuemata/dashboard": true,
"packages/grafana-schema/src/scuemata/dashboard/dist": true,
}
exclude := func(path string) bool {
dir := filepath.Dir(path)
if excl[dir] {
return true
}
for _, p := range skipPaths {
if path == p {
return true
}
}
return false
}
outfiles := make(map[string][]byte)
cuetsify := func(in fs.FS) error {
seen := make(map[string]bool)
return fs.WalkDir(in, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
dir := filepath.Dir(path)
if d.IsDir() || filepath.Ext(d.Name()) != ".cue" || seen[dir] || exclude(path) {
return nil
}
seen[dir] = true
clcfg.Dir = dir
// FIXME Horrible hack to figure out the identifier used for
// imported packages - intercept the parser called by the loader to
// look at the ast.Files on their way in to building.
// Much better if we could work backwards from the cue.Value,
// maybe even directly in cuetsy itself, and figure out when a
// referenced object is "out of bounds".
// var imports sync.Map
var imports []*ast.ImportSpec
clcfg.ParseFile = func(name string, src interface{}) (*ast.File, error) {
f, err := parser.ParseFile(name, src, parser.ParseComments)
if err != nil {
return nil, err
}
imports = append(imports, f.Imports...)
return f, nil
}
// FIXME loading in this way causes all files in a dir to be loaded
// as a single cue.Instance or cue.Value, which makes it quite
// difficult to map them _back_ onto the original file and generate
// discrete .gen.ts files for each .cue input. However, going one
// .cue file at a time and passing it as the first arg to
// load.Instances() means that the other files are ignored
// completely, causing references between these files to be
// unresolved, and thus encounter a different kind of error.
insts := cload.Instances(nil, clcfg)
if len(insts) > 1 {
panic("extra instances")
}
bi := insts[0]
v := ctx.BuildInstance(bi)
if v.Err() != nil {
return v.Err()
}
var b []byte
f := &tsFile{}
seen := make(map[string]bool)
// FIXME explicitly mapping path patterns to conversion patterns
// is exactly what we want to avoid
switch {
// panel plugin models.cue files
case strings.Contains(path, "public/app/plugins"):
for _, im := range imports {
ip := strings.Trim(im.Path.Value, "\"")
if ip != allowedImport {
// TODO make a specific error type for this
return errors.Newf(im.Pos(), "import %q not allowed, panel plugins may only import from %q", ip, allowedImport)
}
// TODO this approach will silently swallow the unfixable
// error case where multiple files in the same dir import
// the same package to a different ident
if !seen[ip] {
seen[ip] = true
f.Imports = append(f.Imports, convertImport(im))
}
}
// val := v.LookupPath(cue.ParsePath("Panel.lineages[0][0]"))
// Extract the latest schema and its version number. (All of this goes away with Thema, whew)
f.V = &tsModver{}
lins := v.LookupPath(cue.ParsePath("Panel.lineages"))
f.V.Lin, _ = lins.Len().Int64()
f.V.Lin = f.V.Lin - 1
schs := lins.LookupPath(cue.MakePath(cue.Index(int(f.V.Lin))))
f.V.Sch, _ = schs.Len().Int64()
f.V.Sch = f.V.Sch - 1
latest := schs.LookupPath(cue.MakePath(cue.Index(int(f.V.Sch))))
b, err = cuetsy.Generate(latest, cuetsy.Config{})
default:
b, err = cuetsy.Generate(v, cuetsy.Config{})
}
if err != nil {
return err
}
f.Body = string(b)
var buf bytes.Buffer
err = tsTemplate.Execute(&buf, f)
outfiles[strings.Replace(path, ".cue", ".gen.ts", -1)] = buf.Bytes()
return err
})
}
err = cuetsify(fspaths.BaseCueFS)
if err != nil {
return gerrors.New(errors.Details(err, nil))
}
err = cuetsify(fspaths.DistPluginCueFS)
if err != nil {
return gerrors.New(errors.Details(err, nil))
}
diff := c.Bool("diff")
var derr bool
for of, b := range outfiles {
p := filepath.Join(root, of)
if diff {
if _, err := os.Stat(p); err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Printf("%s: no generated code file to compare against\n", p)
derr = true
continue
}
return fmt.Errorf("%s: %w", p, err)
}
f, err := os.Open(filepath.Clean(p))
if err != nil {
return fmt.Errorf("%s: %w", p, err)
}
ob, err := io.ReadAll(f)
if err != nil {
return err
}
dstr := cmp.Diff(string(ob), string(b))
if dstr != "" {
derr = true
fmt.Printf("%s would have changed:\n%s\n", p, dstr)
}
} else {
err := os.WriteFile(p, b, 0644)
if err != nil {
return err
}
}
}
if derr {
return errors.New("some files changed")
}
return nil
return wd.Write()
}
func convertImport(im *ast.ImportSpec) *tsImport {
tsim := &tsImport{
Pkg: importMap[allowedImport],
}
if im.Name != nil && im.Name.String() != "" {
tsim.Ident = im.Name.String()
} else {
sl := strings.Split(im.Path.Value, "/")
final := sl[len(sl)-1]
if idx := strings.Index(final, ":"); idx != -1 {
tsim.Pkg = final[idx:]
} else {
tsim.Pkg = final
}
}
return tsim
}
func defaultOverlay(p load.BaseLoadPaths) (map[string]cload.Source, error) {
overlay := make(map[string]cload.Source)
if err := toOverlay(prefix, p.BaseCueFS, overlay); err != nil {
return nil, err
}
if err := toOverlay(prefix, p.DistPluginCueFS, overlay); err != nil {
return nil, err
}
return overlay, nil
}
func toOverlay(prefix string, vfs fs.FS, overlay map[string]cload.Source) error {
if !filepath.IsAbs(prefix) {
return fmt.Errorf("must provide absolute path prefix when generating cue overlay, got %q", prefix)
}
err := fs.WalkDir(vfs, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
f, err := vfs.Open(path)
if err != nil {
return err
}
defer func(f fs.File) {
err := f.Close()
if err != nil {
return
}
}(f)
b, err := io.ReadAll(f)
if err != nil {
return err
}
overlay[filepath.Join(prefix, path)] = cload.FromBytes(b)
return nil
})
if err != nil {
return err
}
return nil
}
type tsFile struct {
V *tsModver
Imports []*tsImport
Body string
}
type tsModver struct {
Lin, Sch int64
}
type tsImport struct {
Ident string
Pkg string
}
var tsTemplate = template.Must(template.New("cuetsygen").Parse(
`//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy. DO NOT EDIT!
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{{range .Imports}}
import * as {{.Ident}} from '{{.Pkg}}';{{end}}
{{if .V}}
export const modelVersion = Object.freeze([{{ .V.Lin }}, {{ .V.Sch }}]);
{{end}}
{{.Body}}`))

399
pkg/codegen/coremodel.go Normal file
View File

@ -0,0 +1,399 @@
package codegen
import (
"bytes"
"errors"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"testing/fstest"
"text/template"
"cuelang.org/go/pkg/encoding/yaml"
"github.com/deepmap/oapi-codegen/pkg/codegen"
"github.com/getkin/kin-openapi/openapi3"
"github.com/grafana/cuetsy"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/thema"
"github.com/grafana/thema/encoding/openapi"
"golang.org/x/tools/imports"
)
// ExtractedLineage contains the results of statically analyzing a Grafana
// directory for a Thema lineage.
type ExtractedLineage struct {
Lineage thema.Lineage
// Absolute path to the coremodel's lineage.cue file.
LineagePath string
// Path to the coremodel's lineage.cue file relative to repo root.
RelativePath string
// Indicates whether the coremodel is considered canonical or not. Generated
// code from not-yet-canonical coremodels should include appropriate caveats in
// documentation and possibly be hidden from external public API surface areas.
IsCanonical bool
}
// ExtractLineage loads a Grafana Thema lineage from the filesystem.
//
// The provided path must be the absolute path to the file containing the
// lineage to be loaded.
//
// This loading approach is intended primarily for use with code generators, or
// other use cases external to grafana-server backend. For code within
// grafana-server, prefer lineage loaders provided in e.g. pkg/coremodel/*.
func ExtractLineage(path string, lib thema.Library) (*ExtractedLineage, error) {
if !filepath.IsAbs(path) {
return nil, fmt.Errorf("must provide an absolute path, got %q", path)
}
ec := &ExtractedLineage{
LineagePath: path,
}
var find func(path string) (string, error)
find = func(path string) (string, error) {
parent := filepath.Dir(path)
if parent == path {
return "", errors.New("grafana root directory could not be found")
}
fp := filepath.Join(path, "go.mod")
if _, err := os.Stat(fp); err == nil {
return path, nil
}
return find(parent)
}
groot, err := find(path)
if err != nil {
return ec, err
}
f, err := os.Open(ec.LineagePath)
if err != nil {
return nil, fmt.Errorf("could not open lineage file at %s: %w", path, err)
}
byt, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
fs := fstest.MapFS{
"lineage.cue": &fstest.MapFile{
Data: byt,
},
}
ec.RelativePath, err = filepath.Rel(groot, filepath.Dir(path))
if err != nil {
// should be unreachable, since we rootclimbed to find groot above
panic(err)
}
ec.Lineage, err = cuectx.LoadGrafanaInstancesWithThema(ec.RelativePath, fs, lib)
if err != nil {
return ec, err
}
ec.IsCanonical = isCanonical(ec.Lineage.Name())
return ec, nil
}
func isCanonical(name string) bool {
return canonicalCoremodels[name]
}
// FIXME specificying coremodel canonicality DOES NOT belong here - it should be part of the coremodel declaration.
var canonicalCoremodels = map[string]bool{
"dashboard": false,
}
// GenerateGoCoremodel generates a standard Go coremodel from a Thema lineage.
//
// The provided path must be a directory. Generated code files will be written
// to that path. The final element of the path must match the Lineage.Name().
func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error) {
lin, lib := ls.Lineage, ls.Lineage.Library()
_, name := filepath.Split(path)
if name != lin.Name() {
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", lin.Name(), path)
}
sch := thema.SchemaP(lin, thema.LatestVersion(lin))
f, err := openapi.GenerateSchema(sch, nil)
if err != nil {
return nil, fmt.Errorf("thema openapi generation failed: %w", err)
}
str, err := yaml.Marshal(lib.Context().BuildFile(f))
if err != nil {
return nil, fmt.Errorf("cue-yaml marshaling failed: %w", err)
}
loader := openapi3.NewLoader()
oT, err := loader.LoadFromData([]byte(str))
if err != nil {
return nil, fmt.Errorf("loading generated openapi failed; %w", err)
}
gostr, err := codegen.Generate(oT, lin.Name(), codegen.Options{
GenerateTypes: true,
SkipPrune: true,
SkipFmt: true,
UserTemplates: map[string]string{
"imports.tmpl": fmt.Sprintf(tmplImports, ls.RelativePath),
"typedef.tmpl": tmplTypedef,
},
})
if err != nil {
return nil, fmt.Errorf("openapi generation failed: %w", err)
}
vars := goPkg{
Name: lin.Name(),
LineagePath: ls.RelativePath,
LatestSeqv: sch.Version()[0],
LatestSchv: sch.Version()[1],
}
var buuf bytes.Buffer
err = tmplAddenda.Execute(&buuf, vars)
if err != nil {
panic(err)
}
fset := token.NewFileSet()
gf, err := parser.ParseFile(fset, "coremodel_gen.go", gostr+buuf.String(), parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("generated go file parsing failed: %w", err)
}
m := makeReplacer(lin.Name())
ast.Walk(m, gf)
var buf bytes.Buffer
err = format.Node(&buf, fset, gf)
if err != nil {
return nil, fmt.Errorf("ast printing failed: %w", err)
}
byt, err := imports.Process("coremodel_gen.go", buf.Bytes(), nil)
if err != nil {
return nil, fmt.Errorf("goimports processing failed: %w", err)
}
// Generate the assignability test. TODO do this in a framework test instead
var buf3 bytes.Buffer
err = tmplAssignableTest.Execute(&buf3, vars)
if err != nil {
return nil, fmt.Errorf("failed generating assignability test file: %w", err)
}
wd := NewWriteDiffer()
wd[filepath.Join(path, "coremodel_gen.go")] = byt
wd[filepath.Join(path, "coremodel_gen_test.go")] = buf3.Bytes()
return wd, nil
}
type goPkg struct {
Name string
LineagePath string
LatestSeqv, LatestSchv uint
IsComposed bool
}
func (ls *ExtractedLineage) GenerateTypescriptCoremodel(path string) (WriteDiffer, error) {
_, name := filepath.Split(path)
if name != ls.Lineage.Name() {
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", ls.Lineage.Name(), path)
}
schv := thema.SchemaP(ls.Lineage, thema.LatestVersion(ls.Lineage)).UnwrapCUE()
parts, err := cuetsy.GenerateAST(schv, cuetsy.Config{})
if err != nil {
return nil, fmt.Errorf("cuetsy parts gen failed: %w", err)
}
top, err := cuetsy.GenerateSingleAST(string(makeReplacer(ls.Lineage.Name())), schv, cuetsy.TypeInterface)
if err != nil {
return nil, fmt.Errorf("cuetsy top gen failed: %w", err)
}
// TODO until cuetsy can toposort its outputs, put the top/parent type at the bottom of the file.
parts.Nodes = append(parts.Nodes, top.T, top.D)
// parts.Nodes = append([]ts.Decl{top.T, top.D}, parts.Nodes...)
var strb strings.Builder
var str string
fpath := ls.Lineage.Name() + ".gen.ts"
strb.WriteString(fmt.Sprintf(genHeader, ls.RelativePath))
if !ls.IsCanonical {
fpath = fmt.Sprintf("%s_experimental.gen.ts", ls.Lineage.Name())
strb.WriteString(`
// This model is a WIP and not yet canonical. Consequently, its members are
// not exported to exclude it from grafana-schema's public API surface.
`)
strb.WriteString(fmt.Sprint(parts))
// TODO replace this regexp with cuetsy config for whether members are exported
re := regexp.MustCompile(`(?m)^export `)
str = re.ReplaceAllLiteralString(strb.String(), "")
} else {
strb.WriteString(fmt.Sprint(parts))
str = strb.String()
}
wd := NewWriteDiffer()
wd[filepath.Join(path, fpath)] = []byte(str)
return wd, nil
}
type modelReplacer string
func makeReplacer(name string) modelReplacer {
return modelReplacer(fmt.Sprintf("%s%s", string(strings.ToUpper(name)[0]), name[1:]))
}
func (m modelReplacer) Visit(n ast.Node) ast.Visitor {
switch x := n.(type) {
case *ast.Ident:
x.Name = m.replacePrefix(x.Name)
}
return m
}
func (m modelReplacer) replacePrefix(str string) string {
if len(str) >= len(m) && str[:len(m)] == string(m) {
return strings.Replace(str, string(m), "Model", 1)
}
return str
}
var genHeader = `// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from repository root.
//
// Derived from the Thema lineage at %s
`
var tmplImports = genHeader + `package {{ .PackageName }}
import (
"embed"
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"path/filepath"
"strings"
"time"
"github.com/deepmap/oapi-codegen/pkg/runtime"
openapi_types "github.com/deepmap/oapi-codegen/pkg/types"
"github.com/getkin/kin-openapi/openapi3"
"github.com/grafana/thema"
"github.com/grafana/grafana/pkg/cuectx"
)
`
var tmplAddenda = template.Must(template.New("addenda").Parse(`
//go:embed lineage.cue
var cueFS embed.FS
// codegen ensures that this is always the latest Thema schema version
var currentVersion = thema.SV({{ .LatestSeqv }}, {{ .LatestSchv }})
// Lineage returns the Thema lineage representing a Grafana {{ .Name }}.
//
// The lineage is the canonical specification of the current {{ .Name }} schema,
// all prior schema versions, and the mappings that allow migration between
// schema versions.
{{- if .IsComposed }}//
// This is the base variant of the schema. It does not include any composed
// plugin schemas.{{ end }}
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, lib, opts...)
}
var _ thema.LineageFactory = Lineage
// Coremodel contains the foundational schema declaration for {{ .Name }}s.
type Coremodel struct {
lin thema.Lineage
}
// Lineage returns the canonical dashboard Lineage.
func (c *Coremodel) Lineage() thema.Lineage {
return c.lin
}
// CurrentSchema returns the current (latest) {{ .Name }} Thema schema.
func (c *Coremodel) CurrentSchema() thema.Schema {
return thema.SchemaP(c.lin, currentVersion)
}
// GoType returns a pointer to an empty Go struct that corresponds to
// the current Thema schema.
func (c *Coremodel) GoType() interface{} {
return &Model{}
}
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) {
lin, err := Lineage(lib)
if err != nil {
return nil, err
}
return &Coremodel{
lin: lin,
}, nil
}
`))
var tmplAssignableTest = template.Must(template.New("addenda").Parse(fmt.Sprintf(genHeader, "{{ .LineagePath }}") + `package {{ .Name }}
import (
"testing"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/thema"
)
func TestSchemaAssignability(t *testing.T) {
lin, err := Lineage(cuectx.ProvideThemaLibrary())
if err != nil {
t.Fatal(err)
}
sch := thema.SchemaP(lin, currentVersion)
err = thema.AssignableTo(sch, &Model{})
if err != nil {
t.Fatal(err)
}
}
`))
var tmplTypedef = `{{range .Types}}
{{ with .Schema.Description }}{{ . }}{{ else }}// {{.TypeName}} defines model for {{.JsonName}}.{{ end }}
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}}
{{end}}
`

134
pkg/codegen/diffwrite.go Normal file
View File

@ -0,0 +1,134 @@
package codegen
import (
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-multierror"
"golang.org/x/sync/errgroup"
)
// WriteDiffer is a pseudo-filesystem that supports batch-writing its contents
// to the real filesystem, or batch-comparing its contents to the real
// filesystem. Its intended use is for idiomatic `go generate`-style code
// generators, where it is expected that the results of codegen are committed to
// version control.
//
// In such cases, the normal behavior of a generator is to write files to disk,
// but in CI, that behavior should change to verify that what is already on disk
// is identical to the results of code generation. This allows CI to ensure that
// the results of code generation are always up to date. WriteDiffer supports
// these related behaviors through its Write() and Verify() methods, respectively.
//
// Note that the statelessness of WriteDiffer means that, if a particular input
// to the code generator goes away, it will not notice generated files left
// behind if their inputs are removed.
// TODO introduce a search/match system
type WriteDiffer map[string][]byte
func NewWriteDiffer() WriteDiffer {
return WriteDiffer(make(map[string][]byte))
}
type writeSlice []struct {
path string
contents []byte
}
// Verify checks the contents of each file against the filesystem. It emits an error
// if any of its contained files differ.
func (wd WriteDiffer) Verify() error {
var result error
for _, item := range wd.toSlice() {
if _, err := os.Stat(item.path); err != nil {
if errors.Is(err, os.ErrNotExist) {
result = multierror.Append(result, fmt.Errorf("%s: generated file should exist, but does not", item.path))
} else {
result = multierror.Append(result, fmt.Errorf("%s: could not stat generated file: %w", item.path, err))
}
continue
}
f, err := os.Open(filepath.Clean(item.path))
if err != nil {
result = multierror.Append(result, fmt.Errorf("%s: %w", item.path, err))
continue
}
ob, err := io.ReadAll(f)
if err != nil {
result = multierror.Append(result, fmt.Errorf("%s: %w", item.path, err))
continue
}
dstr := cmp.Diff(string(ob), string(item.contents))
if dstr != "" {
result = multierror.Append(result, fmt.Errorf("%s would have changed:\n\n%s", item.path, dstr))
}
}
return result
}
// Write writes all of the files to their indicated paths.
func (wd WriteDiffer) Write() error {
g, _ := errgroup.WithContext(context.TODO())
g.SetLimit(12)
for _, item := range wd.toSlice() {
it := item
g.Go(func() error {
err := os.MkdirAll(filepath.Dir(it.path), os.ModePerm)
if err != nil {
return fmt.Errorf("%s: failed to ensure parent directory exists: %w", it.path, err)
}
if err := os.WriteFile(it.path, it.contents, 0644); err != nil {
return fmt.Errorf("%s: error while writing file: %w", it.path, err)
}
return nil
})
}
return g.Wait()
}
func (wd WriteDiffer) toSlice() writeSlice {
sl := make(writeSlice, 0, len(wd))
type ws struct {
path string
contents []byte
}
for k, v := range wd {
sl = append(sl, ws{
path: k,
contents: v,
})
}
sort.Slice(sl, func(i, j int) bool {
return sl[i].path < sl[j].path
})
return sl
}
// Merge combines all the entries from the provided WriteDiffer into the callee
// WriteDiffer. Duplicate paths result in an error.
func (wd WriteDiffer) Merge(wd2 WriteDiffer) error {
for k, v := range wd2 {
if _, has := wd[k]; has {
return fmt.Errorf("path %s already exists in write differ", k)
}
wd[k] = v
}
return nil
}

343
pkg/codegen/pluggen.go Normal file
View File

@ -0,0 +1,343 @@
package codegen
import (
"bytes"
gerrors "errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"testing/fstest"
"text/template"
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/errors"
cload "cuelang.org/go/cue/load"
"cuelang.org/go/cue/parser"
"github.com/grafana/cuetsy"
"github.com/grafana/grafana/pkg/schema/load"
)
// The only import statement we currently allow in any models.cue file
const allowedImport = "github.com/grafana/grafana/packages/grafana-schema/src/schema"
var importMap = map[string]string{
allowedImport: "@grafana/schema",
}
// Hard-coded list of paths to skip. Remove a particular file as we're ready
// to rely on the TypeScript auto-generated by cuetsy for that particular file.
var skipPaths = []string{
"public/app/plugins/panel/barchart/models.cue",
"public/app/plugins/panel/canvas/models.cue",
"public/app/plugins/panel/histogram/models.cue",
"public/app/plugins/panel/heatmap-new/models.cue",
"public/app/plugins/panel/candlestick/models.cue",
"public/app/plugins/panel/state-timeline/models.cue",
"public/app/plugins/panel/status-history/models.cue",
"public/app/plugins/panel/table/models.cue",
"public/app/plugins/panel/timeseries/models.cue",
}
const prefix = "/"
var paths = load.GetDefaultLoadPaths()
// CuetsifyPlugins runs cuetsy against plugins' models.cue files.
func CuetsifyPlugins(ctx *cue.Context, root string) (WriteDiffer, error) {
// TODO this whole func has a lot of old, crufty behavior from the scuemata era; needs TLC
var fspaths load.BaseLoadPaths
var err error
fspaths.BaseCueFS, err = populateMapFSFromRoot(paths.BaseCueFS, root, "")
if err != nil {
return nil, err
}
fspaths.DistPluginCueFS, err = populateMapFSFromRoot(paths.DistPluginCueFS, root, "")
if err != nil {
return nil, err
}
overlay, err := defaultOverlay(fspaths)
if err != nil {
return nil, err
}
// Prep the cue load config
clcfg := &cload.Config{
Overlay: overlay,
// FIXME these module paths won't work for things not under our cue.mod - AKA third-party plugins
ModuleRoot: prefix,
Module: "github.com/grafana/grafana",
}
// FIXME hardcoding paths to exclude is not the way to handle this
excl := map[string]bool{
"cue.mod": true,
"cue/scuemata": true,
"packages/grafana-schema/src/scuemata/dashboard": true,
"packages/grafana-schema/src/scuemata/dashboard/dist": true,
}
exclude := func(path string) bool {
dir := filepath.Dir(path)
if excl[dir] {
return true
}
for _, p := range skipPaths {
if path == p {
return true
}
}
return false
}
outfiles := NewWriteDiffer()
cuetsify := func(in fs.FS) error {
seen := make(map[string]bool)
return fs.WalkDir(in, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
dir := filepath.Dir(path)
if d.IsDir() || filepath.Ext(d.Name()) != ".cue" || seen[dir] || exclude(path) {
return nil
}
seen[dir] = true
clcfg.Dir = filepath.Join(root, dir)
// FIXME Horrible hack to figure out the identifier used for
// imported packages - intercept the parser called by the loader to
// look at the ast.Files on their way in to building.
// Much better if we could work backwards from the cue.Value,
// maybe even directly in cuetsy itself, and figure out when a
// referenced object is "out of bounds".
// var imports sync.Map
var imports []*ast.ImportSpec
clcfg.ParseFile = func(name string, src interface{}) (*ast.File, error) {
f, err := parser.ParseFile(name, src, parser.ParseComments)
if err != nil {
return nil, err
}
imports = append(imports, f.Imports...)
return f, nil
}
if strings.Contains(path, "public/app/plugins") {
clcfg.Package = "grafanaschema"
} else {
clcfg.Package = ""
}
// FIXME loading in this way causes all files in a dir to be loaded
// as a single cue.Instance or cue.Value, which makes it quite
// difficult to map them _back_ onto the original file and generate
// discrete .gen.ts files for each .cue input. However, going one
// .cue file at a time and passing it as the first arg to
// load.Instances() means that the other files are ignored
// completely, causing references between these files to be
// unresolved, and thus encounter a different kind of error.
insts := cload.Instances(nil, clcfg)
if len(insts) > 1 {
panic("extra instances")
}
bi := insts[0]
v := ctx.BuildInstance(bi)
if v.Err() != nil {
return v.Err()
}
var b []byte
f := &tsFile{}
seen := make(map[string]bool)
// FIXME explicitly mapping path patterns to conversion patterns
// is exactly what we want to avoid
switch {
// panel plugin models.cue files
case strings.Contains(path, "public/app/plugins"):
for _, im := range imports {
ip := strings.Trim(im.Path.Value, "\"")
if ip != allowedImport {
// TODO make a specific error type for this
return errors.Newf(im.Pos(), "import %q not allowed, panel plugins may only import from %q", ip, allowedImport)
}
// TODO this approach will silently swallow the unfixable
// error case where multiple files in the same dir import
// the same package to a different ident
if !seen[ip] {
seen[ip] = true
f.Imports = append(f.Imports, convertImport(im))
}
}
// Extract the latest schema and its version number. (All of this goes away with Thema, whew)
f.V = &tsModver{}
lins := v.LookupPath(cue.ParsePath("Panel.lineages"))
f.V.Lin, _ = lins.Len().Int64()
f.V.Lin = f.V.Lin - 1
schs := lins.LookupPath(cue.MakePath(cue.Index(int(f.V.Lin))))
f.V.Sch, _ = schs.Len().Int64()
f.V.Sch = f.V.Sch - 1
latest := schs.LookupPath(cue.MakePath(cue.Index(int(f.V.Sch))))
b, err = cuetsy.Generate(latest, cuetsy.Config{})
default:
b, err = cuetsy.Generate(v, cuetsy.Config{})
}
if err != nil {
return err
}
f.Body = string(b)
var buf bytes.Buffer
err = tsTemplate.Execute(&buf, f)
outfiles[filepath.Join(root, strings.Replace(path, ".cue", ".gen.ts", -1))] = buf.Bytes()
return err
})
}
err = cuetsify(fspaths.BaseCueFS)
if err != nil {
return nil, gerrors.New(errors.Details(err, nil))
}
err = cuetsify(fspaths.DistPluginCueFS)
if err != nil {
return nil, gerrors.New(errors.Details(err, nil))
}
return outfiles, nil
}
func convertImport(im *ast.ImportSpec) *tsImport {
tsim := &tsImport{
Pkg: importMap[allowedImport],
}
if im.Name != nil && im.Name.String() != "" {
tsim.Ident = im.Name.String()
} else {
sl := strings.Split(im.Path.Value, "/")
final := sl[len(sl)-1]
if idx := strings.Index(final, ":"); idx != -1 {
tsim.Pkg = final[idx:]
} else {
tsim.Pkg = final
}
}
return tsim
}
func defaultOverlay(p load.BaseLoadPaths) (map[string]cload.Source, error) {
overlay := make(map[string]cload.Source)
if err := toOverlay(prefix, p.BaseCueFS, overlay); err != nil {
return nil, err
}
if err := toOverlay(prefix, p.DistPluginCueFS, overlay); err != nil {
return nil, err
}
return overlay, nil
}
func toOverlay(prefix string, vfs fs.FS, overlay map[string]cload.Source) error {
if !filepath.IsAbs(prefix) {
return fmt.Errorf("must provide absolute path prefix when generating cue overlay, got %q", prefix)
}
err := fs.WalkDir(vfs, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
f, err := vfs.Open(path)
if err != nil {
return err
}
defer func(f fs.File) {
err := f.Close()
if err != nil {
return
}
}(f)
b, err := io.ReadAll(f)
if err != nil {
return err
}
overlay[filepath.Join(prefix, path)] = cload.FromBytes(b)
return nil
})
if err != nil {
return err
}
return nil
}
// Helper function that populates an fs.FS by walking over a virtual filesystem,
// and reading files from disk corresponding to each file encountered.
func populateMapFSFromRoot(in fs.FS, root, join string) (fs.FS, error) {
out := make(fstest.MapFS)
err := fs.WalkDir(in, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
// Ignore gosec warning G304. The input set here is necessarily
// constrained to files specified in embed.go
// nolint:gosec
b, err := os.Open(filepath.Join(root, join, path))
if err != nil {
return err
}
byt, err := io.ReadAll(b)
if err != nil {
return err
}
out[path] = &fstest.MapFile{Data: byt}
return nil
})
return out, err
}
type tsFile struct {
V *tsModver
Imports []*tsImport
Body string
}
type tsModver struct {
Lin, Sch int64
}
type tsImport struct {
Ident string
Pkg string
}
var tsTemplate = template.Must(template.New("cuetsygen").Parse(`//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{{range .Imports}}
import * as {{.Ident}} from '{{.Pkg}}';{{end}}
{{if .V}}
export const modelVersion = Object.freeze([{{ .V.Lin }}, {{ .V.Sch }}]);
{{end}}
{{.Body}}`))

View File

@ -0,0 +1,10 @@
package dashboard
// HandoffSchemaVersion is the minimum schemaVersion for dashboards at which the
// Thema-based dashboard schema is possibly valid
//
// schemaVersion is the original version numbering system for dashboards. If a
// dashboard is below this schemaVersion, it is necessary for the frontend
// typescript dashboard migration logic to first run and get it past this
// number, after which Thema can take over.
const HandoffSchemaVersion = 36

View File

@ -1,40 +0,0 @@
package dashboard
import (
"github.com/grafana/thema"
)
// Coremodel contains the foundational schema declaration for dashboards.
type Coremodel struct {
lin thema.Lineage
}
// Lineage returns the canonical dashboard Lineage.
func (c *Coremodel) Lineage() thema.Lineage {
return c.lin
}
func (c *Coremodel) CurrentSchema() thema.Schema {
sch, err := c.lin.Schema(currentVersion)
if err != nil {
// Only reachable if our own schema currentVersion does not exist, which
// can really only happen transitionally during development
panic(err)
}
return sch
}
func (c *Coremodel) GoType() interface{} {
return &model{}
}
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) {
lin, err := Lineage(lib)
if err != nil {
return nil, err
}
return &Coremodel{
lin: lin,
}, nil
}

View File

@ -0,0 +1,751 @@
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from repository root.
//
// Derived from the Thema lineage at pkg/coremodel/dashboard
package dashboard
import (
"embed"
"path/filepath"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/thema"
)
// Defines values for DashboardGraphTooltip.
const (
ModelGraphTooltipN0 ModelGraphTooltip = 0
ModelGraphTooltipN1 ModelGraphTooltip = 1
ModelGraphTooltipN2 ModelGraphTooltip = 2
)
// Defines values for DashboardStyle.
const (
ModelStyleDark ModelStyle = "dark"
ModelStyleLight ModelStyle = "light"
)
// Defines values for DashboardTimezone.
const (
ModelTimezoneBrowser ModelTimezone = "browser"
ModelTimezoneEmpty ModelTimezone = ""
ModelTimezoneUtc ModelTimezone = "utc"
)
// Defines values for DashboardDashboardCursorSync.
const (
ModelDashboardCursorSyncN0 ModelDashboardCursorSync = 0
ModelDashboardCursorSyncN1 ModelDashboardCursorSync = 1
ModelDashboardCursorSyncN2 ModelDashboardCursorSync = 2
)
// Defines values for DashboardDashboardLinkType.
const (
ModelDashboardLinkTypeDashboards ModelDashboardLinkType = "dashboards"
ModelDashboardLinkTypeLink ModelDashboardLinkType = "link"
)
// Defines values for DashboardFieldColorModeId.
const (
ModelFieldColorModeIdContinuousGrYlRd ModelFieldColorModeId = "continuous-GrYlRd"
ModelFieldColorModeIdFixed ModelFieldColorModeId = "fixed"
ModelFieldColorModeIdPaletteClassic ModelFieldColorModeId = "palette-classic"
ModelFieldColorModeIdPaletteSaturated ModelFieldColorModeId = "palette-saturated"
ModelFieldColorModeIdThresholds ModelFieldColorModeId = "thresholds"
)
// Defines values for DashboardFieldColorSeriesByMode.
const (
ModelFieldColorSeriesByModeLast ModelFieldColorSeriesByMode = "last"
ModelFieldColorSeriesByModeMax ModelFieldColorSeriesByMode = "max"
ModelFieldColorSeriesByModeMin ModelFieldColorSeriesByMode = "min"
)
// Defines values for DashboardGraphPanelType.
const (
ModelGraphPanelTypeGraph ModelGraphPanelType = "graph"
)
// Defines values for DashboardHeatmapPanelType.
const (
ModelHeatmapPanelTypeHeatmap ModelHeatmapPanelType = "heatmap"
)
// Defines values for DashboardPanelRepeatDirection.
const (
ModelPanelRepeatDirectionH ModelPanelRepeatDirection = "h"
ModelPanelRepeatDirectionV ModelPanelRepeatDirection = "v"
)
// Defines values for DashboardRowPanelType.
const (
ModelRowPanelTypeRow ModelRowPanelType = "row"
)
// Defines values for DashboardThresholdsConfigMode.
const (
ModelThresholdsConfigModeAbsolute ModelThresholdsConfigMode = "absolute"
ModelThresholdsConfigModePercentage ModelThresholdsConfigMode = "percentage"
)
// Defines values for DashboardThresholdsMode.
const (
ModelThresholdsModeAbsolute ModelThresholdsMode = "absolute"
ModelThresholdsModePercentage ModelThresholdsMode = "percentage"
)
// Defines values for DashboardVariableModelType.
const (
ModelVariableModelTypeAdhoc ModelVariableModelType = "adhoc"
ModelVariableModelTypeConstant ModelVariableModelType = "constant"
ModelVariableModelTypeCustom ModelVariableModelType = "custom"
ModelVariableModelTypeDatasource ModelVariableModelType = "datasource"
ModelVariableModelTypeInterval ModelVariableModelType = "interval"
ModelVariableModelTypeQuery ModelVariableModelType = "query"
ModelVariableModelTypeSystem ModelVariableModelType = "system"
ModelVariableModelTypeTextbox ModelVariableModelType = "textbox"
)
// Defines values for DashboardVariableType.
const (
ModelVariableTypeAdhoc ModelVariableType = "adhoc"
ModelVariableTypeConstant ModelVariableType = "constant"
ModelVariableTypeCustom ModelVariableType = "custom"
ModelVariableTypeDatasource ModelVariableType = "datasource"
ModelVariableTypeInterval ModelVariableType = "interval"
ModelVariableTypeQuery ModelVariableType = "query"
ModelVariableTypeSystem ModelVariableType = "system"
ModelVariableTypeTextbox ModelVariableType = "textbox"
)
// Dashboard defines model for dashboard.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type Model struct {
Annotations *struct {
List []ModelAnnotationQuery `json:"list"`
} `json:"annotations,omitempty"`
// Description of dashboard.
Description *string `json:"description,omitempty"`
// Whether a dashboard is editable or not.
Editable bool `json:"editable"`
// TODO docs
FiscalYearStartMonth *int `json:"fiscalYearStartMonth,omitempty"`
GnetId *string `json:"gnetId,omitempty"`
GraphTooltip ModelGraphTooltip `json:"graphTooltip"`
// Unique numeric identifier for the dashboard.
// TODO must isolate or remove identifiers local to a Grafana instance...?
Id *int64 `json:"id,omitempty"`
// TODO docs
Links *[]ModelDashboardLink `json:"links,omitempty"`
// TODO docs
LiveNow *bool `json:"liveNow,omitempty"`
Panels *[]interface{} `json:"panels,omitempty"`
// TODO docs
Refresh *interface{} `json:"refresh,omitempty"`
// Version of the JSON schema, incremented each time a Grafana update brings
// changes to said schema.
// TODO this is the existing schema numbering system. It will be replaced by Thema's themaVersion
SchemaVersion int `json:"schemaVersion"`
// Theme of dashboard.
Style ModelStyle `json:"style"`
// Tags associated with dashboard.
Tags *[]string `json:"tags,omitempty"`
Templating *struct {
List []ModelVariableModel `json:"list"`
} `json:"templating,omitempty"`
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc
Time *struct {
From string `json:"from"`
To string `json:"to"`
} `json:"time,omitempty"`
// TODO docs
// TODO this appears to be spread all over in the frontend. Concepts will likely need tidying in tandem with schema changes
Timepicker *struct {
// Whether timepicker is collapsed or not.
Collapse bool `json:"collapse"`
// Whether timepicker is enabled or not.
Enable bool `json:"enable"`
// Whether timepicker is visible or not.
Hidden bool `json:"hidden"`
// Selectable intervals for auto-refresh.
RefreshIntervals []string `json:"refresh_intervals"`
} `json:"timepicker,omitempty"`
// Timezone of dashboard,
Timezone *ModelTimezone `json:"timezone,omitempty"`
// Title of dashboard.
Title *string `json:"title,omitempty"`
// Unique dashboard identifier that can be generated by anyone. string (8-40)
Uid *string `json:"uid,omitempty"`
// Version of the dashboard, incremented each time the dashboard is updated.
Version *int `json:"version,omitempty"`
// TODO docs
WeekStart *string `json:"weekStart,omitempty"`
}
// DashboardGraphTooltip defines model for Dashboard.GraphTooltip.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelGraphTooltip int
// Theme of dashboard.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelStyle string
// Timezone of dashboard,
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelTimezone string
// TODO docs
// FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelAnnotationQuery struct {
BuiltIn int `json:"builtIn"`
// Datasource to use for annotation.
Datasource struct {
Type *string `json:"type,omitempty"`
Uid *string `json:"uid,omitempty"`
} `json:"datasource"`
// Whether annotation is enabled.
Enable bool `json:"enable"`
// Whether to hide annotation.
Hide *bool `json:"hide,omitempty"`
// Annotation icon color.
IconColor *string `json:"iconColor,omitempty"`
// Name of annotation.
Name *string `json:"name,omitempty"`
// Query for annotation data.
RawQuery *string `json:"rawQuery,omitempty"`
ShowIn int `json:"showIn"`
// Schema for panel targets is specified by datasource
// plugins. We use a placeholder definition, which the Go
// schema loader either left open/as-is with the Base
// variant of the Dashboard and Panel families, or filled
// with types derived from plugins in the Instance variant.
// When working directly from CUE, importers can extend this
// type directly to achieve the same effect.
Target *ModelTarget `json:"target,omitempty"`
Type string `json:"type"`
}
// 0 for no shared crosshair or tooltip (default).
// 1 for shared crosshair.
// 2 for shared crosshair AND shared tooltip.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelDashboardCursorSync int
// FROM public/app/features/dashboard/state/DashboardModels.ts - ish
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelDashboardLink struct {
AsDropdown bool `json:"asDropdown"`
Icon *string `json:"icon,omitempty"`
IncludeVars bool `json:"includeVars"`
KeepTime bool `json:"keepTime"`
Tags []string `json:"tags"`
TargetBlank bool `json:"targetBlank"`
Title string `json:"title"`
Tooltip *string `json:"tooltip,omitempty"`
Type ModelDashboardLinkType `json:"type"`
Url *string `json:"url,omitempty"`
}
// DashboardDashboardLinkType defines model for DashboardDashboardLink.Type.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelDashboardLinkType string
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelFieldColor struct {
// Stores the fixed color value if mode is fixed
FixedColor *string `json:"fixedColor,omitempty"`
// The main color scheme mode
Mode interface{} `json:"mode"`
// TODO docs
SeriesBy *ModelFieldColorSeriesByMode `json:"seriesBy,omitempty"`
}
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelFieldColorModeId string
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelFieldColorSeriesByMode string
// DashboardGraphPanel defines model for dashboard.GraphPanel.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelGraphPanel struct {
// Support for legacy graph and heatmap panels.
Type ModelGraphPanelType `json:"type"`
}
// Support for legacy graph and heatmap panels.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelGraphPanelType string
// DashboardHeatmapPanel defines model for dashboard.HeatmapPanel.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelHeatmapPanel struct {
Type ModelHeatmapPanelType `json:"type"`
}
// DashboardHeatmapPanelType defines model for DashboardHeatmapPanel.Type.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelHeatmapPanelType string
// Dashboard panels. Panels are canonically defined inline
// because they share a version timeline with the dashboard
// schema; they do not evolve independently.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelPanel struct {
// The datasource used in all targets.
Datasource *struct {
Type *string `json:"type,omitempty"`
Uid *string `json:"uid,omitempty"`
} `json:"datasource,omitempty"`
// Description.
Description *string `json:"description,omitempty"`
FieldConfig struct {
Defaults struct {
// TODO docs
Color *ModelFieldColor `json:"color,omitempty"`
// custom is specified by the PanelFieldConfig field
// in panel plugin schemas.
Custom *map[string]interface{} `json:"custom,omitempty"`
// Significant digits (for display)
Decimals *float32 `json:"decimals,omitempty"`
// Human readable field metadata
Description *string `json:"description,omitempty"`
// The display value for this field. This supports template variables blank is auto
DisplayName *string `json:"displayName,omitempty"`
// This can be used by data sources that return and explicit naming structure for values and labels
// When this property is configured, this value is used rather than the default naming strategy.
DisplayNameFromDS *string `json:"displayNameFromDS,omitempty"`
// True if data source field supports ad-hoc filters
Filterable *bool `json:"filterable,omitempty"`
// // The behavior when clicking on a result
Links *[]interface{} `json:"links,omitempty"`
// Convert input values into a display string
//
// TODO this one corresponds to a complex type with
// generics on the typescript side. Ouch. Will
// either need special care, or we'll just need to
// accept a very loosely specified schema. It's very
// unlikely we'll be able to translate cue to
// typescript generics in the general case, though
// this particular one *may* be able to work.
Mappings *[]map[string]interface{} `json:"mappings,omitempty"`
Max *float32 `json:"max,omitempty"`
Min *float32 `json:"min,omitempty"`
// Alternative to empty string
NoValue *string `json:"noValue,omitempty"`
// An explict path to the field in the datasource. When the frame meta includes a path,
// This will default to `${frame.meta.path}/${field.name}
//
// When defined, this value can be used as an identifier within the datasource scope, and
// may be used to update the results
Path *string `json:"path,omitempty"`
Thresholds *ModelThresholdsConfig `json:"thresholds,omitempty"`
// Numeric Options
Unit *string `json:"unit,omitempty"`
// True if data source can write a value to the path. Auth/authz are supported separately
Writeable *bool `json:"writeable,omitempty"`
} `json:"defaults"`
Overrides []struct {
Matcher struct {
Id string `json:"id"`
Options *interface{} `json:"options,omitempty"`
} `json:"matcher"`
Properties []struct {
Id string `json:"id"`
Value *interface{} `json:"value,omitempty"`
} `json:"properties"`
} `json:"overrides"`
} `json:"fieldConfig"`
// Grid position.
GridPos *struct {
// Panel
H int `json:"h"`
// true if fixed
Static *bool `json:"static,omitempty"`
// Panel
W int `json:"w"`
// Panel x
X int `json:"x"`
// Panel y
Y int `json:"y"`
} `json:"gridPos,omitempty"`
// TODO docs
Id *int `json:"id,omitempty"`
// TODO docs
// TODO tighter constraint
Interval *string `json:"interval,omitempty"`
// Panel links.
// TODO fill this out - seems there are a couple variants?
Links *[]ModelDashboardLink `json:"links,omitempty"`
// TODO docs
MaxDataPoints *float32 `json:"maxDataPoints,omitempty"`
// options is specified by the PanelOptions field in panel
// plugin schemas.
Options map[string]interface{} `json:"options"`
// FIXME this almost certainly has to be changed in favor of scuemata versions
PluginVersion *string `json:"pluginVersion,omitempty"`
// Name of template variable to repeat for.
Repeat *string `json:"repeat,omitempty"`
// Direction to repeat in if 'repeat' is set.
// "h" for horizontal, "v" for vertical.
RepeatDirection ModelPanelRepeatDirection `json:"repeatDirection"`
// TODO docs
Tags *[]string `json:"tags,omitempty"`
// TODO docs
Targets *[]ModelTarget `json:"targets,omitempty"`
// TODO docs
Thresholds *[]interface{} `json:"thresholds,omitempty"`
// TODO docs
// TODO tighter constraint
TimeFrom *string `json:"timeFrom,omitempty"`
// TODO docs
TimeRegions *[]interface{} `json:"timeRegions,omitempty"`
// TODO docs
// TODO tighter constraint
TimeShift *string `json:"timeShift,omitempty"`
// Panel title.
Title *string `json:"title,omitempty"`
Transformations []struct {
Id string `json:"id"`
Options map[string]interface{} `json:"options"`
} `json:"transformations"`
// Whether to display the panel without a background.
Transparent bool `json:"transparent"`
// The panel plugin type id. May not be empty.
Type string `json:"type"`
}
// Direction to repeat in if 'repeat' is set.
// "h" for horizontal, "v" for vertical.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelPanelRepeatDirection string
// Row panel
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelRowPanel struct {
Collapsed bool `json:"collapsed"`
// Name of default datasource.
Datasource *struct {
Type *string `json:"type,omitempty"`
Uid *string `json:"uid,omitempty"`
} `json:"datasource,omitempty"`
GridPos *struct {
// Panel
H int `json:"h"`
// true if fixed
Static *bool `json:"static,omitempty"`
// Panel
W int `json:"w"`
// Panel x
X int `json:"x"`
// Panel y
Y int `json:"y"`
} `json:"gridPos,omitempty"`
Id int `json:"id"`
Panels []interface{} `json:"panels"`
// Name of template variable to repeat for.
Repeat *string `json:"repeat,omitempty"`
Title *string `json:"title,omitempty"`
Type ModelRowPanelType `json:"type"`
}
// DashboardRowPanelType defines model for DashboardRowPanel.Type.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelRowPanelType string
// Schema for panel targets is specified by datasource
// plugins. We use a placeholder definition, which the Go
// schema loader either left open/as-is with the Base
// variant of the Dashboard and Panel families, or filled
// with types derived from plugins in the Instance variant.
// When working directly from CUE, importers can extend this
// type directly to achieve the same effect.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelTarget map[string]interface{}
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelThreshold struct {
// TODO docs
Color string `json:"color"`
// TODO docs
// TODO are the values here enumerable into a disjunction?
// Some seem to be listed in typescript comment
State *string `json:"state,omitempty"`
// TODO docs
// FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON
Value *float32 `json:"value,omitempty"`
}
// DashboardThresholdsConfig defines model for dashboard.ThresholdsConfig.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelThresholdsConfig struct {
Mode ModelThresholdsConfigMode `json:"mode"`
// Must be sorted by 'value', first value is always -Infinity
Steps []struct {
// TODO docs
Color string `json:"color"`
// TODO docs
// TODO are the values here enumerable into a disjunction?
// Some seem to be listed in typescript comment
State *string `json:"state,omitempty"`
// TODO docs
// FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON
Value *float32 `json:"value,omitempty"`
} `json:"steps"`
}
// DashboardThresholdsConfigMode defines model for DashboardThresholdsConfig.Mode.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelThresholdsConfigMode string
// DashboardThresholdsMode defines model for dashboard.ThresholdsMode.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelThresholdsMode string
// TODO docs
// FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelTransformation struct {
Id string `json:"id"`
Options map[string]interface{} `json:"options"`
}
// FROM: packages/grafana-data/src/types/templateVars.ts
// TODO docs
// TODO what about what's in public/app/features/types.ts?
// TODO there appear to be a lot of different kinds of [template] vars here? if so need a disjunction
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelVariableModel struct {
Label *string `json:"label,omitempty"`
Name string `json:"name"`
Type ModelVariableModelType `json:"type"`
}
// DashboardVariableModelType defines model for DashboardVariableModel.Type.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelVariableModelType string
// FROM: packages/grafana-data/src/types/templateVars.ts
// TODO docs
// TODO this implies some wider pattern/discriminated union, probably?
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ModelVariableType string
//go:embed lineage.cue
var cueFS embed.FS
// codegen ensures that this is always the latest Thema schema version
var currentVersion = thema.SV(0, 0)
// Lineage returns the Thema lineage representing a Grafana dashboard.
//
// The lineage is the canonical specification of the current dashboard schema,
// all prior schema versions, and the mappings that allow migration between
// schema versions.
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, lib, opts...)
}
var _ thema.LineageFactory = Lineage
// Coremodel contains the foundational schema declaration for dashboards.
type Coremodel struct {
lin thema.Lineage
}
// Lineage returns the canonical dashboard Lineage.
func (c *Coremodel) Lineage() thema.Lineage {
return c.lin
}
// CurrentSchema returns the current (latest) dashboard Thema schema.
func (c *Coremodel) CurrentSchema() thema.Schema {
return thema.SchemaP(c.lin, currentVersion)
}
// GoType returns a pointer to an empty Go struct that corresponds to
// the current Thema schema.
func (c *Coremodel) GoType() interface{} {
return &Model{}
}
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) {
lin, err := Lineage(lib)
if err != nil {
return nil, err
}
return &Coremodel{
lin: lin,
}, nil
}

View File

@ -0,0 +1,28 @@
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from repository root.
//
// Derived from the Thema lineage at pkg/coremodel/dashboard
package dashboard
import (
"testing"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/thema"
)
func TestSchemaAssignability(t *testing.T) {
lin, err := Lineage(cuectx.ProvideThemaLibrary())
if err != nil {
t.Fatal(err)
}
sch := thema.SchemaP(lin, currentVersion)
err = thema.AssignableTo(sch, &Model{})
if err != nil {
t.Fatal(err)
}
}

View File

@ -136,7 +136,7 @@ seqs: [
#VariableType: "query" | "adhoc" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" @cuetsy(kind="type")
// TODO docs
#FieldColorModeId: "thresholds" | "palette-classic" | "palette-saturated" | "continuous-GrYlRd" | "fixed" @cuetsy(kind="enum")
#FieldColorModeId: "thresholds" | "palette-classic" | "palette-saturated" | "continuous-GrYlRd" | "fixed" @cuetsy(kind="enum",memberNames="Thresholds|PaletteClassic|PaletteSaturated|ContinuousGrYlRd|Fixed")
// TODO docs
#FieldColorSeriesByMode: "min" | "max" | "last" @cuetsy(kind="type")

View File

@ -1,117 +0,0 @@
package dashboard
import (
"embed"
"path/filepath"
"github.com/grafana/thema"
"github.com/grafana/grafana/pkg/cuectx"
)
var (
//go:embed lineage.cue
cueFS embed.FS
// TODO: this should be generated by Thema.
currentVersion = thema.SV(0, 0)
)
// HandoffSchemaVersion is the minimum schemaVersion for dashboards at which the
// Thema-based dashboard schema is known to be valid.
//
// schemaVersion is the original version numbering system for dashboards. If a
// dashboard is below this schemaVersion, it is necessary for the frontend
// typescript dashboard migration logic to first run and get it past this
// number, after which Thema can take over.
const HandoffSchemaVersion = 36
// Lineage returns the Thema lineage representing Grafana dashboards. The
// lineage is the canonical specification of the current datasource schema, all
// prior schema versions, and the mappings that allow migration between schema
// versions.
//
// This is the base variant of the schema, which does not include any composed
// plugin schemas.
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) {
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, lib, opts...)
}
// Model is a dummy struct stand-in for dashboards.
//
// It exists solely to trick compgen into accepting the dashboard coremodel as valid.
type Model struct{}
// model is a hacky Go struct representing a dashboard.
//
// This exists solely because the coremodel framework enforces that there is a Go struct to which
// all valid Thema schema instances can be assigned, per Thema's assignability checker. See
// https://github.com/grafana/thema/blob/main/docs/invariants.md#go-assignability for rules.
//
// DO NOT RELY ON THIS FOR ANYTHING REAL. It is unclear whether we will ever attempt to have a correct, complete
// Go struct representation of dashboards, let alone compress it into a single struct.
type model struct {
Title string `json:"title"`
Description string `json:"description"`
GnetId string `json:"gnetId"`
Tags []string `json:"tags"`
Style string `json:"style"`
Timezone string `json:"timezone"`
Editable bool `json:"editable"`
GraphTooltip uint8 `json:"graphTooltip"`
Time struct {
From string `json:"from"`
To string `json:"to"`
} `json:"time"`
Timepicker struct {
Collapse bool `json:"collapse"`
Enable bool `json:"enable"`
Hidden bool `json:"hidden"`
RefreshIntervals []string `json:"refresh_intervals"`
} `json:"timepicker"`
Templating struct {
List []interface{} `json:"list"`
} `json:"templating"`
Annotations struct {
List []struct {
Name string `json:"name"`
Type string `json:"type"`
BuiltIn uint8 `json:"builtIn"`
Datasource struct {
Type string `json:"type"`
Uid string `json:"uid"`
} `json:"datasource"`
Enable bool `json:"enable"`
Hide bool `json:"hide,omitempty"`
IconColor string `json:"iconColor"`
RawQuery string `json:"rawQuery,omitempty"`
ShowIn int `json:"showIn"`
Target interface{} `json:"target"`
} `json:"list"`
} `json:"annotations"`
Refresh interface{} `json:"refresh"` // (bool|string)
SchemaVersion int `json:"schemaVersion"`
Links []struct {
Title string `json:"title"`
Type string `json:"type"`
Icon string `json:"icon,omitempty"`
Tooltip string `json:"tooltip,omitempty"`
Url string `json:"url,omitempty"`
Tags []string `json:"tags"`
AsDropdown bool `json:"asDropdown"`
TargetBlank bool `json:"targetBlank"`
IncludeVars bool `json:"includeVars"`
KeepTime bool `json:"keepTime"`
} `json:"links"`
Panels []interface{} `json:"panels"`
FiscalYearStartMonth uint8 `json:"fiscalYearStartMonth"`
LiveNow bool `json:"liveNow"`
WeekStart string `json:"weekStart"`
// //
Uid string `json:"uid"`
// OrgId int64 `json:"orgId"`
Id int64 `json:"id,omitempty"`
Version int `json:"version"`
}

View File

@ -0,0 +1,93 @@
// go:build ignore
//go:build ignore
// +build ignore
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"cuelang.org/go/cue/cuecontext"
gcgen "github.com/grafana/grafana/pkg/codegen"
"github.com/grafana/thema"
)
var lib = thema.NewLibrary(cuecontext.New())
const sep = string(filepath.Separator)
// Generate Go and Typescript implementations for all coremodels, and populate the
// coremodel static registry.
func main() {
if len(os.Args) > 1 {
fmt.Fprintf(os.Stderr, "coremodel code generator does not currently accept any arguments\n, got %q", os.Args)
os.Exit(1)
}
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "could not get working directory: %s", err)
os.Exit(1)
}
// TODO this binds us to only having coremodels in a single directory. If we need more, compgen is the way
grootp := strings.Split(cwd, sep)
groot := filepath.Join(sep, filepath.Join(grootp[:len(grootp)-3]...))
cmroot := filepath.Join(groot, "pkg", "coremodel")
tsroot := filepath.Join(groot, "packages", "grafana-schema", "src", "schema")
items, err := ioutil.ReadDir(cmroot)
if err != nil {
fmt.Fprintf(os.Stderr, "could not read coremodels parent dir %s: %s\n", cmroot, err)
os.Exit(1)
}
var lins []*gcgen.ExtractedLineage
for _, item := range items {
if item.IsDir() {
lin, err := gcgen.ExtractLineage(filepath.Join(cmroot, item.Name(), "lineage.cue"), lib)
if err != nil {
fmt.Fprintf(os.Stderr, "could not process coremodel dir %s: %s\n", cmroot, err)
os.Exit(1)
}
lins = append(lins, lin)
}
}
wd := gcgen.NewWriteDiffer()
for _, ls := range lins {
wdg, err := ls.GenerateGoCoremodel(filepath.Join(cmroot, ls.Lineage.Name()))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate Go for %s: %s\n", ls.Lineage.Name(), err)
os.Exit(1)
}
wd.Merge(wdg)
wdt, err := ls.GenerateTypescriptCoremodel(filepath.Join(tsroot, ls.Lineage.Name()))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate TypeScript for %s: %s\n", ls.Lineage.Name(), err)
os.Exit(1)
}
wd.Merge(wdt)
}
if _, set := os.LookupEnv("CODEGEN_VERIFY"); set {
err = wd.Verify()
if err != nil {
fmt.Fprintf(os.Stderr, "generated code is not up to date:\n%s\nrun `make gen-cue` to regenerate\n\n", err)
os.Exit(1)
}
} else {
err = wd.Write()
if err != nil {
fmt.Fprintf(os.Stderr, "error while writing generated code to disk:\n%s\n", err)
os.Exit(1)
}
}
}

View File

@ -1,5 +1,9 @@
package coremodel
// Generates all code derived from coremodel Thema lineages that's used directly
// by both the frontend and backend.
//go:generate go run gen.go
import (
"github.com/grafana/thema"
)

63
public/app/plugins/gen.go Normal file
View File

@ -0,0 +1,63 @@
// go:build ignore
//go:build ignore
// +build ignore
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"cuelang.org/go/cue/cuecontext"
"github.com/grafana/grafana/pkg/codegen"
)
// Generate TypeScript for all plugin models.cue
func main() {
if len(os.Args) > 1 {
fmt.Fprintf(os.Stderr, "plugin thema code generator does not currently accept any arguments\n, got %q", os.Args)
os.Exit(1)
}
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "could not get working directory: %s", err)
os.Exit(1)
}
var find func(path string) (string, error)
find = func(path string) (string, error) {
parent := filepath.Dir(path)
if parent == path {
return "", errors.New("grafana root directory could not be found")
}
fp := filepath.Join(path, "go.mod")
if _, err := os.Stat(fp); err == nil {
return path, nil
}
return find(parent)
}
groot, err := find(cwd)
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
wd, err := codegen.CuetsifyPlugins(cuecontext.New(), groot)
if _, set := os.LookupEnv("CODEGEN_VERIFY"); set {
err = wd.Verify()
if err != nil {
fmt.Fprintf(os.Stderr, "generated code is out of sync with inputs:\n%s\nrun `make gen-cue` to regenerate\n\n", err)
os.Exit(1)
}
} else {
err = wd.Write()
if err != nil {
fmt.Fprintf(os.Stderr, "error while writing generated code to disk:\n%s\n", err)
os.Exit(1)
}
}
}

View File

@ -0,0 +1,3 @@
package plugins
//go:generate go run gen.go

View File

@ -1,5 +1,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy. DO NOT EDIT!
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,5 +1,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy. DO NOT EDIT!
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import * as ui from '@grafana/schema';

View File

@ -1,5 +1,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy. DO NOT EDIT!
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,5 +1,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy. DO NOT EDIT!
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import * as ui from '@grafana/schema';

View File

@ -1,5 +1,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy. DO NOT EDIT!
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,5 +1,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy. DO NOT EDIT!
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import * as ui from '@grafana/schema';

View File

@ -1,5 +1,7 @@
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file was autogenerated by cuetsy. DO NOT EDIT!
// This file is autogenerated. DO NOT EDIT.
//
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -41,6 +41,7 @@ load(
'upload_cdn_step',
'validate_scuemata_step',
'ensure_cuetsified_step',
'verify_gen_cue_step',
'test_a11y_frontend_step',
'trigger_oss'
)
@ -136,6 +137,7 @@ def get_steps(edition):
build_plugins_step(edition=edition, sign=True),
validate_scuemata_step(),
ensure_cuetsified_step(),
verify_gen_cue_step(),
]
integration_test_steps = [
postgres_integration_tests_step(edition=edition, ver_mode=ver_mode),

View File

@ -31,6 +31,7 @@ load(
'benchmark_ldap_step',
'validate_scuemata_step',
'ensure_cuetsified_step',
'verify_gen_cue_step',
'test_a11y_frontend_step',
'enterprise_downstream_step',
)
@ -126,6 +127,7 @@ def pr_pipelines(edition):
build_plugins_step(edition=edition),
validate_scuemata_step(),
ensure_cuetsified_step(),
verify_gen_cue_step(),
]
integration_test_steps = [
postgres_integration_tests_step(edition=edition, ver_mode=ver_mode),

View File

@ -41,6 +41,7 @@ load(
'upload_cdn_step',
'validate_scuemata_step',
'ensure_cuetsified_step',
'verify_gen_cue_step',
'publish_images_step',
'trigger_oss'
)
@ -183,6 +184,7 @@ def get_steps(edition, ver_mode):
build_plugins_step(edition=edition, sign=True),
validate_scuemata_step(),
ensure_cuetsified_step(),
verify_gen_cue_step(),
]
integration_test_steps = [

View File

@ -1167,6 +1167,16 @@ def ensure_cuetsified_step():
],
}
def verify_gen_cue_step():
return {
'name': 'verify-gen-cue',
'image': build_image,
'commands': [
'# It is required that code generated from Thema/CUE be committed and in sync with its inputs.',
'# The following command will fail if running code generators produces any diff in output.',
'CODEGEN_VERIFY=1 make gen-cue',
],
}
def end_to_end_tests_deps(edition):
if disable_tests: