Create app.go file and define app configuration, custom route and basic handler for that route

This commit is contained in:
alexandra vargas
2025-12-31 13:48:48 +01:00
parent 8e9675ce1c
commit e616d04010
19 changed files with 1239 additions and 283 deletions
+8 -101
View File
@@ -1,102 +1,9 @@
# FIXME: This Makefile was generated by `grafana-app-sdk project init`,
# With all possible pre-made make targets, and should be customized to your project based on your needs.
include ../sdk.mk
SOURCES := $(shell find . -type f -name "*.go")
MOD_FILES := go.mod go.sum
VENDOR := vendor
COVOUT := coverage.out
OPERATOR_DOCKERIMAGE := "dashvalidator"
.PHONY: all
all: deps lint test build
.PHONY: deps
deps: $(VENDOR)
.PHONY: lint
lint:
golangci-lint run --max-same-issues=0 --max-issues-per-linter=0
.PHONY: test
test:
go test -count=1 -cover -covermode=atomic -coverprofile=$(COVOUT) ./...
.PHONY: coverage
coverage: test
go tool cover -html=$(COVOUT)
.PHONY: build
build: build/plugin build/operator
.PHONY: build/plugin
build/plugin: build/plugin-backend build/plugin-frontend
.PHONY: build/plugin-frontend
build/plugin-frontend:
ifeq ("$(wildcard plugin/src/plugin.json)","plugin/src/plugin.json")
@cd plugin && yarn install && yarn build
else
@echo "No plugin.json found, skipping frontend build"
endif
.PHONY: build/plugin-backend
build/plugin-backend:
ifeq ("$(wildcard plugin/Magefile.go)","plugin/Magefile.go")
@cd plugin && mage -v
else
@echo "No Magefile.go found, skipping backend build"
endif
.PHONY: build/operator
build/operator:
docker build -t $(OPERATOR_DOCKERIMAGE) -f cmd/operator/Dockerfile .
.PHONY: compile/operator
compile/operator:
@go build -o "target/operator" cmd/operator/*.go
.PHONY: generate
generate:
@grafana-app-sdk generate --source kinds --format cue --crdmanifest
.PHONY: local/up
local/up: local/generate
@local/scripts/cluster.sh create "local/generated/k3d-config.json"
@cd local && tilt up
.PHONY: local/generate
local/generate:
@grafana-app-sdk project local generate
.PHONY: local/down
local/down:
@cd local && tilt down
.PHONY: local/deploy_plugin
local/deploy_plugin:
-tilt disable grafana
@mkdir -p local/mounted-files/plugin/dist
cp -R plugin/dist/* local/mounted-files/plugin/dist/
-tilt enable grafana
.PHONY: local/push_operator
local/push_operator:
# Tag the docker image as part of localhost, which is what the generated k8s uses to avoid confusion with the real operator image
@docker tag "$(OPERATOR_DOCKERIMAGE):latest" "localhost/$(OPERATOR_DOCKERIMAGE):latest"
@local/scripts/push_image.sh "localhost/$(OPERATOR_DOCKERIMAGE):latest"
.PHONY: local/clean
local/clean: local/down
@local/scripts/cluster.sh delete
.PHONY: clean
clean:
@rm -f $(COVOUT)
@rm -rf $(VENDOR)
.PHONY: $(VENDOR)
$(VENDOR): $(SOURCES) $(MOD_FILES)
@go mod tidy
@go mod vendor
@touch $@
.PHONY: generate # Run Grafana App SDK code generation
generate: install-app-sdk update-app-sdk
@$(APP_SDK_BIN) generate \
--source=./kinds/ \
--gogenpath=./pkg/apis \
--grouping=group \
--defencoding=none
+72 -1
View File
@@ -1,3 +1,74 @@
module github.com/grafana/grafana/apps/dashvalidator
go 1.22
go 1.24.0
require (
github.com/grafana/grafana-app-sdk v0.48.7
k8s.io/apimachinery v0.34.3
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.22.4 // indirect
github.com/go-openapi/jsonreference v0.21.4 // indirect
github.com/go-openapi/swag v0.25.4 // indirect
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
github.com/go-openapi/swag/conv v0.25.4 // indirect
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
github.com/go-openapi/swag/loading v0.25.4 // indirect
github.com/go-openapi/swag/mangling v0.25.4 // indirect
github.com/go-openapi/swag/netutils v0.25.4 // indirect
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/grafana/grafana-app-sdk/logging v0.48.5 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/woodsbury/decimal128 v1.4.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/client-go v0.34.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
+209 -1
View File
@@ -1 +1,209 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo=
github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k=
github.com/grafana/grafana-app-sdk/logging v0.48.5 h1:vWiTZrsSscbC5IQq2heWXm0dXhvg40nXIeUTCdE9qsc=
github.com/grafana/grafana-app-sdk/logging v0.48.5/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4=
k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk=
k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE=
k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A=
k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ=
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=
sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+1 -1
View File
@@ -8,7 +8,7 @@ manifest: {
// By default, an app's API group is LOWER(REPLACE(appName, '-', '')).ext.grafana.com,
// but there are cases where this needs to be changed.
// Keep in mind that changing this after an app is deployed can cause problems with clients and/or kind data.
// groupOverride: foo.ext.grafana.app
groupOverride: "dashvalidator.grafana.app"
// versions is a map of versions supported by your app. Version names should follow the format "v<integer>" or
// "v<integer>(alpha|beta)<integer>". Each version contains the kinds your app manages for that version.
-77
View File
@@ -1,77 +0,0 @@
# version_settings() enforces a minimum Tilt version
# https://docs.tilt.dev/api.html#api.version_settings
version_settings(constraint='>=0.22.2')
def name(c):
return c['metadata']['name']
def namespace(c):
if 'namespace' in c['metadata']:
return c['metadata']['namespace']
return ''
def decode(yaml):
resources = decode_yaml_stream(yaml)
# workaround a bug in decode_yaml_stream where it returns duplicates
# This bug has been fixed in Tilt v0.17.3+
filtered = []
names = {}
for r in resources:
if r == None:
continue
n = '%s:%s:%s' % (name(r), r['kind'], namespace(r))
if n in names:
continue
names[n] = True
filtered.append(r)
return filtered
def get_label(o, lbl):
if 'labels' in o['metadata'] and lbl in o['metadata']['labels']:
return o['metadata']['labels'][lbl]
return ''
def find_overlapping(o, yamls):
for elem in yamls:
if name(o) == name(elem) and o['kind'] == elem['kind'] and namespace(o) == namespace(elem):
return elem
return None
# Parse all YAML files in our "generated" directory
yaml_objects = []
for filename in listdir('generated'):
if filename.lower().endswith(('.yaml', '.yml')):
decoded = decode(read_file(filename))
yaml_objects += decoded
# Next, allow for additional yamls which can optionally override the generated yamls
for filename in listdir('additional'):
if filename.lower().endswith(('.yaml', '.yml')):
decoded = decode(read_file(filename))
for o in decoded:
present = find_overlapping(o, yaml_objects)
if present != None:
yaml_objects.remove(present)
yaml_objects += decoded
bundle = encode_yaml_stream(yaml_objects)
# k8s_yaml automatically creates resources in Tilt for the entities
# and will inject any images referenced in the Tiltfile when deploying
# https://docs.tilt.dev/api.html#api.k8s_yaml
k8s_yaml(bundle)
# Group CRD's together
crds = [r for r in yaml_objects if (r['kind'] == 'CustomResourceDefinition')]
if len(crds) > 0:
k8s_resource(new_name='CustomResourceDefinitions', objects=[('%s' % name(r)) for r in yaml_objects if (r['kind'] == 'CustomResourceDefinition')], resource_deps=['uncategorized'])
# Make webhooks dependent on all services starting
services = [('%s' % name(r)) for r in yaml_objects if r['kind'] == 'Service']
webhooks = [r for r in yaml_objects if (r['kind'] == 'ValidatingWebhookConfiguration' or r['kind'] == 'MutatingWebhookConfiguration')]
if len(webhooks) > 0:
k8s_resource(new_name='Webhooks', objects=[('%s' % name(r)) for r in webhooks], resource_deps=services)
-47
View File
@@ -1,47 +0,0 @@
# Port used to bind services to localhost, for example, grafana will be available at http://grafana.k3d.localhost:9999
port: 9999
# Port used for the kubernetes APIServer on localhost
kubePort: 8556
# Pre-configured datasources to install on grafana. For custom-configuration datasources, use `datasourceConfigs`
datasources:
- cortex
- tempo
- loki
# Plugin JSON data, as key/value pairs
pluginJson:
foo: bar
# Plugin Secure JSON data. By default, the `kubeconfig` and `kubenamespace` values will be added in the generated YAML.
# You can overwrite those values by specifying them here instead.
pluginSecureJson:
baz: foo
# Standalone operator docker image. Leave this empty to not deploy an operator
operatorImage: 'dashvalidator:latest'
# Governs whether the local setup generates kubernetes manifests for varying kinds of webhooks attached to your operator and each CRD
webhooks:
# The port the operator exposes an HTTPS server with the webhook endpoint(s) on
port: 8443
# Non-standard or additional datasources you want to automatically include in grafana's provisioned list
# The actual datasources need to be set up manually (arbitrary kubernetes yamls can be added to the local setup via the 'additional' folder),
# but you can predefine the connection details so they'll be added to the local grafana.
datasourceConfigs:
# Here is an example cortex config
# - access: proxy
# editable: false
# name: "my-cortex-datasource"
# type: prometheus
# uid: "my-cortex-datasource"
# url: "http://cortex.default.svc.cluster.local:9009/api/prom"
#
# Toggle the generating of the grafana deployments, if you want to control these elsewhere
generateGrafanaDeployment: true
# which grafana image to use
grafanaImage: grafana/grafana-enterprise:main
# Install plugins from other sources (URLS). See https://grafana.com/docs/grafana/latest/setup-grafana/configure-docker/#install-plugins-from-other-sources
grafanaInstallPlugins: ''
# You can mount additional volumes from the local disk (aside from the already-mounted ./local/mounted-files) by specifying them here
additionalVolumeMounts:
# - sourcePath: "./local/other" # Paths starting with a ./ or no leading slash are relative to the project root. Start with / for an absolute path
# mountPath: "/tmp/k3d/other" # The destination volume path. Best practice is to use /tmp/k3d as the starting point. The default volume mount for ./local/mounted-files is /tmp/k3d/mounted-files
@@ -1,43 +0,0 @@
#!/usr/bin/env bash
set -eufo pipefail
CLUSTER_NAME="dashvalidator"
create_cluster() {
K3D_CONFIG="${1:-generated/k3d-config.json}"
if ! k3d cluster list "${CLUSTER_NAME}" >/dev/null 2>&1; then
# Array of extra options to add to the k3d cluster create command
EXTRA_K3D_OPTS=()
# Bug in k3d for btrfs filesystems workaround, see https://k3d.io/v5.2.2/faq/faq/#issues-with-btrfs
# Apple is APFS/HFS and stat has a different API, so we might as well skip that
if [[ "${OSTYPE}" != "darwin*" ]]; then
ROOTFS="$(stat -f --format="%T" "/")"
if [[ "${ROOTFS}" == "btrfs" ]]; then
EXTRA_K3D_OPTS+=("-v" "/dev/mapper:/dev/mapper")
fi
fi
k3d cluster create "${CLUSTER_NAME}" --config "${K3D_CONFIG}" ${EXTRA_K3D_OPTS[@]+"${EXTRA_K3D_OPTS[@]}"}
else
echo "Cluster already exists"
fi
}
delete_cluster() {
k3d cluster delete "${CLUSTER_NAME}"
}
if [ $# -lt 1 ]; then
echo "Usage: ./cluster.sh [create|delete]"
exit 1
fi
if [ $1 == "create" ]; then
create_cluster $2
elif [ $1 == "delete" ]; then
delete_cluster
else
echo "Unknown argument ${1}"
fi
@@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -eufo pipefail
CLUSTER_NAME="dashvalidator"
IMAGE="$1"
if [[ "$IMAGE" == "" ]]; then
echo "usage: push_image.sh <image_name:tag>"
exit 1
fi
k3d image import "${IMAGE}" -c "${CLUSTER_NAME}"
@@ -0,0 +1,18 @@
package v1alpha1
import "k8s.io/apimachinery/pkg/runtime/schema"
const (
// APIGroup is the API group used by all kinds in this package
APIGroup = "dashvalidator.grafana.app"
// APIVersion is the API version used by all kinds in this package
APIVersion = "v1alpha1"
)
var (
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
GroupVersion = schema.GroupVersion{
Group: APIGroup,
Version: APIVersion,
}
)
@@ -0,0 +1,99 @@
package v1alpha1
import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type DashboardCompatibilityScoreClient struct {
client *resource.TypedClient[*DashboardCompatibilityScore, *DashboardCompatibilityScoreList]
}
func NewDashboardCompatibilityScoreClient(client resource.Client) *DashboardCompatibilityScoreClient {
return &DashboardCompatibilityScoreClient{
client: resource.NewTypedClient[*DashboardCompatibilityScore, *DashboardCompatibilityScoreList](client, DashboardCompatibilityScoreKind()),
}
}
func NewDashboardCompatibilityScoreClientFromGenerator(generator resource.ClientGenerator) (*DashboardCompatibilityScoreClient, error) {
c, err := generator.ClientFor(DashboardCompatibilityScoreKind())
if err != nil {
return nil, err
}
return NewDashboardCompatibilityScoreClient(c), nil
}
func (c *DashboardCompatibilityScoreClient) Get(ctx context.Context, identifier resource.Identifier) (*DashboardCompatibilityScore, error) {
return c.client.Get(ctx, identifier)
}
func (c *DashboardCompatibilityScoreClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*DashboardCompatibilityScoreList, error) {
return c.client.List(ctx, namespace, opts)
}
func (c *DashboardCompatibilityScoreClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*DashboardCompatibilityScoreList, error) {
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
ResourceVersion: opts.ResourceVersion,
Limit: opts.Limit,
LabelFilters: opts.LabelFilters,
FieldSelectors: opts.FieldSelectors,
})
if err != nil {
return nil, err
}
for resp.GetContinue() != "" {
page, err := c.client.List(ctx, namespace, resource.ListOptions{
Continue: resp.GetContinue(),
ResourceVersion: opts.ResourceVersion,
Limit: opts.Limit,
LabelFilters: opts.LabelFilters,
FieldSelectors: opts.FieldSelectors,
})
if err != nil {
return nil, err
}
resp.SetContinue(page.GetContinue())
resp.SetResourceVersion(page.GetResourceVersion())
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
}
return resp, nil
}
func (c *DashboardCompatibilityScoreClient) Create(ctx context.Context, obj *DashboardCompatibilityScore, opts resource.CreateOptions) (*DashboardCompatibilityScore, error) {
// Make sure apiVersion and kind are set
obj.APIVersion = GroupVersion.Identifier()
obj.Kind = DashboardCompatibilityScoreKind().Kind()
return c.client.Create(ctx, obj, opts)
}
func (c *DashboardCompatibilityScoreClient) Update(ctx context.Context, obj *DashboardCompatibilityScore, opts resource.UpdateOptions) (*DashboardCompatibilityScore, error) {
return c.client.Update(ctx, obj, opts)
}
func (c *DashboardCompatibilityScoreClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*DashboardCompatibilityScore, error) {
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *DashboardCompatibilityScoreClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardCompatibilityScoreStatus, opts resource.UpdateOptions) (*DashboardCompatibilityScore, error) {
return c.client.Update(ctx, &DashboardCompatibilityScore{
TypeMeta: metav1.TypeMeta{
Kind: DashboardCompatibilityScoreKind().Kind(),
APIVersion: GroupVersion.Identifier(),
},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: opts.ResourceVersion,
Namespace: identifier.Namespace,
Name: identifier.Name,
},
Status: newStatus,
}, resource.UpdateOptions{
Subresource: "status",
ResourceVersion: opts.ResourceVersion,
})
}
func (c *DashboardCompatibilityScoreClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}
@@ -0,0 +1,28 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v1alpha1
import (
"encoding/json"
"io"
"github.com/grafana/grafana-app-sdk/resource"
)
// DashboardCompatibilityScoreJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
type DashboardCompatibilityScoreJSONCodec struct{}
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
func (*DashboardCompatibilityScoreJSONCodec) Read(reader io.Reader, into resource.Object) error {
return json.NewDecoder(reader).Decode(into)
}
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
func (*DashboardCompatibilityScoreJSONCodec) Write(writer io.Writer, from resource.Object) error {
return json.NewEncoder(writer).Encode(from)
}
// Interface compliance checks
var _ resource.Codec = &DashboardCompatibilityScoreJSONCodec{}
@@ -0,0 +1,31 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v1alpha1
import (
time "time"
)
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
type DashboardCompatibilityScoreMetadata struct {
UpdateTimestamp time.Time `json:"updateTimestamp"`
CreatedBy string `json:"createdBy"`
Uid string `json:"uid"`
CreationTimestamp time.Time `json:"creationTimestamp"`
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
Finalizers []string `json:"finalizers"`
ResourceVersion string `json:"resourceVersion"`
Generation int64 `json:"generation"`
UpdatedBy string `json:"updatedBy"`
Labels map[string]string `json:"labels"`
}
// NewDashboardCompatibilityScoreMetadata creates a new DashboardCompatibilityScoreMetadata object.
func NewDashboardCompatibilityScoreMetadata() *DashboardCompatibilityScoreMetadata {
return &DashboardCompatibilityScoreMetadata{
Finalizers: []string{},
Labels: map[string]string{},
}
}
@@ -0,0 +1,326 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v1alpha1
import (
"fmt"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"time"
)
// +k8s:openapi-gen=true
type DashboardCompatibilityScore struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
// Spec is the spec of the DashboardCompatibilityScore
Spec DashboardCompatibilityScoreSpec `json:"spec" yaml:"spec"`
Status DashboardCompatibilityScoreStatus `json:"status" yaml:"status"`
}
func NewDashboardCompatibilityScore() *DashboardCompatibilityScore {
return &DashboardCompatibilityScore{
Spec: *NewDashboardCompatibilityScoreSpec(),
Status: *NewDashboardCompatibilityScoreStatus(),
}
}
func (o *DashboardCompatibilityScore) GetSpec() any {
return o.Spec
}
func (o *DashboardCompatibilityScore) SetSpec(spec any) error {
cast, ok := spec.(DashboardCompatibilityScoreSpec)
if !ok {
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
}
o.Spec = cast
return nil
}
func (o *DashboardCompatibilityScore) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
}
func (o *DashboardCompatibilityScore) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
}
func (o *DashboardCompatibilityScore) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(DashboardCompatibilityScoreStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type DashboardCompatibilityScoreStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
}
func (o *DashboardCompatibilityScore) GetStaticMetadata() resource.StaticMetadata {
gvk := o.GroupVersionKind()
return resource.StaticMetadata{
Name: o.ObjectMeta.Name,
Namespace: o.ObjectMeta.Namespace,
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
}
}
func (o *DashboardCompatibilityScore) SetStaticMetadata(metadata resource.StaticMetadata) {
o.Name = metadata.Name
o.Namespace = metadata.Namespace
o.SetGroupVersionKind(schema.GroupVersionKind{
Group: metadata.Group,
Version: metadata.Version,
Kind: metadata.Kind,
})
}
func (o *DashboardCompatibilityScore) GetCommonMetadata() resource.CommonMetadata {
dt := o.DeletionTimestamp
var deletionTimestamp *time.Time
if dt != nil {
deletionTimestamp = &dt.Time
}
// Legacy ExtraFields support
extraFields := make(map[string]any)
if o.Annotations != nil {
extraFields["annotations"] = o.Annotations
}
if o.ManagedFields != nil {
extraFields["managedFields"] = o.ManagedFields
}
if o.OwnerReferences != nil {
extraFields["ownerReferences"] = o.OwnerReferences
}
return resource.CommonMetadata{
UID: string(o.UID),
ResourceVersion: o.ResourceVersion,
Generation: o.Generation,
Labels: o.Labels,
CreationTimestamp: o.CreationTimestamp.Time,
DeletionTimestamp: deletionTimestamp,
Finalizers: o.Finalizers,
UpdateTimestamp: o.GetUpdateTimestamp(),
CreatedBy: o.GetCreatedBy(),
UpdatedBy: o.GetUpdatedBy(),
ExtraFields: extraFields,
}
}
func (o *DashboardCompatibilityScore) SetCommonMetadata(metadata resource.CommonMetadata) {
o.UID = types.UID(metadata.UID)
o.ResourceVersion = metadata.ResourceVersion
o.Generation = metadata.Generation
o.Labels = metadata.Labels
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
if metadata.DeletionTimestamp != nil {
dt := metav1.NewTime(*metadata.DeletionTimestamp)
o.DeletionTimestamp = &dt
} else {
o.DeletionTimestamp = nil
}
o.Finalizers = metadata.Finalizers
if o.Annotations == nil {
o.Annotations = make(map[string]string)
}
if !metadata.UpdateTimestamp.IsZero() {
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
}
if metadata.CreatedBy != "" {
o.SetCreatedBy(metadata.CreatedBy)
}
if metadata.UpdatedBy != "" {
o.SetUpdatedBy(metadata.UpdatedBy)
}
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
if metadata.ExtraFields != nil {
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
if cast, ok := annotations.(map[string]string); ok {
o.Annotations = cast
}
}
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
o.ManagedFields = cast
}
}
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
o.OwnerReferences = cast
}
}
}
}
func (o *DashboardCompatibilityScore) GetCreatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
}
func (o *DashboardCompatibilityScore) SetCreatedBy(createdBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
}
func (o *DashboardCompatibilityScore) GetUpdateTimestamp() time.Time {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
return parsed
}
func (o *DashboardCompatibilityScore) SetUpdateTimestamp(updateTimestamp time.Time) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
}
func (o *DashboardCompatibilityScore) GetUpdatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
}
func (o *DashboardCompatibilityScore) SetUpdatedBy(updatedBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
}
func (o *DashboardCompatibilityScore) Copy() resource.Object {
return resource.CopyObject(o)
}
func (o *DashboardCompatibilityScore) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *DashboardCompatibilityScore) DeepCopy() *DashboardCompatibilityScore {
cpy := &DashboardCompatibilityScore{}
o.DeepCopyInto(cpy)
return cpy
}
func (o *DashboardCompatibilityScore) DeepCopyInto(dst *DashboardCompatibilityScore) {
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
dst.TypeMeta.Kind = o.TypeMeta.Kind
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
o.Spec.DeepCopyInto(&dst.Spec)
o.Status.DeepCopyInto(&dst.Status)
}
// Interface compliance compile-time check
var _ resource.Object = &DashboardCompatibilityScore{}
// +k8s:openapi-gen=true
type DashboardCompatibilityScoreList struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ListMeta `json:"metadata" yaml:"metadata"`
Items []DashboardCompatibilityScore `json:"items" yaml:"items"`
}
func (o *DashboardCompatibilityScoreList) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *DashboardCompatibilityScoreList) Copy() resource.ListObject {
cpy := &DashboardCompatibilityScoreList{
TypeMeta: o.TypeMeta,
Items: make([]DashboardCompatibilityScore, len(o.Items)),
}
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
for i := 0; i < len(o.Items); i++ {
if item, ok := o.Items[i].Copy().(*DashboardCompatibilityScore); ok {
cpy.Items[i] = *item
}
}
return cpy
}
func (o *DashboardCompatibilityScoreList) GetItems() []resource.Object {
items := make([]resource.Object, len(o.Items))
for i := 0; i < len(o.Items); i++ {
items[i] = &o.Items[i]
}
return items
}
func (o *DashboardCompatibilityScoreList) SetItems(items []resource.Object) {
o.Items = make([]DashboardCompatibilityScore, len(items))
for i := 0; i < len(items); i++ {
o.Items[i] = *items[i].(*DashboardCompatibilityScore)
}
}
func (o *DashboardCompatibilityScoreList) DeepCopy() *DashboardCompatibilityScoreList {
cpy := &DashboardCompatibilityScoreList{}
o.DeepCopyInto(cpy)
return cpy
}
func (o *DashboardCompatibilityScoreList) DeepCopyInto(dst *DashboardCompatibilityScoreList) {
resource.CopyObjectInto(dst, o)
}
// Interface compliance compile-time check
var _ resource.ListObject = &DashboardCompatibilityScoreList{}
// Copy methods for all subresource types
// DeepCopy creates a full deep copy of Spec
func (s *DashboardCompatibilityScoreSpec) DeepCopy() *DashboardCompatibilityScoreSpec {
cpy := &DashboardCompatibilityScoreSpec{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies Spec into another Spec object
func (s *DashboardCompatibilityScoreSpec) DeepCopyInto(dst *DashboardCompatibilityScoreSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of DashboardCompatibilityScoreStatus
func (s *DashboardCompatibilityScoreStatus) DeepCopy() *DashboardCompatibilityScoreStatus {
cpy := &DashboardCompatibilityScoreStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies DashboardCompatibilityScoreStatus into another DashboardCompatibilityScoreStatus object
func (s *DashboardCompatibilityScoreStatus) DeepCopyInto(dst *DashboardCompatibilityScoreStatus) {
resource.CopyObjectInto(dst, s)
}
@@ -0,0 +1,34 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v1alpha1
import (
"github.com/grafana/grafana-app-sdk/resource"
)
// schema is unexported to prevent accidental overwrites
var (
schemaDashboardCompatibilityScore = resource.NewSimpleSchema("dashvalidator.grafana.app", "v1alpha1", NewDashboardCompatibilityScore(), &DashboardCompatibilityScoreList{}, resource.WithKind("DashboardCompatibilityScore"),
resource.WithPlural("dashboardcompatibilityscores"), resource.WithScope(resource.NamespacedScope))
kindDashboardCompatibilityScore = resource.Kind{
Schema: schemaDashboardCompatibilityScore,
Codecs: map[resource.KindEncoding]resource.Codec{
resource.KindEncodingJSON: &DashboardCompatibilityScoreJSONCodec{},
},
}
)
// Kind returns a resource.Kind for this Schema with a JSON codec
func DashboardCompatibilityScoreKind() resource.Kind {
return kindDashboardCompatibilityScore
}
// Schema returns a resource.SimpleSchema representation of DashboardCompatibilityScore
func DashboardCompatibilityScoreSchema() *resource.SimpleSchema {
return schemaDashboardCompatibilityScore
}
// Interface compliance checks
var _ resource.Schema = kindDashboardCompatibilityScore
@@ -0,0 +1,48 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v1alpha1
// DataSourceMapping specifies a datasource to validate dashboard queries against.
// Maps logical datasource references in the dashboard to actual datasource instances.
// +k8s:openapi-gen=true
type DashboardCompatibilityScoreDataSourceMapping struct {
// Unique identifier of the datasource instance.
// Example: "prometheus-prod-us-west"
Uid string `json:"uid"`
// Type of datasource plugin.
// MVP: Only "prometheus" supported.
// Future: "mysql", "postgres", "elasticsearch", etc.
Type string `json:"type"`
// Optional human-readable name for display in results.
// If not provided, UID will be used in error messages.
// Example: "Production Prometheus (US-West)"
Name *string `json:"name,omitempty"`
}
// NewDashboardCompatibilityScoreDataSourceMapping creates a new DashboardCompatibilityScoreDataSourceMapping object.
func NewDashboardCompatibilityScoreDataSourceMapping() *DashboardCompatibilityScoreDataSourceMapping {
return &DashboardCompatibilityScoreDataSourceMapping{}
}
// +k8s:openapi-gen=true
type DashboardCompatibilityScoreSpec struct {
// Complete dashboard JSON object to validate.
// Must be a v1 dashboard schema (contains "panels" array).
// v2 dashboards (with "elements" structure) are not yet supported.
DashboardJson map[string]interface{} `json:"dashboardJson"`
// Array of datasources to validate against.
// The validator will check dashboard queries against each datasource
// and provide per-datasource compatibility results.
//
// MVP: Only single datasource supported (array length = 1), Prometheus type only.
// Future: Will support multiple datasources for dashboards with mixed queries.
DatasourceMappings []DashboardCompatibilityScoreDataSourceMapping `json:"datasourceMappings"`
}
// NewDashboardCompatibilityScoreSpec creates a new DashboardCompatibilityScoreSpec object.
func NewDashboardCompatibilityScoreSpec() *DashboardCompatibilityScoreSpec {
return &DashboardCompatibilityScoreSpec{
DashboardJson: map[string]interface{}{},
DatasourceMappings: []DashboardCompatibilityScoreDataSourceMapping{},
}
}
@@ -0,0 +1,151 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v1alpha1
// DataSourceResult contains validation results for a single datasource.
// Provides aggregate statistics and per-query breakdown of compatibility.
// +k8s:openapi-gen=true
type DashboardCompatibilityScoreDataSourceResult struct {
// Datasource UID that was validated (matches DataSourceMapping.uid)
Uid string `json:"uid"`
// Datasource type (matches DataSourceMapping.type)
Type string `json:"type"`
// Optional display name (matches DataSourceMapping.name if provided)
Name *string `json:"name,omitempty"`
// Total number of queries in the dashboard targeting this datasource.
// Includes all panel targets/queries that reference this datasource.
TotalQueries int64 `json:"totalQueries"`
// Number of queries successfully validated.
// May be less than totalQueries if some queries couldn't be parsed.
CheckedQueries int64 `json:"checkedQueries"`
// Total number of unique metrics/identifiers referenced across all queries.
// For Prometheus: metric names extracted from PromQL expressions.
// For SQL datasources: table and column names.
TotalMetrics int64 `json:"totalMetrics"`
// Number of metrics that exist in the datasource schema.
// foundMetrics <= totalMetrics
FoundMetrics int64 `json:"foundMetrics"`
// Array of metric names that were referenced but don't exist.
// Useful for debugging why a dashboard shows "no data".
// Example for Prometheus: ["http_requests_total", "api_latency_seconds"]
MissingMetrics []string `json:"missingMetrics"`
// Per-query breakdown showing which specific queries have issues.
// One entry per query target (refId: "A", "B", "C", etc.) in each panel.
// Allows pinpointing exactly which panel/query needs fixing.
QueryBreakdown []DashboardCompatibilityScoreQueryBreakdown `json:"queryBreakdown"`
// Overall compatibility score for this datasource (0-100).
// Calculated as: (foundMetrics / totalMetrics) * 100
// Used to calculate the global compatibilityScore in status.
CompatibilityScore float64 `json:"compatibilityScore"`
}
// NewDashboardCompatibilityScoreDataSourceResult creates a new DashboardCompatibilityScoreDataSourceResult object.
func NewDashboardCompatibilityScoreDataSourceResult() *DashboardCompatibilityScoreDataSourceResult {
return &DashboardCompatibilityScoreDataSourceResult{
MissingMetrics: []string{},
QueryBreakdown: []DashboardCompatibilityScoreQueryBreakdown{},
}
}
// QueryBreakdown provides compatibility details for a single query within a panel.
// Granular per-query results allow users to identify exactly which queries need fixing.
//
// Note: A panel can have multiple queries (refId: "A", "B", "C", etc.),
// so there may be multiple QueryBreakdown entries for the same panelID.
// +k8s:openapi-gen=true
type DashboardCompatibilityScoreQueryBreakdown struct {
// Human-readable panel title for context.
// Example: "CPU Usage", "Request Rate"
PanelTitle string `json:"panelTitle"`
// Numeric panel ID from dashboard JSON.
// Used to correlate with dashboard structure.
PanelID int64 `json:"panelID"`
// Query identifier within the panel.
// Values: "A", "B", "C", etc. (from panel.targets[].refId)
// Uniquely identifies which query in a multi-query panel this refers to.
QueryRefId string `json:"queryRefId"`
// Number of unique metrics referenced in this specific query.
// For Prometheus: metrics extracted from the PromQL expr.
// Example: rate(http_requests_total[5m]) references 1 metric.
TotalMetrics int64 `json:"totalMetrics"`
// Number of those metrics that exist in the datasource.
// foundMetrics <= totalMetrics
FoundMetrics int64 `json:"foundMetrics"`
// Array of missing metric names specific to this query.
// Helps identify exactly which part of a query expression will fail.
// Empty array means query is fully compatible.
MissingMetrics []string `json:"missingMetrics"`
// Compatibility percentage for this individual query (0-100).
// Calculated as: (foundMetrics / totalMetrics) * 100
// 100 = query will work perfectly, 0 = query will return no data.
CompatibilityScore float64 `json:"compatibilityScore"`
}
// NewDashboardCompatibilityScoreQueryBreakdown creates a new DashboardCompatibilityScoreQueryBreakdown object.
func NewDashboardCompatibilityScoreQueryBreakdown() *DashboardCompatibilityScoreQueryBreakdown {
return &DashboardCompatibilityScoreQueryBreakdown{
MissingMetrics: []string{},
}
}
// +k8s:openapi-gen=true
type DashboardCompatibilityScorestatusOperatorState struct {
// lastEvaluation is the ResourceVersion last evaluated
LastEvaluation string `json:"lastEvaluation"`
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
State DashboardCompatibilityScoreStatusOperatorStateState `json:"state"`
// descriptiveState is an optional more descriptive state field which has no requirements on format
DescriptiveState *string `json:"descriptiveState,omitempty"`
// details contains any extra information that is operator-specific
Details map[string]interface{} `json:"details,omitempty"`
}
// NewDashboardCompatibilityScorestatusOperatorState creates a new DashboardCompatibilityScorestatusOperatorState object.
func NewDashboardCompatibilityScorestatusOperatorState() *DashboardCompatibilityScorestatusOperatorState {
return &DashboardCompatibilityScorestatusOperatorState{}
}
// +k8s:openapi-gen=true
type DashboardCompatibilityScoreStatus struct {
// Overall compatibility score across all datasources (0-100).
// Calculated as: (total found metrics / total referenced metrics) * 100
//
// Score interpretation:
// - 100: Perfect compatibility, all queries will work
// - 80-99: Excellent, minor missing metrics
// - 50-79: Fair, significant missing metrics
// - 0-49: Poor, most queries will fail
CompatibilityScore float64 `json:"compatibilityScore"`
// Per-datasource validation results.
// Array length matches spec.datasourceMappings.
// Each element contains detailed metrics and query-level breakdown.
DatasourceResults []DashboardCompatibilityScoreDataSourceResult `json:"datasourceResults"`
// ISO 8601 timestamp of when validation was last performed.
// Example: "2024-01-15T10:30:00Z"
LastChecked *string `json:"lastChecked,omitempty"`
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
OperatorStates map[string]DashboardCompatibilityScorestatusOperatorState `json:"operatorStates,omitempty"`
// Human-readable summary of validation result.
// Examples: "All queries compatible", "3 missing metrics found"
Message *string `json:"message,omitempty"`
// additionalFields is reserved for future use
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
}
// NewDashboardCompatibilityScoreStatus creates a new DashboardCompatibilityScoreStatus object.
func NewDashboardCompatibilityScoreStatus() *DashboardCompatibilityScoreStatus {
return &DashboardCompatibilityScoreStatus{
DatasourceResults: []DashboardCompatibilityScoreDataSourceResult{},
}
}
// +k8s:openapi-gen=true
type DashboardCompatibilityScoreStatusOperatorStateState string
const (
DashboardCompatibilityScoreStatusOperatorStateStateSuccess DashboardCompatibilityScoreStatusOperatorStateState = "success"
DashboardCompatibilityScoreStatusOperatorStateStateInProgress DashboardCompatibilityScoreStatusOperatorStateState = "in_progress"
DashboardCompatibilityScoreStatusOperatorStateStateFailed DashboardCompatibilityScoreStatusOperatorStateState = "failed"
)
File diff suppressed because one or more lines are too long
+89
View File
@@ -0,0 +1,89 @@
package app
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
"k8s.io/apimachinery/pkg/runtime/schema"
validatorv1alpha1 "github.com/grafana/grafana/apps/dashvalidator/pkg/apis/dashvalidator/v1alpha1"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
)
type DashValidatorConfig struct {
DatasourceSvc datasources.DataSourceService
PluginCtx *plugincontext.Provider
}
func New(cfg app.Config) (app.App, error) {
specificConfig, ok := cfg.SpecificConfig.(*DashValidatorConfig)
if !ok {
return nil, fmt.Errorf("invalid config type: expected DashValidatorConfig")
}
log := logging.DefaultLogger.With("app", "dashvalidator")
// configure our app
simpleConfig := simple.AppConfig{
Name: "dashvalidator",
KubeConfig: cfg.KubeConfig,
//Define our custom route
VersionedCustomRoutes: map[string]simple.AppVersionRouteHandlers{
"v1alpha1": {
{
Namespaced: true,
Path: "check",
Method: "POST",
}: handleCheckRoute(log, specificConfig.DatasourceSvc, specificConfig.PluginCtx),
},
},
}
a, err := simple.NewApp(simpleConfig)
if err != nil {
return nil, fmt.Errorf("failed to create app: %w", err)
}
return a, nil
}
// custom route handler to check dashboard compatibility
func handleCheckRoute(
log logging.Logger,
datasourceSvc datasources.DataSourceService,
pluginCtx *plugincontext.Provider,
) func(context.Context, app.CustomRouteResponseWriter, *app.CustomRouteRequest) error {
return func(ctx context.Context, w app.CustomRouteResponseWriter, r *app.CustomRouteRequest) error {
logger := log.WithContext(ctx)
logger.Info("Received compatibility check request")
// TODO validation logic here
// for now a simple response
w.WriteHeader(http.StatusOK)
return json.NewEncoder(w).Encode(map[string]interface{}{
"message": "Handler working!",
})
}
}
func GetKinds() map[schema.GroupVersion][]resource.Kind {
gv := schema.GroupVersion{
Group: "dashvalidator.grafana.com",
Version: "v1alpha1",
}
return map[schema.GroupVersion][]resource.Kind{
gv: {validatorv1alpha1.DashboardCompatibilityScoreKind()},
}
}
+1
View File
@@ -14,6 +14,7 @@ use (
./apps/collections
./apps/correlations
./apps/dashboard
./apps/dashvalidator
./apps/example
./apps/folder
./apps/iam