diff --git a/.drone.yml b/.drone.yml index 40305f9faf0..be4083094e6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -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 ... diff --git a/.gitignore b/.gitignore index affabc71b2a..708681a5b34 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/Makefile b/Makefile index e4881487e56..fb4e6b4611c 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/go.mod b/go.mod index 821e967d0d9..8990b3cd027 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index fe6d2ae3be3..fefde043168 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/packages/grafana-schema/src/index.ts b/packages/grafana-schema/src/index.ts index e3c6d27dc95..4ba5c03ffa4 100644 --- a/packages/grafana-schema/src/index.ts +++ b/packages/grafana-schema/src/index.ts @@ -3,4 +3,4 @@ * * @packageDocumentation */ -export * from './schema/graph.gen'; +export * from './schema/mudball.gen'; diff --git a/packages/grafana-schema/src/schema/dashboard/dashboard_experimental.gen.ts b/packages/grafana-schema/src/schema/dashboard/dashboard_experimental.gen.ts new file mode 100644 index 00000000000..33850d2d83d --- /dev/null +++ b/packages/grafana-schema/src/schema/dashboard/dashboard_experimental.gen.ts @@ -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 = { + 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 = { + 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 = { + 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 = { + 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 = { + editable: true, + graphTooltip: DashboardCursorSync.Off, + links: [], + panels: [], + schemaVersion: 36, + style: 'dark', + tags: [], + timezone: 'browser', +}; diff --git a/packages/grafana-schema/src/schema/graph.cue b/packages/grafana-schema/src/schema/graph.cue deleted file mode 100644 index cc1abcf37b9..00000000000 --- a/packages/grafana-schema/src/schema/graph.cue +++ /dev/null @@ -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") diff --git a/packages/grafana-schema/src/schema/legend.cue b/packages/grafana-schema/src/schema/legend.cue deleted file mode 100644 index 336ab2324b2..00000000000 --- a/packages/grafana-schema/src/schema/legend.cue +++ /dev/null @@ -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") diff --git a/packages/grafana-schema/src/schema/mudball.cue b/packages/grafana-schema/src/schema/mudball.cue index bf046c2d400..6031fb31c05 100644 --- a/packages/grafana-schema/src/schema/mudball.cue +++ b/packages/grafana-schema/src/schema/mudball.cue @@ -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") diff --git a/packages/grafana-schema/src/schema/graph.gen.ts b/packages/grafana-schema/src/schema/mudball.gen.ts similarity index 89% rename from packages/grafana-schema/src/schema/graph.gen.ts rename to packages/grafana-schema/src/schema/mudball.gen.ts index 770e1469521..c77a9687d3b 100644 --- a/packages/grafana-schema/src/schema/graph.gen.ts +++ b/packages/grafana-schema/src/schema/mudball.gen.ts @@ -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 = { + dash: [], +}; + export interface LineConfig { lineColor?: string; lineInterpolation?: LineInterpolation; @@ -176,6 +183,10 @@ export interface ReduceDataOptions { values?: boolean; } +export const defaultReduceDataOptions: Partial = { + 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 = { + 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 = { align: 'auto', displayMode: TableCellDisplayMode.Auto, inspect: false, diff --git a/packages/grafana-schema/src/schema/table.cue b/packages/grafana-schema/src/schema/table.cue deleted file mode 100644 index e62dbb3d561..00000000000 --- a/packages/grafana-schema/src/schema/table.cue +++ /dev/null @@ -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") diff --git a/packages/grafana-schema/src/schema/text.cue b/packages/grafana-schema/src/schema/text.cue deleted file mode 100644 index d5475387576..00000000000 --- a/packages/grafana-schema/src/schema/text.cue +++ /dev/null @@ -1,8 +0,0 @@ -package schema - -VizTextDisplayOptions: { - // Explicit title text size - titleSize?: number - // Explicit value text size - valueSize?: number -} @cuetsy(kind="interface") diff --git a/packages/grafana-schema/src/schema/tooltip.cue b/packages/grafana-schema/src/schema/tooltip.cue deleted file mode 100644 index 464ba8ef511..00000000000 --- a/packages/grafana-schema/src/schema/tooltip.cue +++ /dev/null @@ -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") diff --git a/pkg/cmd/grafana-cli/commands/cuetsify_command.go b/pkg/cmd/grafana-cli/commands/cuetsify_command.go index ed8deb0f11a..f22fb52c7f4 100644 --- a/pkg/cmd/grafana-cli/commands/cuetsify_command.go +++ b/pkg/cmd/grafana-cli/commands/cuetsify_command.go @@ -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}}`)) diff --git a/pkg/codegen/coremodel.go b/pkg/codegen/coremodel.go new file mode 100644 index 00000000000..ea8cf93ed4b --- /dev/null +++ b/pkg/codegen/coremodel.go @@ -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}} +` diff --git a/pkg/codegen/diffwrite.go b/pkg/codegen/diffwrite.go new file mode 100644 index 00000000000..fb1cece0fcd --- /dev/null +++ b/pkg/codegen/diffwrite.go @@ -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 +} diff --git a/pkg/codegen/pluggen.go b/pkg/codegen/pluggen.go new file mode 100644 index 00000000000..c79e2fb2cbb --- /dev/null +++ b/pkg/codegen/pluggen.go @@ -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}}`)) diff --git a/pkg/coremodel/dashboard/addenda.go b/pkg/coremodel/dashboard/addenda.go new file mode 100644 index 00000000000..5ef913c4c66 --- /dev/null +++ b/pkg/coremodel/dashboard/addenda.go @@ -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 diff --git a/pkg/coremodel/dashboard/coremodel.go b/pkg/coremodel/dashboard/coremodel.go deleted file mode 100644 index ed36c36dc7b..00000000000 --- a/pkg/coremodel/dashboard/coremodel.go +++ /dev/null @@ -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 -} diff --git a/pkg/coremodel/dashboard/coremodel_gen.go b/pkg/coremodel/dashboard/coremodel_gen.go new file mode 100644 index 00000000000..56d19f5494f --- /dev/null +++ b/pkg/coremodel/dashboard/coremodel_gen.go @@ -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 +} diff --git a/pkg/coremodel/dashboard/coremodel_gen_test.go b/pkg/coremodel/dashboard/coremodel_gen_test.go new file mode 100644 index 00000000000..acade7a0c56 --- /dev/null +++ b/pkg/coremodel/dashboard/coremodel_gen_test.go @@ -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) + } +} diff --git a/pkg/coremodel/dashboard/lineage.cue b/pkg/coremodel/dashboard/lineage.cue index 1f58c380440..d622d638066 100644 --- a/pkg/coremodel/dashboard/lineage.cue +++ b/pkg/coremodel/dashboard/lineage.cue @@ -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") diff --git a/pkg/coremodel/dashboard/schema.go b/pkg/coremodel/dashboard/schema.go deleted file mode 100644 index a0a04588d3f..00000000000 --- a/pkg/coremodel/dashboard/schema.go +++ /dev/null @@ -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"` -} diff --git a/pkg/framework/coremodel/gen.go b/pkg/framework/coremodel/gen.go new file mode 100644 index 00000000000..bfe045148e5 --- /dev/null +++ b/pkg/framework/coremodel/gen.go @@ -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) + } + } +} diff --git a/pkg/framework/coremodel/interface.go b/pkg/framework/coremodel/interface.go index 07957dd3b44..e0d64ae7763 100644 --- a/pkg/framework/coremodel/interface.go +++ b/pkg/framework/coremodel/interface.go @@ -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" ) diff --git a/public/app/plugins/gen.go b/public/app/plugins/gen.go new file mode 100644 index 00000000000..cffbe146556 --- /dev/null +++ b/public/app/plugins/gen.go @@ -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) + } + } +} diff --git a/public/app/plugins/igen.go b/public/app/plugins/igen.go new file mode 100644 index 00000000000..ba167576982 --- /dev/null +++ b/public/app/plugins/igen.go @@ -0,0 +1,3 @@ +package plugins + +//go:generate go run gen.go diff --git a/public/app/plugins/panel/annolist/models.gen.ts b/public/app/plugins/panel/annolist/models.gen.ts index 7d846343887..ac6a16414c8 100644 --- a/public/app/plugins/panel/annolist/models.gen.ts +++ b/public/app/plugins/panel/annolist/models.gen.ts @@ -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. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/public/app/plugins/panel/bargauge/models.gen.ts b/public/app/plugins/panel/bargauge/models.gen.ts index e595e3cd0a2..6d860afc96d 100644 --- a/public/app/plugins/panel/bargauge/models.gen.ts +++ b/public/app/plugins/panel/bargauge/models.gen.ts @@ -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'; diff --git a/public/app/plugins/panel/dashlist/models.gen.ts b/public/app/plugins/panel/dashlist/models.gen.ts index bcfa4297921..fe3e61ff34a 100644 --- a/public/app/plugins/panel/dashlist/models.gen.ts +++ b/public/app/plugins/panel/dashlist/models.gen.ts @@ -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. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/public/app/plugins/panel/gauge/models.gen.ts b/public/app/plugins/panel/gauge/models.gen.ts index ce9b2ce4f67..1d20ea1ae00 100644 --- a/public/app/plugins/panel/gauge/models.gen.ts +++ b/public/app/plugins/panel/gauge/models.gen.ts @@ -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'; diff --git a/public/app/plugins/panel/news/models.gen.ts b/public/app/plugins/panel/news/models.gen.ts index 6351ddcbc67..ebc8cd0e145 100644 --- a/public/app/plugins/panel/news/models.gen.ts +++ b/public/app/plugins/panel/news/models.gen.ts @@ -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. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/public/app/plugins/panel/stat/models.gen.ts b/public/app/plugins/panel/stat/models.gen.ts index 6ddf201bff6..bbf3bda964b 100644 --- a/public/app/plugins/panel/stat/models.gen.ts +++ b/public/app/plugins/panel/stat/models.gen.ts @@ -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'; diff --git a/public/app/plugins/panel/text/models.gen.ts b/public/app/plugins/panel/text/models.gen.ts index 3ebbc4da015..71ea4aaebe9 100644 --- a/public/app/plugins/panel/text/models.gen.ts +++ b/public/app/plugins/panel/text/models.gen.ts @@ -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. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/scripts/drone/pipelines/main.star b/scripts/drone/pipelines/main.star index bbff9162515..66d2cc42736 100644 --- a/scripts/drone/pipelines/main.star +++ b/scripts/drone/pipelines/main.star @@ -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), diff --git a/scripts/drone/pipelines/pr.star b/scripts/drone/pipelines/pr.star index cd96747db0d..2dee551749d 100644 --- a/scripts/drone/pipelines/pr.star +++ b/scripts/drone/pipelines/pr.star @@ -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), diff --git a/scripts/drone/pipelines/release.star b/scripts/drone/pipelines/release.star index e85610016b5..9f6b572d8eb 100644 --- a/scripts/drone/pipelines/release.star +++ b/scripts/drone/pipelines/release.star @@ -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 = [ diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index 1f369b422e0..559e058cb9e 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -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: