Compare commits

..

3 Commits

Author SHA1 Message Date
Dominik Prokop 43e3efc98a Migrate to new import pattern
- Update all consumers to import from @grafana/schema/dashboard/v2beta1
- Update raw dashboard type imports to use @grafana/schema/dashboard/v0
- Add v2beta1/index.ts re-export file for the sub-path
- Consolidate duplicate imports to fix lint errors
2025-12-16 13:10:17 +01:00
Dominik Prokop d853bd598d Add development build support for schema sub-paths
- Add webpack NormalModuleReplacementPlugin to resolve sub-paths from source
- Add TypeScript paths mappings for monorepo type-checking
- Add Jest moduleNameMapper for test resolution
2025-12-16 11:13:18 +01:00
Dominik Prokop 1c94a7ddd5 Add versioned dashboard schema sub-path exports
- Create dashboard/v0 sub-path for raw dashboard types
- Create dashboard/v2beta1 sub-path for v2 schema types
- Add exports and typesVersions to package.json via prepare-npm-package.js
- typesVersions provides backwards compatibility for moduleResolution: node
- Add rollup build targets for both sub-paths
2025-12-16 10:34:46 +01:00
214 changed files with 803 additions and 10223 deletions
+6 -7
View File
@@ -111,13 +111,12 @@ jobs:
ownerRepo: 'grafana/grafana-enterprise'
from: ${{ needs.setup.outputs.release_branch }}
to: ${{ needs.create_next_release_branch_enterprise.outputs.branch }}
# Removed this for now since it doesn't work
# post_changelog_on_forum:
# needs: setup
# uses: grafana/grafana/.github/workflows/community-release.yml@main
# with:
# version: ${{ needs.setup.outputs.version }}
# dry_run: ${{ needs.setup.outputs.dry_run == 'true' }}
post_changelog_on_forum:
needs: setup
uses: grafana/grafana/.github/workflows/community-release.yml@main
with:
version: ${{ needs.setup.outputs.version }}
dry_run: ${{ needs.setup.outputs.dry_run == 'true' }}
create_github_release:
# a github release requires a git tag
# The github-release action retrieves the changelog using the /repos/grafana/grafana/contents/CHANGELOG.md API
+5 -15
View File
@@ -3,7 +3,7 @@
# Others can set up the YAML LSP manually, which supports schemas: https://github.com/redhat-developer/yaml-language-server
# $schema: https://golangci-lint.run/jsonschema/golangci.jsonschema.json
version: '2'
version: "2"
run:
timeout: 15m
concurrency: 10
@@ -83,16 +83,6 @@ linters:
deny:
- pkg: github.com/grafana/grafana/pkg
desc: apps/playlist is not allowed to import grafana core
apps-dashboard:
list-mode: lax
files:
- ./apps/dashboard/*
- ./apps/dashboard/**/*
allow:
- github.com/grafana/grafana/pkg/apimachinery
deny:
- pkg: github.com/grafana/grafana/pkg
desc: apps/dashboard is not allowed to import grafana core
apps-secret:
list-mode: lax
files:
@@ -291,16 +281,16 @@ linters:
text: G306
- linters:
- gosec
text: '401'
text: "401"
- linters:
- gosec
text: '402'
text: "402"
- linters:
- gosec
text: '501'
text: "501"
- linters:
- gosec
text: '404'
text: "404"
- linters:
- errorlint
text: non-wrapping format verb for fmt.Errorf
+1 -19
View File
@@ -15,7 +15,6 @@ require (
github.com/stretchr/testify v1.11.1
k8s.io/apimachinery v0.34.2
k8s.io/apiserver v0.34.2
k8s.io/client-go v0.34.2
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912
)
@@ -44,7 +43,6 @@ replace github.com/grafana/grafana/apps/plugins => ../plugins
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
@@ -57,7 +55,6 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
@@ -88,7 +85,6 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@@ -105,7 +101,6 @@ require (
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gchaincl/sqlhooks v1.3.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
@@ -149,7 +144,6 @@ require (
github.com/golang-migrate/migrate/v4 v4.7.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.1 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
@@ -168,7 +162,6 @@ require (
github.com/grafana/sqlds/v4 v4.2.7 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
@@ -183,7 +176,6 @@ require (
github.com/hashicorp/memberlist v0.5.2 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
github.com/jessevdk/go-flags v1.6.1 // indirect
github.com/jmespath-community/go-jmespath v1.1.1 // indirect
@@ -256,9 +248,7 @@ require (
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/cobra v1.10.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
github.com/thomaspoignant/go-feature-flag v1.42.0 // indirect
@@ -266,9 +256,6 @@ require (
github.com/woodsbury/decimal128 v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.etcd.io/etcd/api/v3 v3.6.4 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect
go.etcd.io/etcd/client/v3 v3.6.4 // indirect
go.mongodb.org/mongo-driver v1.17.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
@@ -287,8 +274,6 @@ require (
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.45.0 // indirect
@@ -312,26 +297,23 @@ require (
google.golang.org/grpc v1.77.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect
gopkg.in/telebot.v3 v3.3.8 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.2 // indirect
k8s.io/apiextensions-apiserver v0.34.2 // indirect
k8s.io/client-go v0.34.2 // indirect
k8s.io/component-base v0.34.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kms v0.34.2 // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.40.1 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
-29
View File
@@ -282,7 +282,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg=
@@ -407,8 +406,6 @@ 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-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/analysis v0.24.0 h1:vE/VFFkICKyYuTWYnplQ+aVr45vlG6NcZKC7BdIXhsA=
github.com/go-openapi/analysis v0.24.0/go.mod h1:GLyoJA+bvmGGaHgpfeDh8ldpGo69fAJg7eeMDMRCIrw=
github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs=
@@ -609,8 +606,6 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0=
github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o=
@@ -754,8 +749,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
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/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
@@ -986,7 +979,6 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM=
github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
@@ -1004,7 +996,6 @@ github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
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.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
@@ -1019,7 +1010,6 @@ github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57J
github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg=
github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
@@ -1046,7 +1036,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
@@ -1069,8 +1058,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -1084,7 +1071,6 @@ github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
@@ -1110,7 +1096,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
@@ -1127,8 +1112,6 @@ github.com/thomaspoignant/go-feature-flag v1.42.0/go.mod h1:y0QiWH7chHWhGATb/+Xq
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc=
github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
@@ -1146,8 +1129,6 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1158,8 +1139,6 @@ github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo=
go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
@@ -1170,12 +1149,6 @@ go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A=
go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo=
go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA=
go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE=
go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU=
go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg=
go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
@@ -1328,7 +1301,6 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1740,7 +1712,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-48
View File
@@ -8,24 +8,18 @@ import (
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/k8s"
appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
advisorapi "github.com/grafana/grafana/apps/advisor/pkg/apis"
advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
"github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry"
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
"github.com/grafana/grafana/apps/advisor/pkg/app/checkscheduler"
"github.com/grafana/grafana/apps/advisor/pkg/app/checktyperegisterer"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/setting"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/rest"
)
func New(cfg app.Config) (app.App, error) {
@@ -194,45 +188,3 @@ func GetKinds() map[schema.GroupVersion][]resource.Kind {
},
}
}
func ProvideAppInstaller(
authorizer authorizer.Authorizer,
checkRegistry checkregistry.CheckService,
cfg *setting.Cfg,
orgService org.Service,
) (*AdvisorAppInstaller, error) {
provider := simple.NewAppProvider(advisorapi.LocalManifest(), nil, New)
pluginConfig := cfg.PluginSettings["grafana-advisor-app"]
specificConfig := checkregistry.AdvisorAppConfig{
CheckRegistry: checkRegistry,
PluginConfig: pluginConfig,
StackID: cfg.StackID,
OrgService: orgService,
}
appCfg := app.Config{
KubeConfig: rest.Config{},
ManifestData: *advisorapi.LocalManifest().ManifestData,
SpecificConfig: specificConfig,
}
defaultInstaller, err := appsdkapiserver.NewDefaultAppInstaller(provider, appCfg, advisorapi.NewGoTypeAssociator())
if err != nil {
return nil, err
}
installer := &AdvisorAppInstaller{
AppInstaller: defaultInstaller,
authorizer: authorizer,
}
return installer, nil
}
type AdvisorAppInstaller struct {
appsdkapiserver.AppInstaller
authorizer authorizer.Authorizer
}
func (a *AdvisorAppInstaller) GetAuthorizer() authorizer.Authorizer {
return a.authorizer
}
+47
View File
@@ -0,0 +1,47 @@
package app
import (
"context"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
func GetAuthorizer() authorizer.Authorizer {
return authorizer.AuthorizerFunc(func(
ctx context.Context, attr authorizer.Attributes,
) (authorized authorizer.Decision, reason string, err error) {
if !attr.IsResourceRequest() {
return authorizer.DecisionNoOpinion, "", nil
}
// Check for service identity
if identity.IsServiceIdentity(ctx) {
return authorizer.DecisionAllow, "", nil
}
// Check for access policy identity
info, ok := claims.AuthInfoFrom(ctx)
if ok && claims.IsIdentityType(info.GetIdentityType(), claims.TypeAccessPolicy) {
// For access policy identities, we need to use ResourceAuthorizer
// This requires an AccessClient, which should be provided by the API server
// For now, we'll use the default ResourceAuthorizer from the API server
// This will be set up by the API server's authorization chain
return authorizer.DecisionNoOpinion, "", nil
}
// For regular Grafana users, check if they are admin
u, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "valid user is required", err
}
// check if is admin
if u.HasRole(identity.RoleAdmin) {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "forbidden", nil
})
}
+91
View File
@@ -0,0 +1,91 @@
package app
import (
"context"
"testing"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/stretchr/testify/assert"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
func TestGetAuthorizer(t *testing.T) {
tests := []struct {
name string
ctx context.Context
attr authorizer.Attributes
expectedDecision authorizer.Decision
expectedReason string
expectedErr error
}{
{
name: "non-resource request",
ctx: context.TODO(),
attr: &mockAttributes{resourceRequest: false},
expectedDecision: authorizer.DecisionNoOpinion,
expectedReason: "",
expectedErr: nil,
},
{
name: "user is admin",
ctx: identity.WithRequester(context.TODO(), &mockUser{isGrafanaAdmin: true}),
attr: &mockAttributes{resourceRequest: true},
expectedDecision: authorizer.DecisionAllow,
expectedReason: "",
expectedErr: nil,
},
{
name: "user is not admin",
ctx: identity.WithRequester(context.TODO(), &mockUser{isGrafanaAdmin: false}),
attr: &mockAttributes{resourceRequest: true},
expectedDecision: authorizer.DecisionDeny,
expectedReason: "forbidden",
expectedErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
auth := GetAuthorizer()
decision, reason, err := auth.Authorize(tt.ctx, tt.attr)
assert.Equal(t, tt.expectedDecision, decision)
assert.Equal(t, tt.expectedReason, reason)
assert.Equal(t, tt.expectedErr, err)
})
}
}
type mockAttributes struct {
authorizer.Attributes
resourceRequest bool
}
func (m *mockAttributes) IsResourceRequest() bool {
return m.resourceRequest
}
// Implement other methods of authorizer.Attributes as needed
type mockUser struct {
identity.Requester
isGrafanaAdmin bool
}
func (m *mockUser) GetIsGrafanaAdmin() bool {
return m.isGrafanaAdmin
}
func (m *mockUser) HasRole(role identity.RoleType) bool {
return role == identity.RoleAdmin && m.isGrafanaAdmin
}
func (m *mockUser) GetUID() string {
return "test-uid"
}
func (m *mockUser) GetIdentityType() claims.IdentityType {
return claims.TypeUser
}
// Implement other methods of identity.Requester as needed
+1 -1
View File
@@ -9,7 +9,6 @@ require (
github.com/grafana/grafana-app-sdk/logging v0.48.3
github.com/grafana/grafana-plugin-sdk-go v0.284.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/prometheus/client_golang v1.23.2
github.com/stretchr/testify v1.11.1
k8s.io/apimachinery v0.34.2
@@ -58,6 +57,7 @@ require (
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.7.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
@@ -1,715 +0,0 @@
{
"kind": "DashboardWithAccessInfo",
"apiVersion": "dashboard.grafana.app/v2beta1",
"metadata": {
"name": "adt885j",
"namespace": "default",
"uid": "yTWet6JgBjlRIWnqRE9ZOmUycfT0tEkr2mljaln1GWIX",
"resourceVersion": "2",
"generation": 2,
"creationTimestamp": "2025-12-16T10:44:31Z",
"labels": {
"grafana.app/deprecatedInternalID": "2409"
},
"annotations": {
"grafana.app/createdBy": "user:u000000001",
"grafana.app/updatedBy": "user:u000000001",
"grafana.app/updatedTimestamp": "2025-12-16T10:51:14Z"
}
},
"spec": {
"annotations": [
{
"kind": "AnnotationQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "grafana",
"version": "v0",
"datasource": {
"name": "-- Grafana --"
},
"spec": {}
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"builtIn": true
}
}
],
"cursorSync": "Off",
"description": "",
"editable": true,
"elements": {
"panel-1": {
"kind": "Panel",
"spec": {
"id": 1,
"title": "Panel1",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "grafana-testdata-datasource",
"version": "v0",
"datasource": {
"name": "PD8C576611E62080A"
},
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "timeseries",
"version": "12.4.0-pre",
"spec": {
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
},
"panel-2": {
"kind": "Panel",
"spec": {
"id": 2,
"title": "Panel2",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "",
"version": "v0",
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "timeseries",
"version": "12.4.0-pre",
"spec": {
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
},
"panel-3": {
"kind": "Panel",
"spec": {
"id": 3,
"title": "Panel3",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "",
"version": "v0",
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "timeseries",
"version": "12.4.0-pre",
"spec": {
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
},
"panel-4": {
"kind": "Panel",
"spec": {
"id": 4,
"title": "Panel4",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "",
"version": "v0",
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "timeseries",
"version": "12.4.0-pre",
"spec": {
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
},
"panel-5": {
"kind": "Panel",
"spec": {
"id": 5,
"title": "Panel5",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "DataQuery",
"group": "",
"version": "v0",
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "VizConfig",
"group": "timeseries",
"version": "12.4.0-pre",
"spec": {
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
}
},
"layout": {
"kind": "TabsLayout",
"spec": {
"tabs": [
{
"kind": "TabsLayoutTab",
"spec": {
"title": "Tab1",
"layout": {
"kind": "GridLayout",
"spec": {
"items": [
{
"kind": "GridLayoutItem",
"spec": {
"x": 0,
"y": 0,
"width": 7,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-1"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 7,
"y": 0,
"width": 8,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-2"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 15,
"y": 0,
"width": 9,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-3"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 0,
"y": 8,
"width": 12,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-4"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 12,
"y": 8,
"width": 12,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-5"
}
}
}
]
}
}
}
}
]
}
},
"links": [],
"liveNow": false,
"preload": false,
"tags": [],
"timeSettings": {
"timezone": "browser",
"from": "now-6h",
"to": "now",
"autoRefresh": "",
"autoRefreshIntervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"hideTimepicker": false,
"fiscalYearStartMonth": 0
},
"title": "Dashboard with tabs",
"variables": []
},
"status": {},
"access": {
"slug": "dashboard-with-tabs",
"url": "/d/adt885j/dashboard-with-tabs",
"isPublic": false,
"canSave": true,
"canEdit": true,
"canAdmin": true,
"canStar": true,
"canDelete": true,
"annotationsPermissions": {
"dashboard": {
"canAdd": true,
"canEdit": true,
"canDelete": true
},
"organization": {
"canAdd": true,
"canEdit": true,
"canDelete": true
}
}
}
}
@@ -1,511 +0,0 @@
{
"kind": "DashboardWithAccessInfo",
"apiVersion": "dashboard.grafana.app/v0alpha1",
"metadata": {
"name": "adt885j",
"namespace": "default",
"uid": "yTWet6JgBjlRIWnqRE9ZOmUycfT0tEkr2mljaln1GWIX",
"resourceVersion": "2",
"generation": 2,
"creationTimestamp": "2025-12-16T10:44:31Z",
"labels": {
"grafana.app/deprecatedInternalID": "2409"
},
"annotations": {
"grafana.app/createdBy": "user:u000000001",
"grafana.app/updatedBy": "user:u000000001",
"grafana.app/updatedTimestamp": "2025-12-16T10:51:14Z"
}
},
"spec": {
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"description": "",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"liveNow": false,
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": -1,
"panels": [],
"title": "Tab1",
"type": "row"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 7,
"x": 0,
"y": 1
},
"id": 1,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A"
}
],
"title": "Panel1",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 8,
"x": 7,
"y": 1
},
"id": 2,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "Panel2",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 9,
"x": 15,
"y": 1
},
"id": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "Panel3",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 9
},
"id": 4,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "Panel4",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 9
},
"id": 5,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "Panel5",
"type": "timeseries"
}
],
"preload": false,
"refresh": "",
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "browser",
"title": "Dashboard with tabs"
},
"status": {
"conversion": {
"failed": false,
"storedVersion": "v2beta1"
}
}
}
@@ -1,511 +0,0 @@
{
"kind": "DashboardWithAccessInfo",
"apiVersion": "dashboard.grafana.app/v1beta1",
"metadata": {
"name": "adt885j",
"namespace": "default",
"uid": "yTWet6JgBjlRIWnqRE9ZOmUycfT0tEkr2mljaln1GWIX",
"resourceVersion": "2",
"generation": 2,
"creationTimestamp": "2025-12-16T10:44:31Z",
"labels": {
"grafana.app/deprecatedInternalID": "2409"
},
"annotations": {
"grafana.app/createdBy": "user:u000000001",
"grafana.app/updatedBy": "user:u000000001",
"grafana.app/updatedTimestamp": "2025-12-16T10:51:14Z"
}
},
"spec": {
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"description": "",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"liveNow": false,
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": -1,
"panels": [],
"title": "Tab1",
"type": "row"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 7,
"x": 0,
"y": 1
},
"id": 1,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A"
}
],
"title": "Panel1",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 8,
"x": 7,
"y": 1
},
"id": 2,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "Panel2",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 9,
"x": 15,
"y": 1
},
"id": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "Panel3",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 9
},
"id": 4,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "Panel4",
"type": "timeseries"
},
{
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
}
}
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 9
},
"id": 5,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "12.4.0-pre",
"targets": [
{
"refId": "A"
}
],
"title": "Panel5",
"type": "timeseries"
}
],
"preload": false,
"refresh": "",
"schemaVersion": 42,
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "browser",
"title": "Dashboard with tabs"
},
"status": {
"conversion": {
"failed": false,
"storedVersion": "v2beta1"
}
}
}
@@ -1,683 +0,0 @@
{
"kind": "DashboardWithAccessInfo",
"apiVersion": "dashboard.grafana.app/v2alpha1",
"metadata": {
"name": "adt885j",
"namespace": "default",
"uid": "yTWet6JgBjlRIWnqRE9ZOmUycfT0tEkr2mljaln1GWIX",
"resourceVersion": "2",
"generation": 2,
"creationTimestamp": "2025-12-16T10:44:31Z",
"labels": {
"grafana.app/deprecatedInternalID": "2409"
},
"annotations": {
"grafana.app/createdBy": "user:u000000001",
"grafana.app/updatedBy": "user:u000000001",
"grafana.app/updatedTimestamp": "2025-12-16T10:51:14Z"
}
},
"spec": {
"annotations": [
{
"kind": "AnnotationQuery",
"spec": {
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"query": {
"kind": "grafana",
"spec": {}
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"builtIn": true
}
}
],
"cursorSync": "Off",
"description": "",
"editable": true,
"elements": {
"panel-1": {
"kind": "Panel",
"spec": {
"id": 1,
"title": "Panel1",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "grafana-testdata-datasource",
"spec": {}
},
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "timeseries",
"spec": {
"pluginVersion": "12.4.0-pre",
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
},
"panel-2": {
"kind": "Panel",
"spec": {
"id": 2,
"title": "Panel2",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "",
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "timeseries",
"spec": {
"pluginVersion": "12.4.0-pre",
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
},
"panel-3": {
"kind": "Panel",
"spec": {
"id": 3,
"title": "Panel3",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "",
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "timeseries",
"spec": {
"pluginVersion": "12.4.0-pre",
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
},
"panel-4": {
"kind": "Panel",
"spec": {
"id": 4,
"title": "Panel4",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "",
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "timeseries",
"spec": {
"pluginVersion": "12.4.0-pre",
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
},
"panel-5": {
"kind": "Panel",
"spec": {
"id": 5,
"title": "Panel5",
"description": "",
"links": [],
"data": {
"kind": "QueryGroup",
"spec": {
"queries": [
{
"kind": "PanelQuery",
"spec": {
"query": {
"kind": "",
"spec": {}
},
"refId": "A",
"hidden": false
}
}
],
"transformations": [],
"queryOptions": {}
}
},
"vizConfig": {
"kind": "timeseries",
"spec": {
"pluginVersion": "12.4.0-pre",
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"hideZeros": false,
"mode": "single",
"sort": "none"
}
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"value": 0,
"color": "green"
},
{
"value": 80,
"color": "red"
}
]
},
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
}
},
"overrides": []
}
}
}
}
}
},
"layout": {
"kind": "TabsLayout",
"spec": {
"tabs": [
{
"kind": "TabsLayoutTab",
"spec": {
"title": "Tab1",
"layout": {
"kind": "GridLayout",
"spec": {
"items": [
{
"kind": "GridLayoutItem",
"spec": {
"x": 0,
"y": 0,
"width": 7,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-1"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 7,
"y": 0,
"width": 8,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-2"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 15,
"y": 0,
"width": 9,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-3"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 0,
"y": 8,
"width": 12,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-4"
}
}
},
{
"kind": "GridLayoutItem",
"spec": {
"x": 12,
"y": 8,
"width": 12,
"height": 8,
"element": {
"kind": "ElementReference",
"name": "panel-5"
}
}
}
]
}
}
}
}
]
}
},
"links": [],
"liveNow": false,
"preload": false,
"tags": [],
"timeSettings": {
"timezone": "browser",
"from": "now-6h",
"to": "now",
"autoRefresh": "",
"autoRefreshIntervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"hideTimepicker": false,
"fiscalYearStartMonth": 0
},
"title": "Dashboard with tabs",
"variables": []
},
"status": {
"conversion": {
"failed": false,
"storedVersion": "v2beta1"
}
}
}
@@ -495,9 +495,6 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
currentY = getMaxYFromPanels(nestedPanels, currentY)
} else if tab.Spec.Layout.GridLayoutKind != nil {
// GridLayout inside tab
baseY := currentY
maxY := currentY
for _, item := range tab.Spec.Layout.GridLayoutKind.Spec.Items {
element, ok := elements[item.Spec.Element.Name]
if !ok {
@@ -505,7 +502,7 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
}
adjustedItem := item
adjustedItem.Spec.Y = item.Spec.Y + baseY
adjustedItem.Spec.Y = item.Spec.Y + currentY
panel, err := convertPanelFromElement(&element, &adjustedItem)
if err != nil {
@@ -514,12 +511,10 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash
panels = append(panels, panel)
panelEndY := adjustedItem.Spec.Y + item.Spec.Height
if panelEndY > maxY {
maxY = panelEndY
if panelEndY > currentY {
currentY = panelEndY
}
}
currentY = maxY
} else if tab.Spec.Layout.AutoGridLayoutKind != nil {
// AutoGridLayout inside tab - convert with Y offset
autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, tab.Spec.Layout.AutoGridLayoutKind, currentY)
@@ -5,11 +5,12 @@ import (
"sync"
"time"
"github.com/hashicorp/golang-lru/v2/expirable"
"k8s.io/apiserver/pkg/endpoints/request"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/hashicorp/golang-lru/v2/expirable"
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
const defaultCacheSize = 1000
@@ -31,15 +32,17 @@ type cachedProvider[T any] struct {
fetch func(context.Context) T
cache *expirable.LRU[string, T] // LRU cache: namespace to cache entry
inFlight sync.Map // map[string]*sync.Mutex - per-namespace fetch locks
logger log.Logger
}
// newCachedProvider creates a new cachedProvider.
// The fetch function should be able to handle context with different namespaces.
// A non-positive size turns LRU mechanism off (cache of unlimited size).
// A non-positive cacheTTL disables TTL expiration.
func newCachedProvider[T any](fetch func(context.Context) T, size int, cacheTTL time.Duration) *cachedProvider[T] {
func newCachedProvider[T any](fetch func(context.Context) T, size int, cacheTTL time.Duration, logger log.Logger) *cachedProvider[T] {
cacheProvider := &cachedProvider[T]{
fetch: fetch,
fetch: fetch,
logger: logger,
}
cacheProvider.cache = expirable.NewLRU(size, func(key string, value T) {
cacheProvider.inFlight.Delete(key)
@@ -50,13 +53,14 @@ func newCachedProvider[T any](fetch func(context.Context) T, size int, cacheTTL
// Get returns the cached value if it's still valid, otherwise calls fetch and caches the result.
func (p *cachedProvider[T]) Get(ctx context.Context) T {
// Get namespace info from ctx
namespace, ok := request.NamespaceFrom(ctx)
if !ok {
nsInfo, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
// No namespace, fall back to direct fetch call without caching
logging.FromContext(ctx).Warn("Unable to get namespace info from context, skipping cache")
p.logger.Warn("Unable to get namespace info from context, skipping cache", "error", err)
return p.fetch(ctx)
}
namespace := nsInfo.Value
// Fast path: check if cache is still valid
if entry, ok := p.cache.Get(namespace); ok {
return entry
@@ -77,7 +81,7 @@ func (p *cachedProvider[T]) Get(ctx context.Context) T {
}
// Fetch outside the main lock - only this namespace is blocked
logging.FromContext(ctx).Debug("cache miss or expired, fetching new value", "namespace", namespace)
p.logger.Debug("cache miss or expired, fetching new value", "namespace", namespace)
value := p.fetch(ctx)
// Update the cache for this namespace
@@ -89,12 +93,12 @@ func (p *cachedProvider[T]) Get(ctx context.Context) T {
// Preload loads data into the cache for the given namespaces.
func (p *cachedProvider[T]) Preload(ctx context.Context, nsInfos []types.NamespaceInfo) {
// Build the cache using a context with the namespace
logging.FromContext(ctx).Info("preloading cache", "nsInfos", len(nsInfos))
p.logger.Info("preloading cache", "nsInfos", len(nsInfos))
startedAt := time.Now()
defer func() {
logging.FromContext(ctx).Info("finished preloading cache", "nsInfos", len(nsInfos), "elapsed", time.Since(startedAt))
p.logger.Info("finished preloading cache", "nsInfos", len(nsInfos), "elapsed", time.Since(startedAt))
}()
for _, nsInfo := range nsInfos {
p.cache.Add(nsInfo.Value, p.fetch(request.WithNamespace(ctx, nsInfo.Value)))
p.cache.Add(nsInfo.Value, p.fetch(k8srequest.WithNamespace(ctx, nsInfo.Value)))
}
}
@@ -8,11 +8,11 @@ import (
"testing"
"time"
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apiserver/pkg/endpoints/request"
authlib "github.com/grafana/authlib/types"
)
// testProvider tracks how many times get() is called
@@ -44,7 +44,7 @@ func TestCachedProvider_CacheHit(t *testing.T) {
underlying := newTestProvider(datasources)
// Test newCachedProvider directly instead of the wrapper
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test"))
// Use "default" namespace (org 1) - this is the standard Grafana namespace format
ctx := request.WithNamespace(context.Background(), "default")
@@ -69,7 +69,7 @@ func TestCachedProvider_NamespaceIsolation(t *testing.T) {
}
underlying := newTestProvider(datasources)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test"))
// Use "default" (org 1) and "org-2" (org 2) - standard Grafana namespace formats
ctx1 := request.WithNamespace(context.Background(), "default")
@@ -102,7 +102,7 @@ func TestCachedProvider_NoNamespaceFallback(t *testing.T) {
}
underlying := newTestProvider(datasources)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test"))
// Context without namespace - should fall back to direct provider call
ctx := context.Background()
@@ -123,7 +123,7 @@ func TestCachedProvider_ConcurrentAccess(t *testing.T) {
}
underlying := newTestProvider(datasources)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test"))
// Use "default" namespace (org 1)
ctx := request.WithNamespace(context.Background(), "default")
@@ -155,7 +155,7 @@ func TestCachedProvider_ConcurrentNamespaces(t *testing.T) {
}
underlying := newTestProvider(datasources)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute)
cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test"))
var wg sync.WaitGroup
numOrgs := 10
@@ -198,7 +198,7 @@ func TestCachedProvider_CorrectDataPerNamespace(t *testing.T) {
"org-2": {{UID: "org2-ds", Type: "loki", Name: "Org2 DS", Default: true}},
},
}
cached := newCachedProvider(underlying.Index, defaultCacheSize, time.Minute)
cached := newCachedProvider(underlying.Index, defaultCacheSize, time.Minute, log.New("test"))
// Use valid namespace formats
ctx1 := request.WithNamespace(context.Background(), "default")
@@ -228,7 +228,7 @@ func TestCachedProvider_PreloadMultipleNamespaces(t *testing.T) {
"org-3": {{UID: "org3-ds", Type: "tempo", Name: "Org3 DS", Default: true}},
},
}
cached := newCachedProvider(underlying.Index, defaultCacheSize, time.Minute)
cached := newCachedProvider(underlying.Index, defaultCacheSize, time.Minute, log.New("test"))
// Preload multiple namespaces
nsInfos := []authlib.NamespaceInfo{
@@ -346,7 +346,7 @@ func TestCachedProvider_TTLExpiration(t *testing.T) {
underlying := newTestProvider(datasources)
// Use a very short TTL for testing
shortTTL := 50 * time.Millisecond
cached := newCachedProvider(underlying.get, defaultCacheSize, shortTTL)
cached := newCachedProvider(underlying.get, defaultCacheSize, shortTTL, log.New("test"))
ctx := request.WithNamespace(context.Background(), "default")
@@ -379,7 +379,7 @@ func TestCachedProvider_ParallelNamespacesFetch(t *testing.T) {
{UID: "ds1", Type: "prometheus", Name: "Prometheus", Default: true},
},
}
cached := newCachedProvider(provider.get, defaultCacheSize, time.Minute)
cached := newCachedProvider(provider.get, defaultCacheSize, time.Minute, log.New("test"))
numNamespaces := 5
var wg sync.WaitGroup
@@ -421,7 +421,7 @@ func TestCachedProvider_SameNamespaceSerialFetch(t *testing.T) {
{UID: "ds1", Type: "prometheus", Name: "Prometheus", Default: true},
},
}
cached := newCachedProvider(provider.get, defaultCacheSize, time.Minute)
cached := newCachedProvider(provider.get, defaultCacheSize, time.Minute, log.New("test"))
numGoroutines := 10
var wg sync.WaitGroup
@@ -3,6 +3,8 @@ package schemaversion
import (
"context"
"time"
"github.com/grafana/grafana/pkg/infra/log"
)
// Shared utility functions for datasource migrations across different schema versions.
@@ -34,7 +36,7 @@ func WrapIndexProviderWithCache(provider DataSourceIndexProvider, cacheTTL time.
return provider
}
return &cachedIndexProvider{
newCachedProvider[*DatasourceIndex](provider.Index, defaultCacheSize, cacheTTL),
newCachedProvider[*DatasourceIndex](provider.Index, defaultCacheSize, cacheTTL, log.New("schemaversion.dsindexprovider")),
}
}
@@ -44,7 +46,7 @@ func WrapLibraryElementProviderWithCache(provider LibraryElementIndexProvider, c
return provider
}
return &cachedLibraryElementProvider{
newCachedProvider[[]LibraryElementInfo](provider.GetLibraryElementInfo, defaultCacheSize, cacheTTL),
newCachedProvider[[]LibraryElementInfo](provider.GetLibraryElementInfo, defaultCacheSize, cacheTTL, log.New("schemaversion.leindexprovider")),
}
}
-73
View File
@@ -1,73 +0,0 @@
package repository
connection: {
kind: "Connection"
pluralName: "Connections"
current: "v0alpha1"
validation: {
operations: [
"CREATE",
"UPDATE",
]
}
versions: {
"v0alpha1": {
codegen: {
ts: {enabled: false}
go: {enabled: true}
}
schema: {
#GitHubConnectionConfig: {
// App-level information
// GitHub App ID
appID: int
// Installation-level information
// GitHub App installation ID
installationID: int
}
#BitbucketConnectionConfig: {
// The app clientID
clientID: string
}
#GitlabConnectionConfig: {
// The app clientID
clientID: string
}
#HealthStatus: {
// When not healthy, requests will not be executed
healthy: bool
// When the health was checked last time
checked?: int
// Summary messages (can be shown to users)
// Will only be populated when not healthy
message?: [...string]
}
spec: {
// The connection provider type
type: "github" | "bitbucket" | "gitlab"
// The connection URL
url: *"" | string
// GitHub connection configuration
// Only applicable when provider is "github"
github?: #GitHubConnectionConfig
// Bitbucket connection configuration
// Only applicable when provider is "bitbucket"
bitbucket?: #BitbucketConnectionConfig
// Gitlab connection configuration
// Only applicable when provider is "gitlab"
gitlab?: #GitlabConnectionConfig
}
status: {
// The generation of the spec last time reconciliation ran
observedGeneration?: int
// Connection state
state: "connected" | "disconnected"
// The connection health status
health: #HealthStatus
}
}
}
}
}
+1 -2
View File
@@ -5,6 +5,5 @@ manifest: {
groupOverride: "provisioning.grafana.app"
kinds: [
repository,
connection
]
}
}
@@ -1,118 +0,0 @@
package v0alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
)
// When this code is changed, make sure to update the code generation.
// As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning
// If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type Connection struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ConnectionSpec `json:"spec,omitempty"`
Secure ConnectionSecure `json:"secure,omitzero,omitempty"`
Status ConnectionStatus `json:"status,omitempty"`
}
type ConnectionSecure struct {
// PrivateKey is the reference to the private key used for GitHub App authentication.
// This value is stored securely and cannot be read back
PrivateKey common.InlineSecureValue `json:"privateKey,omitzero,omitempty"`
// ClientSecret is the reference to the secret used for other providers authentication,
// and Github on-behalf-of authentication.
// This value is stored securely and cannot be read back
ClientSecret common.InlineSecureValue `json:"clientSecret,omitzero,omitempty"`
// Token is the reference of the token used to act as the Connection.
// This value is stored securely and cannot be read back
Token common.InlineSecureValue `json:"webhook,omitzero,omitempty"`
}
func (v ConnectionSecure) IsZero() bool {
return v.PrivateKey.IsZero() && v.Token.IsZero()
}
type GitHubConnectionConfig struct {
// GitHub App ID
AppID string `json:"appID"`
// GitHub App installation ID
InstallationID string `json:"installationID"`
}
type BitbucketConnectionConfig struct {
// App client ID
ClientID string `json:"clientID"`
}
type GitlabConnectionConfig struct {
// App client ID
ClientID string `json:"clientID"`
}
// ConnectionType defines the types of Connection providers
// +enum
type ConnectionType string
// ConnectionType values.
const (
GithubConnectionType ConnectionType = "github"
GitlabConnectionType ConnectionType = "gitlab"
BitbucketConnectionType ConnectionType = "bitbucket"
)
type ConnectionSpec struct {
// The connection provider type
Type ConnectionType `json:"type"`
// The connection URL
URL string `json:"url,omitempty"`
// GitHub connection configuration
// Only applicable when provider is "github"
GitHub *GitHubConnectionConfig `json:"github,omitempty"`
// Bitbucket connection configuration
// Only applicable when provider is "bitbucket"
Bitbucket *BitbucketConnectionConfig `json:"bitbucket,omitempty"`
// Gitlab connection configuration
// Only applicable when provider is "gitlab"
Gitlab *GitlabConnectionConfig `json:"gitlab,omitempty"`
}
// ConnectionState defines the state of a Connection
// +enum
type ConnectionState string
// ConnectionState values
const (
ConnectionStateConnected ConnectionState = "connected"
ConnectionStateDisconnected ConnectionState = "disconnected"
)
// The status of a Connection.
// This is expected never to be created by a kubectl call or similar, and is expected to rarely (if ever) be edited manually.
type ConnectionStatus struct {
// The generation of the spec last time reconciliation ran
ObservedGeneration int64 `json:"observedGeneration"`
// Connection state
State ConnectionState `json:"state"`
// The connection health status
Health HealthStatus `json:"health"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ConnectionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
// +listType=atomic
Items []Connection `json:"items"`
}
@@ -1,26 +0,0 @@
package v0alpha1
// HealthFailureType represents different types of healthcheck failures
// +enum
type HealthFailureType string
const (
HealthFailureHook HealthFailureType = "hook"
HealthFailureHealth HealthFailureType = "health"
)
type HealthStatus struct {
// When not healthy, requests will not be executed
Healthy bool `json:"healthy"`
// The type of the error
Error HealthFailureType `json:"error,omitempty"`
// When the health was checked last time
Checked int64 `json:"checked,omitempty"`
// Summary messages (can be shown to users)
// Will only be populated when not healthy
// +listType=atomic
Message []string `json:"message,omitempty"`
}
@@ -115,47 +115,6 @@ var HistoricJobResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
},
})
var ConnectionResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
"connections", "connection", "Connection",
func() runtime.Object { return &Connection{} }, // newObj
func() runtime.Object { return &ConnectionList{} }, // newList
utils.TableColumns{ // Returned by `kubectl get`. Doesn't affect disk storage.
Definition: []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Created At", Type: "date"},
{Name: "Type", Type: "string"},
{Name: "AppID", Type: "string"},
{Name: "InstallationID", Type: "string"},
{Name: "ClientID", Type: "string"},
},
Reader: func(obj any) ([]interface{}, error) {
m, ok := obj.(*Connection)
if !ok {
return nil, errors.New("expected Repository")
}
var appID, installationID, clientID string
switch m.Spec.Type {
case GithubConnectionType:
appID = m.Spec.GitHub.AppID
installationID = m.Spec.GitHub.InstallationID
case BitbucketConnectionType:
clientID = m.Spec.Bitbucket.ClientID
case GitlabConnectionType:
clientID = m.Spec.Gitlab.ClientID
}
return []interface{}{
m.Name,
m.CreationTimestamp.UTC().Format(time.RFC3339),
m.Spec.Type,
appID,
installationID,
clientID,
}, nil
},
})
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
@@ -195,8 +154,6 @@ func AddKnownTypes(gv schema.GroupVersion, scheme *runtime.Scheme) error {
&RefList{},
&HistoricJob{},
&HistoricJobList{},
&Connection{},
&ConnectionList{},
)
return nil
}
@@ -315,6 +315,31 @@ type RepositoryStatus struct {
DeleteError string `json:"deleteError,omitempty"`
}
// HealthFailureType represents different types of repository failures
// +enum
type HealthFailureType string
const (
HealthFailureHook HealthFailureType = "hook"
HealthFailureHealth HealthFailureType = "health"
)
type HealthStatus struct {
// When not healthy, requests will not be executed
Healthy bool `json:"healthy"`
// The type of the error
Error HealthFailureType `json:"error,omitempty"`
// When the health was checked last time
Checked int64 `json:"checked,omitempty"`
// Summary messages (can be shown to users)
// Will only be populated when not healthy
// +listType=atomic
Message []string `json:"message,omitempty"`
}
type SyncStatus struct {
// pending, running, success, error
State JobState `json:"state"`
@@ -27,22 +27,6 @@ func (in *Author) DeepCopy() *Author {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BitbucketConnectionConfig) DeepCopyInto(out *BitbucketConnectionConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BitbucketConnectionConfig.
func (in *BitbucketConnectionConfig) DeepCopy() *BitbucketConnectionConfig {
if in == nil {
return nil
}
out := new(BitbucketConnectionConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BitbucketRepositoryConfig) DeepCopyInto(out *BitbucketRepositoryConfig) {
*out = *in
@@ -59,135 +43,6 @@ func (in *BitbucketRepositoryConfig) DeepCopy() *BitbucketRepositoryConfig {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Connection) DeepCopyInto(out *Connection) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Secure = in.Secure
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection.
func (in *Connection) DeepCopy() *Connection {
if in == nil {
return nil
}
out := new(Connection)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Connection) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConnectionList) DeepCopyInto(out *ConnectionList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Connection, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionList.
func (in *ConnectionList) DeepCopy() *ConnectionList {
if in == nil {
return nil
}
out := new(ConnectionList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ConnectionList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConnectionSecure) DeepCopyInto(out *ConnectionSecure) {
*out = *in
out.PrivateKey = in.PrivateKey
out.ClientSecret = in.ClientSecret
out.Token = in.Token
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionSecure.
func (in *ConnectionSecure) DeepCopy() *ConnectionSecure {
if in == nil {
return nil
}
out := new(ConnectionSecure)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConnectionSpec) DeepCopyInto(out *ConnectionSpec) {
*out = *in
if in.GitHub != nil {
in, out := &in.GitHub, &out.GitHub
*out = new(GitHubConnectionConfig)
**out = **in
}
if in.Bitbucket != nil {
in, out := &in.Bitbucket, &out.Bitbucket
*out = new(BitbucketConnectionConfig)
**out = **in
}
if in.Gitlab != nil {
in, out := &in.Gitlab, &out.Gitlab
*out = new(GitlabConnectionConfig)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionSpec.
func (in *ConnectionSpec) DeepCopy() *ConnectionSpec {
if in == nil {
return nil
}
out := new(ConnectionSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConnectionStatus) DeepCopyInto(out *ConnectionStatus) {
*out = *in
in.Health.DeepCopyInto(&out.Health)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionStatus.
func (in *ConnectionStatus) DeepCopy() *ConnectionStatus {
if in == nil {
return nil
}
out := new(ConnectionStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DeleteJobOptions) DeepCopyInto(out *DeleteJobOptions) {
*out = *in
@@ -293,22 +148,6 @@ func (in *FileList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitHubConnectionConfig) DeepCopyInto(out *GitHubConnectionConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubConnectionConfig.
func (in *GitHubConnectionConfig) DeepCopy() *GitHubConnectionConfig {
if in == nil {
return nil
}
out := new(GitHubConnectionConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitHubRepositoryConfig) DeepCopyInto(out *GitHubRepositoryConfig) {
*out = *in
@@ -357,22 +196,6 @@ func (in *GitRepositoryConfig) DeepCopy() *GitRepositoryConfig {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitlabConnectionConfig) DeepCopyInto(out *GitlabConnectionConfig) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabConnectionConfig.
func (in *GitlabConnectionConfig) DeepCopy() *GitlabConnectionConfig {
if in == nil {
return nil
}
out := new(GitlabConnectionConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HealthStatus) DeepCopyInto(out *HealthStatus) {
*out = *in
@@ -15,23 +15,15 @@ import (
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Author": schema_pkg_apis_provisioning_v0alpha1_Author(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketConnectionConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketConnectionConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Connection": schema_pkg_apis_provisioning_v0alpha1_Connection(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionList": schema_pkg_apis_provisioning_v0alpha1_ConnectionList(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSecure": schema_pkg_apis_provisioning_v0alpha1_ConnectionSecure(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSpec": schema_pkg_apis_provisioning_v0alpha1_ConnectionSpec(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionStatus": schema_pkg_apis_provisioning_v0alpha1_ConnectionStatus(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.DeleteJobOptions": schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ErrorDetails": schema_pkg_apis_provisioning_v0alpha1_ErrorDetails(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExportJobOptions": schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileItem": schema_pkg_apis_provisioning_v0alpha1_FileItem(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileList": schema_pkg_apis_provisioning_v0alpha1_FileList(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubConnectionConfig": schema_pkg_apis_provisioning_v0alpha1_GitHubConnectionConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitHubRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitLabRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitRepositoryConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitlabConnectionConfig": schema_pkg_apis_provisioning_v0alpha1_GitlabConnectionConfig(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus": schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoricJob": schema_pkg_apis_provisioning_v0alpha1_HistoricJob(ref),
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoricJobList": schema_pkg_apis_provisioning_v0alpha1_HistoricJobList(ref),
@@ -108,27 +100,6 @@ func schema_pkg_apis_provisioning_v0alpha1_Author(ref common.ReferenceCallback)
}
}
func schema_pkg_apis_provisioning_v0alpha1_BitbucketConnectionConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"clientID": {
SchemaProps: spec.SchemaProps{
Description: "App client ID",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"clientID"},
},
},
}
}
func schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -171,236 +142,6 @@ func schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref common.
}
}
func schema_pkg_apis_provisioning_v0alpha1_Connection(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "When this code is changed, make sure to update the code generation. As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSpec"),
},
},
"secure": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSecure"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionStatus"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSecure", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSpec", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_pkg_apis_provisioning_v0alpha1_ConnectionList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Connection"),
},
},
},
},
},
},
Required: []string{"items"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Connection", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_pkg_apis_provisioning_v0alpha1_ConnectionSecure(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"privateKey": {
SchemaProps: spec.SchemaProps{
Description: "PrivateKey is the reference to the private key used for GitHub App authentication. This value is stored securely and cannot be read back",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"),
},
},
"clientSecret": {
SchemaProps: spec.SchemaProps{
Description: "ClientSecret is the reference to the secret used for other providers authentication, and Github on-behalf-of authentication. This value is stored securely and cannot be read back",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"),
},
},
"webhook": {
SchemaProps: spec.SchemaProps{
Description: "Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"},
}
}
func schema_pkg_apis_provisioning_v0alpha1_ConnectionSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"type": {
SchemaProps: spec.SchemaProps{
Description: "The connection provider type\n\nPossible enum values:\n - `\"bitbucket\"`\n - `\"github\"`\n - `\"gitlab\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"bitbucket", "github", "gitlab"},
},
},
"url": {
SchemaProps: spec.SchemaProps{
Description: "The connection URL",
Type: []string{"string"},
Format: "",
},
},
"github": {
SchemaProps: spec.SchemaProps{
Description: "GitHub connection configuration Only applicable when provider is \"github\"",
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubConnectionConfig"),
},
},
"bitbucket": {
SchemaProps: spec.SchemaProps{
Description: "Bitbucket connection configuration Only applicable when provider is \"bitbucket\"",
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketConnectionConfig"),
},
},
"gitlab": {
SchemaProps: spec.SchemaProps{
Description: "Gitlab connection configuration Only applicable when provider is \"gitlab\"",
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitlabConnectionConfig"),
},
},
},
Required: []string{"type"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketConnectionConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubConnectionConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitlabConnectionConfig"},
}
}
func schema_pkg_apis_provisioning_v0alpha1_ConnectionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "The status of a Connection. This is expected never to be created by a kubectl call or similar, and is expected to rarely (if ever) be edited manually.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"observedGeneration": {
SchemaProps: spec.SchemaProps{
Description: "The generation of the spec last time reconciliation ran",
Default: 0,
Type: []string{"integer"},
Format: "int64",
},
},
"state": {
SchemaProps: spec.SchemaProps{
Description: "Connection state\n\nPossible enum values:\n - `\"connected\"`\n - `\"disconnected\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"connected", "disconnected"},
},
},
"health": {
SchemaProps: spec.SchemaProps{
Description: "The connection health status",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus"),
},
},
},
Required: []string{"observedGeneration", "state", "health"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus"},
}
}
func schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -621,35 +362,6 @@ func schema_pkg_apis_provisioning_v0alpha1_FileList(ref common.ReferenceCallback
}
}
func schema_pkg_apis_provisioning_v0alpha1_GitHubConnectionConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"appID": {
SchemaProps: spec.SchemaProps{
Description: "GitHub App ID",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"installationID": {
SchemaProps: spec.SchemaProps{
Description: "GitHub App installation ID",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"appID", "installationID"},
},
},
}
}
func schema_pkg_apis_provisioning_v0alpha1_GitHubRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -769,27 +481,6 @@ func schema_pkg_apis_provisioning_v0alpha1_GitRepositoryConfig(ref common.Refere
}
}
func schema_pkg_apis_provisioning_v0alpha1_GitlabConnectionConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"clientID": {
SchemaProps: spec.SchemaProps{
Description: "App client ID",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"clientID"},
},
},
}
}
func schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1,4 +1,3 @@
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Paths
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Resources
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,FileList,Items
@@ -21,8 +20,6 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioni
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ResourceList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,TestResults,Errors
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSecure,Token
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSpec,GitHub
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,URLs
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ManagerStats,Identity
@@ -1,28 +0,0 @@
package connection
import (
"fmt"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
const (
githubInstallationURL = "https://github.com/settings/installations"
)
func MutateConnection(connection *provisioning.Connection) error {
switch connection.Spec.Type {
case provisioning.GithubConnectionType:
// Do nothing in case spec.Github is nil.
// If this field is required, we should fail at validation time.
if connection.Spec.GitHub == nil {
return nil
}
connection.Spec.URL = fmt.Sprintf("%s/%s", githubInstallationURL, connection.Spec.GitHub.InstallationID)
return nil
default:
// TODO: we need to setup the URL for bitbucket and gitlab.
return nil
}
}
@@ -1,35 +0,0 @@
package connection_test
import (
"testing"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestMutateConnection(t *testing.T) {
t.Run("should add URL to Github connection", func(t *testing.T) {
c := &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
},
}
require.NoError(t, connection.MutateConnection(c))
assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
})
}
@@ -1,104 +0,0 @@
package connection
import (
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/validation/field"
)
func ValidateConnection(connection *provisioning.Connection) error {
list := field.ErrorList{}
if connection.Spec.Type == "" {
list = append(list, field.Required(field.NewPath("spec", "type"), "type must be specified"))
}
switch connection.Spec.Type {
case provisioning.GithubConnectionType:
list = append(list, validateGithubConnection(connection)...)
case provisioning.BitbucketConnectionType:
list = append(list, validateBitbucketConnection(connection)...)
case provisioning.GitlabConnectionType:
list = append(list, validateGitlabConnection(connection)...)
default:
list = append(
list, field.NotSupported(
field.NewPath("spec", "type"),
connection.Spec.Type,
[]provisioning.ConnectionType{
provisioning.GithubConnectionType,
provisioning.BitbucketConnectionType,
provisioning.GitlabConnectionType,
}),
)
}
return toError(connection.GetName(), list)
}
func validateGithubConnection(connection *provisioning.Connection) field.ErrorList {
list := field.ErrorList{}
if connection.Spec.GitHub == nil {
list = append(
list, field.Required(field.NewPath("spec", "github"), "github info must be specified for GitHub connection"),
)
}
if connection.Secure.PrivateKey.IsZero() {
list = append(list, field.Required(field.NewPath("secure", "privateKey"), "privateKey must be specified for GitHub connection"))
}
if !connection.Secure.ClientSecret.IsZero() {
list = append(list, field.Forbidden(field.NewPath("secure", "clientSecret"), "clientSecret is forbidden in GitHub connection"))
}
return list
}
func validateBitbucketConnection(connection *provisioning.Connection) field.ErrorList {
list := field.ErrorList{}
if connection.Spec.Bitbucket == nil {
list = append(
list, field.Required(field.NewPath("spec", "bitbucket"), "bitbucket info must be specified in Bitbucket connection"),
)
}
if connection.Secure.ClientSecret.IsZero() {
list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Bitbucket connection"))
}
if !connection.Secure.PrivateKey.IsZero() {
list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Bitbucket connection"))
}
return list
}
func validateGitlabConnection(connection *provisioning.Connection) field.ErrorList {
list := field.ErrorList{}
if connection.Spec.Gitlab == nil {
list = append(
list, field.Required(field.NewPath("spec", "gitlab"), "gitlab info must be specified in Gitlab connection"),
)
}
if connection.Secure.ClientSecret.IsZero() {
list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Gitlab connection"))
}
if !connection.Secure.PrivateKey.IsZero() {
list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Gitlab connection"))
}
return list
}
// toError converts a field.ErrorList to an error, returning nil if the list is empty
func toError(name string, list field.ErrorList) error {
if len(list) == 0 {
return nil
}
return apierrors.NewInvalid(
provisioning.ConnectionResourceInfo.GroupVersionKind().GroupKind(),
name,
list,
)
}
@@ -1,253 +0,0 @@
package connection_test
import (
"testing"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestValidateConnection(t *testing.T) {
tests := []struct {
name string
connection *provisioning.Connection
wantErr bool
errMsg string
}{
{
name: "empty type returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{},
},
wantErr: true,
errMsg: "spec.type",
},
{
name: "invalid type returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: "invalid",
},
},
wantErr: true,
errMsg: "spec.type",
},
{
name: "github type without github config returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
},
},
wantErr: true,
errMsg: "spec.github",
},
{
name: "github type without private key returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
},
wantErr: true,
errMsg: "secure.privateKey",
},
{
name: "github type with client secret returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: true,
errMsg: "secure.clientSecret",
},
{
name: "github type with github config is valid",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GithubConnectionType,
GitHub: &provisioning.GitHubConnectionConfig{
AppID: "123",
InstallationID: "456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
},
},
wantErr: false,
},
{
name: "bitbucket type without bitbucket config returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.BitbucketConnectionType,
},
},
wantErr: true,
errMsg: "spec.bitbucket",
},
{
name: "bitbucket type without client secret returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.BitbucketConnectionType,
Bitbucket: &provisioning.BitbucketConnectionConfig{
ClientID: "client-123",
},
},
},
wantErr: true,
errMsg: "secure.clientSecret",
},
{
name: "bitbucket type with private key returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.BitbucketConnectionType,
Bitbucket: &provisioning.BitbucketConnectionConfig{
ClientID: "client-123",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: true,
errMsg: "secure.privateKey",
},
{
name: "bitbucket type with bitbucket config is valid",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.BitbucketConnectionType,
Bitbucket: &provisioning.BitbucketConnectionConfig{
ClientID: "client-123",
},
},
Secure: provisioning.ConnectionSecure{
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: false,
},
{
name: "gitlab type without gitlab config returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
},
},
wantErr: true,
errMsg: "spec.gitlab",
},
{
name: "gitlab type without client secret returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
Gitlab: &provisioning.GitlabConnectionConfig{
ClientID: "client-456",
},
},
},
wantErr: true,
errMsg: "secure.clientSecret",
},
{
name: "gitlab type with private key returns error",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
Gitlab: &provisioning.GitlabConnectionConfig{
ClientID: "client-456",
},
},
Secure: provisioning.ConnectionSecure{
PrivateKey: common.InlineSecureValue{
Name: "test-private-key",
},
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: true,
errMsg: "secure.privateKey",
},
{
name: "gitlab type with gitlab config is valid",
connection: &provisioning.Connection{
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
Spec: provisioning.ConnectionSpec{
Type: provisioning.GitlabConnectionType,
Gitlab: &provisioning.GitlabConnectionConfig{
ClientID: "client-456",
},
},
Secure: provisioning.ConnectionSecure{
ClientSecret: common.InlineSecureValue{
Name: "test-client-secret",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := connection.ValidateConnection(tt.connection)
if tt.wantErr {
assert.Error(t, err)
if tt.errMsg != "" {
assert.Contains(t, err.Error(), tt.errMsg)
}
} else {
assert.NoError(t, err)
}
})
}
}
@@ -1,25 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
// BitbucketConnectionConfigApplyConfiguration represents a declarative configuration of the BitbucketConnectionConfig type for use
// with apply.
type BitbucketConnectionConfigApplyConfiguration struct {
ClientID *string `json:"clientID,omitempty"`
}
// BitbucketConnectionConfigApplyConfiguration constructs a declarative configuration of the BitbucketConnectionConfig type for use with
// apply.
func BitbucketConnectionConfig() *BitbucketConnectionConfigApplyConfiguration {
return &BitbucketConnectionConfigApplyConfiguration{}
}
// WithClientID sets the ClientID field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ClientID field is set to the value of the last call.
func (b *BitbucketConnectionConfigApplyConfiguration) WithClientID(value string) *BitbucketConnectionConfigApplyConfiguration {
b.ClientID = &value
return b
}
@@ -1,237 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
)
// ConnectionApplyConfiguration represents a declarative configuration of the Connection type for use
// with apply.
type ConnectionApplyConfiguration struct {
v1.TypeMetaApplyConfiguration `json:",inline"`
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
Spec *ConnectionSpecApplyConfiguration `json:"spec,omitempty"`
Secure *ConnectionSecureApplyConfiguration `json:"secure,omitempty"`
Status *ConnectionStatusApplyConfiguration `json:"status,omitempty"`
}
// Connection constructs a declarative configuration of the Connection type for use with
// apply.
func Connection(name, namespace string) *ConnectionApplyConfiguration {
b := &ConnectionApplyConfiguration{}
b.WithName(name)
b.WithNamespace(namespace)
b.WithKind("Connection")
b.WithAPIVersion("provisioning.grafana.app/v0alpha1")
return b
}
func (b ConnectionApplyConfiguration) IsApplyConfiguration() {}
// WithKind sets the Kind field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Kind field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithKind(value string) *ConnectionApplyConfiguration {
b.TypeMetaApplyConfiguration.Kind = &value
return b
}
// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the APIVersion field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithAPIVersion(value string) *ConnectionApplyConfiguration {
b.TypeMetaApplyConfiguration.APIVersion = &value
return b
}
// WithName sets the Name field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Name field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithName(value string) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.Name = &value
return b
}
// WithGenerateName sets the GenerateName field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the GenerateName field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithGenerateName(value string) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.GenerateName = &value
return b
}
// WithNamespace sets the Namespace field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Namespace field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithNamespace(value string) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.Namespace = &value
return b
}
// WithUID sets the UID field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the UID field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithUID(value types.UID) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.UID = &value
return b
}
// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ResourceVersion field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithResourceVersion(value string) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.ResourceVersion = &value
return b
}
// WithGeneration sets the Generation field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Generation field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithGeneration(value int64) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.Generation = &value
return b
}
// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the CreationTimestamp field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.CreationTimestamp = &value
return b
}
// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the DeletionTimestamp field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value
return b
}
// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value
return b
}
// WithLabels puts the entries into the Labels field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Labels field,
// overwriting an existing map entries in Labels field with the same key.
func (b *ConnectionApplyConfiguration) WithLabels(entries map[string]string) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {
b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))
}
for k, v := range entries {
b.ObjectMetaApplyConfiguration.Labels[k] = v
}
return b
}
// WithAnnotations puts the entries into the Annotations field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Annotations field,
// overwriting an existing map entries in Annotations field with the same key.
func (b *ConnectionApplyConfiguration) WithAnnotations(entries map[string]string) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {
b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))
}
for k, v := range entries {
b.ObjectMetaApplyConfiguration.Annotations[k] = v
}
return b
}
// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the OwnerReferences field.
func (b *ConnectionApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
for i := range values {
if values[i] == nil {
panic("nil value passed to WithOwnerReferences")
}
b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])
}
return b
}
// WithFinalizers adds the given value to the Finalizers field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Finalizers field.
func (b *ConnectionApplyConfiguration) WithFinalizers(values ...string) *ConnectionApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
for i := range values {
b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])
}
return b
}
func (b *ConnectionApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {
if b.ObjectMetaApplyConfiguration == nil {
b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}
}
}
// WithSpec sets the Spec field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Spec field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithSpec(value *ConnectionSpecApplyConfiguration) *ConnectionApplyConfiguration {
b.Spec = value
return b
}
// WithSecure sets the Secure field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Secure field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithSecure(value *ConnectionSecureApplyConfiguration) *ConnectionApplyConfiguration {
b.Secure = value
return b
}
// WithStatus sets the Status field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Status field is set to the value of the last call.
func (b *ConnectionApplyConfiguration) WithStatus(value *ConnectionStatusApplyConfiguration) *ConnectionApplyConfiguration {
b.Status = value
return b
}
// GetKind retrieves the value of the Kind field in the declarative configuration.
func (b *ConnectionApplyConfiguration) GetKind() *string {
return b.TypeMetaApplyConfiguration.Kind
}
// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.
func (b *ConnectionApplyConfiguration) GetAPIVersion() *string {
return b.TypeMetaApplyConfiguration.APIVersion
}
// GetName retrieves the value of the Name field in the declarative configuration.
func (b *ConnectionApplyConfiguration) GetName() *string {
b.ensureObjectMetaApplyConfigurationExists()
return b.ObjectMetaApplyConfiguration.Name
}
// GetNamespace retrieves the value of the Namespace field in the declarative configuration.
func (b *ConnectionApplyConfiguration) GetNamespace() *string {
b.ensureObjectMetaApplyConfigurationExists()
return b.ObjectMetaApplyConfiguration.Namespace
}
@@ -1,47 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
import (
commonv0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
)
// ConnectionSecureApplyConfiguration represents a declarative configuration of the ConnectionSecure type for use
// with apply.
type ConnectionSecureApplyConfiguration struct {
PrivateKey *commonv0alpha1.InlineSecureValue `json:"privateKey,omitempty"`
ClientSecret *commonv0alpha1.InlineSecureValue `json:"clientSecret,omitempty"`
Token *commonv0alpha1.InlineSecureValue `json:"webhook,omitempty"`
}
// ConnectionSecureApplyConfiguration constructs a declarative configuration of the ConnectionSecure type for use with
// apply.
func ConnectionSecure() *ConnectionSecureApplyConfiguration {
return &ConnectionSecureApplyConfiguration{}
}
// WithPrivateKey sets the PrivateKey field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the PrivateKey field is set to the value of the last call.
func (b *ConnectionSecureApplyConfiguration) WithPrivateKey(value commonv0alpha1.InlineSecureValue) *ConnectionSecureApplyConfiguration {
b.PrivateKey = &value
return b
}
// WithClientSecret sets the ClientSecret field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ClientSecret field is set to the value of the last call.
func (b *ConnectionSecureApplyConfiguration) WithClientSecret(value commonv0alpha1.InlineSecureValue) *ConnectionSecureApplyConfiguration {
b.ClientSecret = &value
return b
}
// WithToken sets the Token field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Token field is set to the value of the last call.
func (b *ConnectionSecureApplyConfiguration) WithToken(value commonv0alpha1.InlineSecureValue) *ConnectionSecureApplyConfiguration {
b.Token = &value
return b
}
@@ -1,65 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
import (
provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// ConnectionSpecApplyConfiguration represents a declarative configuration of the ConnectionSpec type for use
// with apply.
type ConnectionSpecApplyConfiguration struct {
Type *provisioningv0alpha1.ConnectionType `json:"type,omitempty"`
URL *string `json:"url,omitempty"`
GitHub *GitHubConnectionConfigApplyConfiguration `json:"github,omitempty"`
Bitbucket *BitbucketConnectionConfigApplyConfiguration `json:"bitbucket,omitempty"`
Gitlab *GitlabConnectionConfigApplyConfiguration `json:"gitlab,omitempty"`
}
// ConnectionSpecApplyConfiguration constructs a declarative configuration of the ConnectionSpec type for use with
// apply.
func ConnectionSpec() *ConnectionSpecApplyConfiguration {
return &ConnectionSpecApplyConfiguration{}
}
// WithType sets the Type field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Type field is set to the value of the last call.
func (b *ConnectionSpecApplyConfiguration) WithType(value provisioningv0alpha1.ConnectionType) *ConnectionSpecApplyConfiguration {
b.Type = &value
return b
}
// WithURL sets the URL field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the URL field is set to the value of the last call.
func (b *ConnectionSpecApplyConfiguration) WithURL(value string) *ConnectionSpecApplyConfiguration {
b.URL = &value
return b
}
// WithGitHub sets the GitHub field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the GitHub field is set to the value of the last call.
func (b *ConnectionSpecApplyConfiguration) WithGitHub(value *GitHubConnectionConfigApplyConfiguration) *ConnectionSpecApplyConfiguration {
b.GitHub = value
return b
}
// WithBitbucket sets the Bitbucket field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Bitbucket field is set to the value of the last call.
func (b *ConnectionSpecApplyConfiguration) WithBitbucket(value *BitbucketConnectionConfigApplyConfiguration) *ConnectionSpecApplyConfiguration {
b.Bitbucket = value
return b
}
// WithGitlab sets the Gitlab field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Gitlab field is set to the value of the last call.
func (b *ConnectionSpecApplyConfiguration) WithGitlab(value *GitlabConnectionConfigApplyConfiguration) *ConnectionSpecApplyConfiguration {
b.Gitlab = value
return b
}
@@ -1,47 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
import (
provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// ConnectionStatusApplyConfiguration represents a declarative configuration of the ConnectionStatus type for use
// with apply.
type ConnectionStatusApplyConfiguration struct {
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
State *provisioningv0alpha1.ConnectionState `json:"state,omitempty"`
Health *HealthStatusApplyConfiguration `json:"health,omitempty"`
}
// ConnectionStatusApplyConfiguration constructs a declarative configuration of the ConnectionStatus type for use with
// apply.
func ConnectionStatus() *ConnectionStatusApplyConfiguration {
return &ConnectionStatusApplyConfiguration{}
}
// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ObservedGeneration field is set to the value of the last call.
func (b *ConnectionStatusApplyConfiguration) WithObservedGeneration(value int64) *ConnectionStatusApplyConfiguration {
b.ObservedGeneration = &value
return b
}
// WithState sets the State field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the State field is set to the value of the last call.
func (b *ConnectionStatusApplyConfiguration) WithState(value provisioningv0alpha1.ConnectionState) *ConnectionStatusApplyConfiguration {
b.State = &value
return b
}
// WithHealth sets the Health field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Health field is set to the value of the last call.
func (b *ConnectionStatusApplyConfiguration) WithHealth(value *HealthStatusApplyConfiguration) *ConnectionStatusApplyConfiguration {
b.Health = value
return b
}
@@ -1,34 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
// GitHubConnectionConfigApplyConfiguration represents a declarative configuration of the GitHubConnectionConfig type for use
// with apply.
type GitHubConnectionConfigApplyConfiguration struct {
AppID *string `json:"appID,omitempty"`
InstallationID *string `json:"installationID,omitempty"`
}
// GitHubConnectionConfigApplyConfiguration constructs a declarative configuration of the GitHubConnectionConfig type for use with
// apply.
func GitHubConnectionConfig() *GitHubConnectionConfigApplyConfiguration {
return &GitHubConnectionConfigApplyConfiguration{}
}
// WithAppID sets the AppID field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the AppID field is set to the value of the last call.
func (b *GitHubConnectionConfigApplyConfiguration) WithAppID(value string) *GitHubConnectionConfigApplyConfiguration {
b.AppID = &value
return b
}
// WithInstallationID sets the InstallationID field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the InstallationID field is set to the value of the last call.
func (b *GitHubConnectionConfigApplyConfiguration) WithInstallationID(value string) *GitHubConnectionConfigApplyConfiguration {
b.InstallationID = &value
return b
}
@@ -1,25 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v0alpha1
// GitlabConnectionConfigApplyConfiguration represents a declarative configuration of the GitlabConnectionConfig type for use
// with apply.
type GitlabConnectionConfigApplyConfiguration struct {
ClientID *string `json:"clientID,omitempty"`
}
// GitlabConnectionConfigApplyConfiguration constructs a declarative configuration of the GitlabConnectionConfig type for use with
// apply.
func GitlabConnectionConfig() *GitlabConnectionConfigApplyConfiguration {
return &GitlabConnectionConfigApplyConfiguration{}
}
// WithClientID sets the ClientID field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the ClientID field is set to the value of the last call.
func (b *GitlabConnectionConfigApplyConfiguration) WithClientID(value string) *GitlabConnectionConfigApplyConfiguration {
b.ClientID = &value
return b
}
@@ -18,28 +18,14 @@ import (
func ForKind(kind schema.GroupVersionKind) interface{} {
switch kind {
// Group=provisioning.grafana.app, Version=v0alpha1
case v0alpha1.SchemeGroupVersion.WithKind("BitbucketConnectionConfig"):
return &provisioningv0alpha1.BitbucketConnectionConfigApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("BitbucketRepositoryConfig"):
return &provisioningv0alpha1.BitbucketRepositoryConfigApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("Connection"):
return &provisioningv0alpha1.ConnectionApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("ConnectionSecure"):
return &provisioningv0alpha1.ConnectionSecureApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("ConnectionSpec"):
return &provisioningv0alpha1.ConnectionSpecApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("ConnectionStatus"):
return &provisioningv0alpha1.ConnectionStatusApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("DeleteJobOptions"):
return &provisioningv0alpha1.DeleteJobOptionsApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("ExportJobOptions"):
return &provisioningv0alpha1.ExportJobOptionsApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("GitHubConnectionConfig"):
return &provisioningv0alpha1.GitHubConnectionConfigApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("GitHubRepositoryConfig"):
return &provisioningv0alpha1.GitHubRepositoryConfigApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("GitlabConnectionConfig"):
return &provisioningv0alpha1.GitlabConnectionConfigApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("GitLabRepositoryConfig"):
return &provisioningv0alpha1.GitLabRepositoryConfigApplyConfiguration{}
case v0alpha1.SchemeGroupVersion.WithKind("GitRepositoryConfig"):
@@ -1,60 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by client-gen. DO NOT EDIT.
package v0alpha1
import (
context "context"
provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
applyconfigurationprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1"
scheme "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
gentype "k8s.io/client-go/gentype"
)
// ConnectionsGetter has a method to return a ConnectionInterface.
// A group's client should implement this interface.
type ConnectionsGetter interface {
Connections(namespace string) ConnectionInterface
}
// ConnectionInterface has methods to work with Connection resources.
type ConnectionInterface interface {
Create(ctx context.Context, connection *provisioningv0alpha1.Connection, opts v1.CreateOptions) (*provisioningv0alpha1.Connection, error)
Update(ctx context.Context, connection *provisioningv0alpha1.Connection, opts v1.UpdateOptions) (*provisioningv0alpha1.Connection, error)
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
UpdateStatus(ctx context.Context, connection *provisioningv0alpha1.Connection, opts v1.UpdateOptions) (*provisioningv0alpha1.Connection, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*provisioningv0alpha1.Connection, error)
List(ctx context.Context, opts v1.ListOptions) (*provisioningv0alpha1.ConnectionList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *provisioningv0alpha1.Connection, err error)
Apply(ctx context.Context, connection *applyconfigurationprovisioningv0alpha1.ConnectionApplyConfiguration, opts v1.ApplyOptions) (result *provisioningv0alpha1.Connection, err error)
// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
ApplyStatus(ctx context.Context, connection *applyconfigurationprovisioningv0alpha1.ConnectionApplyConfiguration, opts v1.ApplyOptions) (result *provisioningv0alpha1.Connection, err error)
ConnectionExpansion
}
// connections implements ConnectionInterface
type connections struct {
*gentype.ClientWithListAndApply[*provisioningv0alpha1.Connection, *provisioningv0alpha1.ConnectionList, *applyconfigurationprovisioningv0alpha1.ConnectionApplyConfiguration]
}
// newConnections returns a Connections
func newConnections(c *ProvisioningV0alpha1Client, namespace string) *connections {
return &connections{
gentype.NewClientWithListAndApply[*provisioningv0alpha1.Connection, *provisioningv0alpha1.ConnectionList, *applyconfigurationprovisioningv0alpha1.ConnectionApplyConfiguration](
"connections",
c.RESTClient(),
scheme.ParameterCodec,
namespace,
func() *provisioningv0alpha1.Connection { return &provisioningv0alpha1.Connection{} },
func() *provisioningv0alpha1.ConnectionList { return &provisioningv0alpha1.ConnectionList{} },
),
}
}
@@ -1,37 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1"
typedprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
gentype "k8s.io/client-go/gentype"
)
// fakeConnections implements ConnectionInterface
type fakeConnections struct {
*gentype.FakeClientWithListAndApply[*v0alpha1.Connection, *v0alpha1.ConnectionList, *provisioningv0alpha1.ConnectionApplyConfiguration]
Fake *FakeProvisioningV0alpha1
}
func newFakeConnections(fake *FakeProvisioningV0alpha1, namespace string) typedprovisioningv0alpha1.ConnectionInterface {
return &fakeConnections{
gentype.NewFakeClientWithListAndApply[*v0alpha1.Connection, *v0alpha1.ConnectionList, *provisioningv0alpha1.ConnectionApplyConfiguration](
fake.Fake,
namespace,
v0alpha1.SchemeGroupVersion.WithResource("connections"),
v0alpha1.SchemeGroupVersion.WithKind("Connection"),
func() *v0alpha1.Connection { return &v0alpha1.Connection{} },
func() *v0alpha1.ConnectionList { return &v0alpha1.ConnectionList{} },
func(dst, src *v0alpha1.ConnectionList) { dst.ListMeta = src.ListMeta },
func(list *v0alpha1.ConnectionList) []*v0alpha1.Connection { return gentype.ToPointerSlice(list.Items) },
func(list *v0alpha1.ConnectionList, items []*v0alpha1.Connection) {
list.Items = gentype.FromPointerSlice(items)
},
),
fake,
}
}
@@ -14,10 +14,6 @@ type FakeProvisioningV0alpha1 struct {
*testing.Fake
}
func (c *FakeProvisioningV0alpha1) Connections(namespace string) v0alpha1.ConnectionInterface {
return newFakeConnections(c, namespace)
}
func (c *FakeProvisioningV0alpha1) HistoricJobs(namespace string) v0alpha1.HistoricJobInterface {
return newFakeHistoricJobs(c, namespace)
}
@@ -4,8 +4,6 @@
package v0alpha1
type ConnectionExpansion interface{}
type HistoricJobExpansion interface{}
type JobExpansion interface{}
@@ -14,7 +14,6 @@ import (
type ProvisioningV0alpha1Interface interface {
RESTClient() rest.Interface
ConnectionsGetter
HistoricJobsGetter
JobsGetter
RepositoriesGetter
@@ -25,10 +24,6 @@ type ProvisioningV0alpha1Client struct {
restClient rest.Interface
}
func (c *ProvisioningV0alpha1Client) Connections(namespace string) ConnectionInterface {
return newConnections(c, namespace)
}
func (c *ProvisioningV0alpha1Client) HistoricJobs(namespace string) HistoricJobInterface {
return newHistoricJobs(c, namespace)
}
@@ -39,8 +39,6 @@ func (f *genericInformer) Lister() cache.GenericLister {
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
switch resource {
// Group=provisioning.grafana.app, Version=v0alpha1
case v0alpha1.SchemeGroupVersion.WithResource("connections"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().Connections().Informer()}, nil
case v0alpha1.SchemeGroupVersion.WithResource("historicjobs"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().HistoricJobs().Informer()}, nil
case v0alpha1.SchemeGroupVersion.WithResource("jobs"):
@@ -1,88 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by informer-gen. DO NOT EDIT.
package v0alpha1
import (
context "context"
time "time"
apisprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
versioned "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned"
internalinterfaces "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces"
provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// ConnectionInformer provides access to a shared informer and lister for
// Connections.
type ConnectionInformer interface {
Informer() cache.SharedIndexInformer
Lister() provisioningv0alpha1.ConnectionLister
}
type connectionInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewConnectionInformer constructs a new informer for Connection type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewConnectionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredConnectionInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredConnectionInformer constructs a new informer for Connection type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredConnectionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ProvisioningV0alpha1().Connections(namespace).List(context.Background(), options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ProvisioningV0alpha1().Connections(namespace).Watch(context.Background(), options)
},
ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ProvisioningV0alpha1().Connections(namespace).List(ctx, options)
},
WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ProvisioningV0alpha1().Connections(namespace).Watch(ctx, options)
},
},
&apisprovisioningv0alpha1.Connection{},
resyncPeriod,
indexers,
)
}
func (f *connectionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredConnectionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *connectionInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&apisprovisioningv0alpha1.Connection{}, f.defaultInformer)
}
func (f *connectionInformer) Lister() provisioningv0alpha1.ConnectionLister {
return provisioningv0alpha1.NewConnectionLister(f.Informer().GetIndexer())
}
@@ -10,8 +10,6 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// Connections returns a ConnectionInformer.
Connections() ConnectionInformer
// HistoricJobs returns a HistoricJobInformer.
HistoricJobs() HistoricJobInformer
// Jobs returns a JobInformer.
@@ -31,11 +29,6 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// Connections returns a ConnectionInformer.
func (v *version) Connections() ConnectionInformer {
return &connectionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// HistoricJobs returns a HistoricJobInformer.
func (v *version) HistoricJobs() HistoricJobInformer {
return &historicJobInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
@@ -1,56 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Code generated by lister-gen. DO NOT EDIT.
package v0alpha1
import (
provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
labels "k8s.io/apimachinery/pkg/labels"
listers "k8s.io/client-go/listers"
cache "k8s.io/client-go/tools/cache"
)
// ConnectionLister helps list Connections.
// All objects returned here must be treated as read-only.
type ConnectionLister interface {
// List lists all Connections in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*provisioningv0alpha1.Connection, err error)
// Connections returns an object that can list and get Connections.
Connections(namespace string) ConnectionNamespaceLister
ConnectionListerExpansion
}
// connectionLister implements the ConnectionLister interface.
type connectionLister struct {
listers.ResourceIndexer[*provisioningv0alpha1.Connection]
}
// NewConnectionLister returns a new ConnectionLister.
func NewConnectionLister(indexer cache.Indexer) ConnectionLister {
return &connectionLister{listers.New[*provisioningv0alpha1.Connection](indexer, provisioningv0alpha1.Resource("connection"))}
}
// Connections returns an object that can list and get Connections.
func (s *connectionLister) Connections(namespace string) ConnectionNamespaceLister {
return connectionNamespaceLister{listers.NewNamespaced[*provisioningv0alpha1.Connection](s.ResourceIndexer, namespace)}
}
// ConnectionNamespaceLister helps list and get Connections.
// All objects returned here must be treated as read-only.
type ConnectionNamespaceLister interface {
// List lists all Connections in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*provisioningv0alpha1.Connection, err error)
// Get retrieves the Connection from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*provisioningv0alpha1.Connection, error)
ConnectionNamespaceListerExpansion
}
// connectionNamespaceLister implements the ConnectionNamespaceLister
// interface.
type connectionNamespaceLister struct {
listers.ResourceIndexer[*provisioningv0alpha1.Connection]
}
@@ -4,14 +4,6 @@
package v0alpha1
// ConnectionListerExpansion allows custom methods to be added to
// ConnectionLister.
type ConnectionListerExpansion interface{}
// ConnectionNamespaceListerExpansion allows custom methods to be added to
// ConnectionNamespaceLister.
type ConnectionNamespaceListerExpansion interface{}
// HistoricJobListerExpansion allows custom methods to be added to
// HistoricJobLister.
type HistoricJobListerExpansion interface{}
-2
View File
@@ -125,7 +125,6 @@ navigationTree:
url: /d/_5rDmaQiz
scope: shoe-org
subScope: shoes
preLoadSubScopeChildren: true
children:
- name: shoes-overview
title: Overview
@@ -142,7 +141,6 @@ navigationTree:
url: /d/edediimbjhdz4b
scope: shoes
subScope: frontend
preLoadSubScopeChildren: true
children:
- name: frontend-api
title: API Metrics
+6 -10
View File
@@ -83,7 +83,6 @@ type NavigationConfig struct {
Title string `yaml:"title"` // Display title
Groups []string `yaml:"groups"` // Optional groups for categorization
DisableSubScopeSelection bool `yaml:"disableSubScopeSelection"` // Makes the subscope not selectable
PreLoadSubScopeChildren bool `yaml:"preLoadSubScopeChildren"` // Preload children of subScope without updating UI
}
// NavigationTreeNode represents a node in the navigation tree structure
@@ -95,7 +94,6 @@ type NavigationTreeNode struct {
SubScope string `yaml:"subScope,omitempty"`
Groups []string `yaml:"groups,omitempty"`
DisableSubScopeSelection bool `yaml:"disableSubScopeSelection,omitempty"`
PreLoadSubScopeChildren bool `yaml:"preLoadSubScopeChildren,omitempty"` // Preload children of subScope without updating UI
Children []NavigationTreeNode `yaml:"children,omitempty"`
}
@@ -320,7 +318,6 @@ func (c *Client) createScopeNavigation(name string, nav NavigationConfig) error
URL: nav.URL,
Scope: prefixedScope,
DisableSubScopeSelection: nav.DisableSubScopeSelection,
PreLoadSubScopeChildren: nav.PreLoadSubScopeChildren,
}
if nav.SubScope != "" {
@@ -356,14 +353,14 @@ func (c *Client) createScopeNavigation(name string, nav NavigationConfig) error
return err
}
// Get the created resource to retrieve its resourceVersion for status update
createdNav, err := c.getScopeNavigation(prefixedName)
if err != nil {
return fmt.Errorf("failed to get created navigation: %w", err)
}
// Update status in a second request (status is a subresource)
if nav.Title != "" || len(nav.Groups) > 0 {
// Get the created resource to retrieve its resourceVersion and existing spec
createdNav, err := c.getScopeNavigation(prefixedName)
if err != nil {
return fmt.Errorf("failed to get created navigation: %w", err)
}
statusResource := v0alpha1.ScopeNavigation{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
@@ -414,7 +411,6 @@ func treeToNavigations(node NavigationTreeNode, parentPath []string, dashboardCo
Scope: node.Scope,
Title: node.Title,
DisableSubScopeSelection: node.DisableSubScopeSelection,
PreLoadSubScopeChildren: node.PreLoadSubScopeChildren,
}
if node.SubScope != "" {
nav.SubScope = node.SubScope
+6 -1
View File
@@ -1817,7 +1817,7 @@
},
"public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx": {
"react-hooks/rules-of-hooks": {
"count": 5
"count": 4
}
},
"public/app/features/dashboard-scene/inspect/HelpWizard/HelpWizard.tsx": {
@@ -2910,6 +2910,11 @@
"count": 1
}
},
"public/app/features/plugins/admin/components/PluginDetailsPage.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/features/plugins/admin/helpers.ts": {
"no-restricted-syntax": {
"count": 2
+2
View File
@@ -67,6 +67,8 @@ module.exports = {
// near-membrane-dom won't work in a nodejs environment.
'@locker/near-membrane-dom': '<rootDir>/public/test/mocks/nearMembraneDom.ts',
'^@grafana/schema/dist/esm/(.*)$': '<rootDir>/packages/grafana-schema/src/$1',
'^@grafana/schema/dashboard/v0$': '<rootDir>/packages/grafana-schema/src/schema/dashboard/v0/index',
'^@grafana/schema/dashboard/v2beta1$': '<rootDir>/packages/grafana-schema/src/schema/dashboard/v2beta1/index',
// prevent systemjs amd extra from breaking tests.
'systemjs/dist/extras/amd': '<rootDir>/public/test/mocks/systemjsAMDExtra.ts',
'@bsull/augurs': '<rootDir>/public/test/mocks/augurs.ts',
@@ -1,5 +1,5 @@
import { api } from './baseAPI';
export const addTagTypes = ['API Discovery', 'Connection', 'Job', 'Repository', 'Provisioning'] as const;
export const addTagTypes = ['API Discovery', 'Job', 'Repository', 'Provisioning'] as const;
const injectedRtkApi = api
.enhanceEndpoints({
addTagTypes,
@@ -10,156 +10,6 @@ const injectedRtkApi = api
query: () => ({ url: `/` }),
providesTags: ['API Discovery'],
}),
listConnection: build.query<ListConnectionApiResponse, ListConnectionApiArg>({
query: (queryArg) => ({
url: `/connections`,
params: {
pretty: queryArg.pretty,
allowWatchBookmarks: queryArg.allowWatchBookmarks,
continue: queryArg['continue'],
fieldSelector: queryArg.fieldSelector,
labelSelector: queryArg.labelSelector,
limit: queryArg.limit,
resourceVersion: queryArg.resourceVersion,
resourceVersionMatch: queryArg.resourceVersionMatch,
sendInitialEvents: queryArg.sendInitialEvents,
timeoutSeconds: queryArg.timeoutSeconds,
watch: queryArg.watch,
},
}),
providesTags: ['Connection'],
}),
createConnection: build.mutation<CreateConnectionApiResponse, CreateConnectionApiArg>({
query: (queryArg) => ({
url: `/connections`,
method: 'POST',
body: queryArg.connection,
params: {
pretty: queryArg.pretty,
dryRun: queryArg.dryRun,
fieldManager: queryArg.fieldManager,
fieldValidation: queryArg.fieldValidation,
},
}),
invalidatesTags: ['Connection'],
}),
deletecollectionConnection: build.mutation<
DeletecollectionConnectionApiResponse,
DeletecollectionConnectionApiArg
>({
query: (queryArg) => ({
url: `/connections`,
method: 'DELETE',
params: {
pretty: queryArg.pretty,
continue: queryArg['continue'],
dryRun: queryArg.dryRun,
fieldSelector: queryArg.fieldSelector,
gracePeriodSeconds: queryArg.gracePeriodSeconds,
ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential,
labelSelector: queryArg.labelSelector,
limit: queryArg.limit,
orphanDependents: queryArg.orphanDependents,
propagationPolicy: queryArg.propagationPolicy,
resourceVersion: queryArg.resourceVersion,
resourceVersionMatch: queryArg.resourceVersionMatch,
sendInitialEvents: queryArg.sendInitialEvents,
timeoutSeconds: queryArg.timeoutSeconds,
},
}),
invalidatesTags: ['Connection'],
}),
getConnection: build.query<GetConnectionApiResponse, GetConnectionApiArg>({
query: (queryArg) => ({
url: `/connections/${queryArg.name}`,
params: {
pretty: queryArg.pretty,
},
}),
providesTags: ['Connection'],
}),
replaceConnection: build.mutation<ReplaceConnectionApiResponse, ReplaceConnectionApiArg>({
query: (queryArg) => ({
url: `/connections/${queryArg.name}`,
method: 'PUT',
body: queryArg.connection,
params: {
pretty: queryArg.pretty,
dryRun: queryArg.dryRun,
fieldManager: queryArg.fieldManager,
fieldValidation: queryArg.fieldValidation,
},
}),
invalidatesTags: ['Connection'],
}),
deleteConnection: build.mutation<DeleteConnectionApiResponse, DeleteConnectionApiArg>({
query: (queryArg) => ({
url: `/connections/${queryArg.name}`,
method: 'DELETE',
params: {
pretty: queryArg.pretty,
dryRun: queryArg.dryRun,
gracePeriodSeconds: queryArg.gracePeriodSeconds,
ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential,
orphanDependents: queryArg.orphanDependents,
propagationPolicy: queryArg.propagationPolicy,
},
}),
invalidatesTags: ['Connection'],
}),
updateConnection: build.mutation<UpdateConnectionApiResponse, UpdateConnectionApiArg>({
query: (queryArg) => ({
url: `/connections/${queryArg.name}`,
method: 'PATCH',
body: queryArg.patch,
params: {
pretty: queryArg.pretty,
dryRun: queryArg.dryRun,
fieldManager: queryArg.fieldManager,
fieldValidation: queryArg.fieldValidation,
force: queryArg.force,
},
}),
invalidatesTags: ['Connection'],
}),
getConnectionStatus: build.query<GetConnectionStatusApiResponse, GetConnectionStatusApiArg>({
query: (queryArg) => ({
url: `/connections/${queryArg.name}/status`,
params: {
pretty: queryArg.pretty,
},
}),
providesTags: ['Connection'],
}),
replaceConnectionStatus: build.mutation<ReplaceConnectionStatusApiResponse, ReplaceConnectionStatusApiArg>({
query: (queryArg) => ({
url: `/connections/${queryArg.name}/status`,
method: 'PUT',
body: queryArg.connection,
params: {
pretty: queryArg.pretty,
dryRun: queryArg.dryRun,
fieldManager: queryArg.fieldManager,
fieldValidation: queryArg.fieldValidation,
},
}),
invalidatesTags: ['Connection'],
}),
updateConnectionStatus: build.mutation<UpdateConnectionStatusApiResponse, UpdateConnectionStatusApiArg>({
query: (queryArg) => ({
url: `/connections/${queryArg.name}/status`,
method: 'PATCH',
body: queryArg.patch,
params: {
pretty: queryArg.pretty,
dryRun: queryArg.dryRun,
fieldManager: queryArg.fieldManager,
fieldValidation: queryArg.fieldValidation,
force: queryArg.force,
},
}),
invalidatesTags: ['Connection'],
}),
listJob: build.query<ListJobApiResponse, ListJobApiArg>({
query: (queryArg) => ({
url: `/jobs`,
@@ -561,208 +411,6 @@ const injectedRtkApi = api
export { injectedRtkApi as generatedAPI };
export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList;
export type GetApiResourcesApiArg = void;
export type ListConnectionApiResponse = /** status 200 OK */ ConnectionList;
export type ListConnectionApiArg = {
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
/** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */
allowWatchBookmarks?: boolean;
/** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key".
This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */
continue?: string;
/** A selector to restrict the list of returned objects by their fields. Defaults to everything. */
fieldSelector?: string;
/** A selector to restrict the list of returned objects by their labels. Defaults to everything. */
labelSelector?: string;
/** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.
The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */
limit?: number;
/** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
Defaults to unset */
resourceVersion?: string;
/** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
Defaults to unset */
resourceVersionMatch?: string;
/** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.
When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan
is interpreted as "data at least as new as the provided `resourceVersion`"
and the bookmark event is send when the state is synced
to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
If `resourceVersion` is unset, this is interpreted as "consistent read" and the
bookmark event is send when the state is synced at least to the moment
when request started being processed.
- `resourceVersionMatch` set to any other value or unset
Invalid error is returned.
Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */
sendInitialEvents?: boolean;
/** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */
timeoutSeconds?: number;
/** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */
watch?: boolean;
};
export type CreateConnectionApiResponse = /** status 200 OK */
| Connection
| /** status 201 Created */ Connection
| /** status 202 Accepted */ Connection;
export type CreateConnectionApiArg = {
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
/** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
dryRun?: string;
/** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */
fieldManager?: string;
/** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */
fieldValidation?: string;
connection: Connection;
};
export type DeletecollectionConnectionApiResponse = /** status 200 OK */ Status;
export type DeletecollectionConnectionApiArg = {
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
/** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key".
This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */
continue?: string;
/** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
dryRun?: string;
/** A selector to restrict the list of returned objects by their fields. Defaults to everything. */
fieldSelector?: string;
/** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */
gracePeriodSeconds?: number;
/** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */
ignoreStoreReadErrorWithClusterBreakingPotential?: boolean;
/** A selector to restrict the list of returned objects by their labels. Defaults to everything. */
labelSelector?: string;
/** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.
The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */
limit?: number;
/** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */
orphanDependents?: boolean;
/** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */
propagationPolicy?: string;
/** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
Defaults to unset */
resourceVersion?: string;
/** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
Defaults to unset */
resourceVersionMatch?: string;
/** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.
When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan
is interpreted as "data at least as new as the provided `resourceVersion`"
and the bookmark event is send when the state is synced
to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
If `resourceVersion` is unset, this is interpreted as "consistent read" and the
bookmark event is send when the state is synced at least to the moment
when request started being processed.
- `resourceVersionMatch` set to any other value or unset
Invalid error is returned.
Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */
sendInitialEvents?: boolean;
/** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */
timeoutSeconds?: number;
};
export type GetConnectionApiResponse = /** status 200 OK */ Connection;
export type GetConnectionApiArg = {
/** name of the Connection */
name: string;
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
};
export type ReplaceConnectionApiResponse = /** status 200 OK */ Connection | /** status 201 Created */ Connection;
export type ReplaceConnectionApiArg = {
/** name of the Connection */
name: string;
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
/** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
dryRun?: string;
/** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */
fieldManager?: string;
/** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */
fieldValidation?: string;
connection: Connection;
};
export type DeleteConnectionApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status;
export type DeleteConnectionApiArg = {
/** name of the Connection */
name: string;
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
/** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
dryRun?: string;
/** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */
gracePeriodSeconds?: number;
/** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */
ignoreStoreReadErrorWithClusterBreakingPotential?: boolean;
/** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */
orphanDependents?: boolean;
/** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */
propagationPolicy?: string;
};
export type UpdateConnectionApiResponse = /** status 200 OK */ Connection | /** status 201 Created */ Connection;
export type UpdateConnectionApiArg = {
/** name of the Connection */
name: string;
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
/** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
dryRun?: string;
/** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */
fieldManager?: string;
/** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */
fieldValidation?: string;
/** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */
force?: boolean;
patch: Patch;
};
export type GetConnectionStatusApiResponse = /** status 200 OK */ Connection;
export type GetConnectionStatusApiArg = {
/** name of the Connection */
name: string;
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
};
export type ReplaceConnectionStatusApiResponse = /** status 200 OK */ Connection | /** status 201 Created */ Connection;
export type ReplaceConnectionStatusApiArg = {
/** name of the Connection */
name: string;
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
/** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
dryRun?: string;
/** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */
fieldManager?: string;
/** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */
fieldValidation?: string;
connection: Connection;
};
export type UpdateConnectionStatusApiResponse = /** status 200 OK */ Connection | /** status 201 Created */ Connection;
export type UpdateConnectionStatusApiArg = {
/** name of the Connection */
name: string;
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
/** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
dryRun?: string;
/** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */
fieldManager?: string;
/** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */
fieldValidation?: string;
/** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */
force?: boolean;
patch: Patch;
};
export type ListJobApiResponse = /** status 200 OK */ JobList;
export type ListJobApiArg = {
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
@@ -1405,169 +1053,6 @@ export type ObjectMeta = {
Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */
uid?: string;
};
export type InlineSecureValue =
| {
/** Create a secure value -- this is only used for POST/PUT */
create?: string;
/** Name in the secret service (reference) */
name: string;
/** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */
remove?: boolean;
}
| {
/** Create a secure value -- this is only used for POST/PUT */
create: string;
/** Name in the secret service (reference) */
name?: string;
/** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */
remove?: boolean;
}
| {
/** Create a secure value -- this is only used for POST/PUT */
create?: string;
/** Name in the secret service (reference) */
name?: string;
/** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */
remove: boolean;
};
export type ConnectionSecure = {
/** ClientSecret is the reference to the secret used for other providers authentication, and Github on-behalf-of authentication. This value is stored securely and cannot be read back */
clientSecret?: InlineSecureValue;
/** PrivateKey is the reference to the private key used for GitHub App authentication. This value is stored securely and cannot be read back */
privateKey?: InlineSecureValue;
/** Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back */
webhook?: InlineSecureValue;
};
export type BitbucketConnectionConfig = {
/** App client ID */
clientID: string;
};
export type GitHubConnectionConfig = {
/** GitHub App ID */
appID: string;
/** GitHub App installation ID */
installationID: string;
};
export type GitlabConnectionConfig = {
/** App client ID */
clientID: string;
};
export type ConnectionSpec = {
/** Bitbucket connection configuration Only applicable when provider is "bitbucket" */
bitbucket?: BitbucketConnectionConfig;
/** GitHub connection configuration Only applicable when provider is "github" */
github?: GitHubConnectionConfig;
/** Gitlab connection configuration Only applicable when provider is "gitlab" */
gitlab?: GitlabConnectionConfig;
/** The connection provider type
Possible enum values:
- `"bitbucket"`
- `"github"`
- `"gitlab"` */
type: 'bitbucket' | 'github' | 'gitlab';
/** The connection URL */
url?: string;
};
export type HealthStatus = {
/** When the health was checked last time */
checked?: number;
/** The type of the error
Possible enum values:
- `"health"`
- `"hook"` */
error?: 'health' | 'hook';
/** When not healthy, requests will not be executed */
healthy: boolean;
/** Summary messages (can be shown to users) Will only be populated when not healthy */
message?: string[];
};
export type ConnectionStatus = {
/** The connection health status */
health: HealthStatus;
/** The generation of the spec last time reconciliation ran */
observedGeneration: number;
/** Connection state
Possible enum values:
- `"connected"`
- `"disconnected"` */
state: 'connected' | 'disconnected';
};
export type Connection = {
/** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */
apiVersion?: string;
/** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
kind?: string;
metadata?: ObjectMeta;
secure?: ConnectionSecure;
spec?: ConnectionSpec;
status?: ConnectionStatus;
};
export type ListMeta = {
/** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */
continue?: string;
/** remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact. */
remainingItemCount?: number;
/** String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */
resourceVersion?: string;
/** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */
selfLink?: string;
};
export type ConnectionList = {
/** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */
apiVersion?: string;
items: Connection[];
/** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
kind?: string;
metadata?: ListMeta;
};
export type StatusCause = {
/** The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.
Examples:
"name" - the field "name" on the current resource
"items[0].name" - the field "name" on the first array entry in "items" */
field?: string;
/** A human-readable description of the cause of the error. This field may be presented as-is to a reader. */
message?: string;
/** A machine-readable description of the cause of the error. If this value is empty there is no information available. */
reason?: string;
};
export type StatusDetails = {
/** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */
causes?: StatusCause[];
/** The group attribute of the resource associated with the status StatusReason. */
group?: string;
/** The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
kind?: string;
/** The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). */
name?: string;
/** If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. */
retryAfterSeconds?: number;
/** UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */
uid?: string;
};
export type Status = {
/** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */
apiVersion?: string;
/** Suggested HTTP return code for this status, 0 if not set. */
code?: number;
/** Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type. */
details?: StatusDetails;
/** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
kind?: string;
/** A human-readable description of the status of this operation. */
message?: string;
/** Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
metadata?: ListMeta;
/** A machine-readable description of why this operation is in the "Failure" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. */
reason?: string;
/** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */
status?: string;
};
export type Patch = object;
export type ResourceRef = {
/** Group is the group of the resource, such as "dashboard.grafana.app". */
group?: string;
@@ -1705,6 +1190,16 @@ export type Job = {
spec?: JobSpec;
status?: JobStatus;
};
export type ListMeta = {
/** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */
continue?: string;
/** remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact. */
remainingItemCount?: number;
/** String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */
resourceVersion?: string;
/** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */
selfLink?: string;
};
export type JobList = {
/** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */
apiVersion?: string;
@@ -1713,6 +1208,76 @@ export type JobList = {
kind?: string;
metadata?: ListMeta;
};
export type StatusCause = {
/** The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.
Examples:
"name" - the field "name" on the current resource
"items[0].name" - the field "name" on the first array entry in "items" */
field?: string;
/** A human-readable description of the cause of the error. This field may be presented as-is to a reader. */
message?: string;
/** A machine-readable description of the cause of the error. If this value is empty there is no information available. */
reason?: string;
};
export type StatusDetails = {
/** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */
causes?: StatusCause[];
/** The group attribute of the resource associated with the status StatusReason. */
group?: string;
/** The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
kind?: string;
/** The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). */
name?: string;
/** If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. */
retryAfterSeconds?: number;
/** UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */
uid?: string;
};
export type Status = {
/** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */
apiVersion?: string;
/** Suggested HTTP return code for this status, 0 if not set. */
code?: number;
/** Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type. */
details?: StatusDetails;
/** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
kind?: string;
/** A human-readable description of the status of this operation. */
message?: string;
/** Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
metadata?: ListMeta;
/** A machine-readable description of why this operation is in the "Failure" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. */
reason?: string;
/** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */
status?: string;
};
export type Patch = object;
export type InlineSecureValue =
| {
/** Create a secure value -- this is only used for POST/PUT */
create?: string;
/** Name in the secret service (reference) */
name: string;
/** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */
remove?: boolean;
}
| {
/** Create a secure value -- this is only used for POST/PUT */
create: string;
/** Name in the secret service (reference) */
name?: string;
/** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */
remove?: boolean;
}
| {
/** Create a secure value -- this is only used for POST/PUT */
create?: string;
/** Name in the secret service (reference) */
name?: string;
/** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */
remove: boolean;
};
export type SecureValues = {
/** Token used to connect the configured repository */
token?: InlineSecureValue;
@@ -1809,6 +1374,20 @@ export type RepositorySpec = {
/** UI driven Workflow that allow changes to the contends of the repository. The order is relevant for defining the precedence of the workflows. When empty, the repository does not support any edits (eg, readonly) */
workflows: ('branch' | 'write')[];
};
export type HealthStatus = {
/** When the health was checked last time */
checked?: number;
/** The type of the error
Possible enum values:
- `"health"`
- `"hook"` */
error?: 'health' | 'hook';
/** When not healthy, requests will not be executed */
healthy: boolean;
/** Summary messages (can be shown to users) Will only be populated when not healthy */
message?: string[];
};
export type ResourceCount = {
count: number;
group: string;
@@ -2069,19 +1648,6 @@ export type ResourceStats = {
export const {
useGetApiResourcesQuery,
useLazyGetApiResourcesQuery,
useListConnectionQuery,
useLazyListConnectionQuery,
useCreateConnectionMutation,
useDeletecollectionConnectionMutation,
useGetConnectionQuery,
useLazyGetConnectionQuery,
useReplaceConnectionMutation,
useDeleteConnectionMutation,
useUpdateConnectionMutation,
useGetConnectionStatusQuery,
useLazyGetConnectionStatusQuery,
useReplaceConnectionStatusMutation,
useUpdateConnectionStatusMutation,
useListJobQuery,
useLazyListJobQuery,
useCreateJobMutation,
-10
View File
@@ -422,11 +422,6 @@ export interface FeatureToggles {
*/
alertingQueryOptimization?: boolean;
/**
* Returns compact API responses for alert rules list, reducing payload size by omitting the query, notification settings, and metadata
* @default false
*/
alertingCompactRulesResponse?: boolean;
/**
* Distributes alert rule evaluations more evenly over time, including spreading out rules within the same group. Disables sequential evaluation if enabled.
*/
jitterAlertRulesWithinGroups?: boolean;
@@ -1190,11 +1185,6 @@ export interface FeatureToggles {
*/
onlyStoreActionSets?: boolean;
/**
* Show insights for plugins in the plugin details page
* @default false
*/
pluginInsights?: boolean;
/**
* Enables a new panel time settings drawer
*/
panelTimeSettings?: boolean;
@@ -35,10 +35,6 @@ export interface TraceToMetricsData extends DataSourceJsonData {
interface Props extends DataSourcePluginOptionsEditorProps<TraceToMetricsData> {}
export function TraceToMetricsSettings({ options, onOptionsChange }: Props) {
const supportedDataSourceTypes = [
'prometheus',
'victoriametrics-metrics-datasource', // external
];
const styles = useStyles2(getStyles);
return (
@@ -51,10 +47,10 @@ export function TraceToMetricsSettings({ options, onOptionsChange }: Props) {
>
<DataSourcePicker
inputId="trace-to-metrics-data-source-picker"
pluginId="prometheus"
current={options.jsonData.tracesToMetrics?.datasourceUid}
noDefault={true}
width={40}
filter={(ds) => supportedDataSourceTypes.includes(ds.type)}
onChange={(ds: DataSourceInstanceSettings) =>
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToMetrics', {
...options.jsonData.tracesToMetrics,
+40
View File
@@ -48,4 +48,44 @@ export default [
},
treeshake: false,
},
// Build sub-path exports for dashboard v0
{
input: {
'schema/dashboard/v0': fileURLToPath(new URL('src/schema/dashboard/v0/index.ts', import.meta.url)),
},
plugins: [noderesolve, esbuild],
output: [
{
format: 'esm',
dir: path.dirname(pkg.publishConfig.module),
entryFileNames: '[name].mjs',
},
{
format: 'cjs',
dir: path.dirname(pkg.publishConfig.main),
entryFileNames: '[name].cjs',
},
],
treeshake: false,
},
// Build sub-path exports for dashboard v2beta1
{
input: {
'schema/dashboard/v2beta1': fileURLToPath(new URL('src/schema/dashboard/v2beta1/index.ts', import.meta.url)),
},
plugins: [noderesolve, esbuild],
output: [
{
format: 'esm',
dir: path.dirname(pkg.publishConfig.module),
entryFileNames: '[name].mjs',
},
{
format: 'cjs',
dir: path.dirname(pkg.publishConfig.main),
entryFileNames: '[name].cjs',
},
],
treeshake: false,
},
];
@@ -0,0 +1,3 @@
// Re-export raw dashboard types for v0 (legacy) dashboard schema
// This allows imports like: import { AnnotationPanelFilter, DashboardLink } from '@grafana/schema/dashboard/v0'
export * from '../../../raw/dashboard/x/dashboard_types.gen';
@@ -0,0 +1,4 @@
// Re-export all types and values from types.spec.gen and types.status.gen for sub-path imports
// This allows imports like: import { Spec, Status } from '@grafana/schema/dashboard/v2beta1'
export * from './types.spec.gen';
export * from './types.status.gen';
@@ -97,13 +97,7 @@ export const Dropdown = React.memo(({ children, overlay, placement, offset, root
see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-static-element-interactions.md#case-the-event-handler-is-only-being-used-to-capture-bubbled-events
*/}
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */}
<div
ref={refs.setFloating}
style={floatingStyles}
onClick={onOverlayClicked}
onKeyDown={handleKeys}
{...getFloatingProps()}
>
<div ref={refs.setFloating} style={floatingStyles} onClick={onOverlayClicked} onKeyDown={handleKeys}>
<CSSTransition
nodeRef={transitionRef}
appear={true}
+1 -1
View File
@@ -638,7 +638,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
m := hs.web
m.Use(requestmeta.SetupRequestMetadata())
m.Use(middleware.RequestTracing(hs.tracer, middleware.ShouldTraceWithExceptions))
m.Use(middleware.RequestTracing(hs.tracer, middleware.SkipTracingPaths))
m.Use(middleware.RequestMetrics(hs.Features, hs.Cfg, hs.promRegister))
m.UseMiddleware(hs.LoggerMiddleware.Middleware())
-1
View File
@@ -162,7 +162,6 @@ var serviceIdentityTokenPermissions = []string{
"collections.grafana.app:*", // user stars
"plugins.grafana.app:*",
"historian.alerting.grafana.app:*",
"advisor.grafana.app:*",
// Secrets Manager uses a custom verb for secret decryption, and its authorizer does not allow wildcard permissions.
"secret.grafana.app/securevalues:decrypt",
+2 -9
View File
@@ -19,18 +19,11 @@ func (NoopBackend) Shutdown() {}
func (NoopBackend) String() string { return "" }
// NoopPolicyRuleProvider is a no-op implementation of PolicyRuleProvider
type NoopPolicyRuleProvider struct{}
func ProvideNoopPolicyRuleProvider() PolicyRuleProvider { return &NoopPolicyRuleProvider{} }
func (NoopPolicyRuleProvider) PolicyRuleProvider(PolicyRuleEvaluators) audit.PolicyRuleEvaluator {
return NoopPolicyRuleEvaluator{}
}
// NoopPolicyRuleEvaluator is a no-op implementation of audit.PolicyRuleEvaluator
type NoopPolicyRuleEvaluator struct{}
func ProvideNoopPolicyRuleEvaluator() audit.PolicyRuleEvaluator { return &NoopPolicyRuleEvaluator{} }
func (NoopPolicyRuleEvaluator) EvaluatePolicyRule(authorizer.Attributes) audit.RequestAuditConfig {
return audit.RequestAuditConfig{Level: auditinternal.LevelNone}
}
-59
View File
@@ -1,59 +0,0 @@
package auditing
import (
"slices"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"k8s.io/apimachinery/pkg/runtime/schema"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
// PolicyRuleEvaluators is a map of API group+version to audit.PolicyRuleEvaluator
type PolicyRuleEvaluators = map[schema.GroupVersion]audit.PolicyRuleEvaluator
type PolicyRuleProvider interface {
PolicyRuleProvider(evaluators PolicyRuleEvaluators) audit.PolicyRuleEvaluator
}
// PolicyRuleEvaluator alias for easier imports.
type PolicyRuleEvaluator = audit.PolicyRuleEvaluator
// DefaultGrafanaPolicyRuleEvaluator provides a sane default configuration for audit logging for API group+versions.
type defaultGrafanaPolicyRuleEvaluator struct{}
var _ PolicyRuleEvaluator = &defaultGrafanaPolicyRuleEvaluator{}
func NewDefaultGrafanaPolicyRuleEvaluator() audit.PolicyRuleEvaluator {
return defaultGrafanaPolicyRuleEvaluator{}
}
func (defaultGrafanaPolicyRuleEvaluator) EvaluatePolicyRule(attrs authorizer.Attributes) audit.RequestAuditConfig {
// Skip non-resource and watch requests otherwise it is too noisy.
if !attrs.IsResourceRequest() || attrs.GetVerb() == utils.VerbWatch {
return audit.RequestAuditConfig{
Level: auditinternal.LevelNone,
}
}
// Skip auditing if the user is part of the privileged group.
// The loopback client uses this group, so requests initiated in `/api/` would be duplicated.
if u := attrs.GetUser(); u != nil && slices.Contains(u.GetGroups(), user.SystemPrivilegedGroup) {
return audit.RequestAuditConfig{
Level: auditinternal.LevelNone,
}
}
return audit.RequestAuditConfig{
Level: auditinternal.LevelMetadata,
OmitStages: []auditinternal.Stage{
// Only log on StageResponseComplete
auditinternal.StageRequestReceived,
auditinternal.StageResponseStarted,
auditinternal.StagePanic,
},
OmitManagedFields: false, // Setting it to true causes extra copying/unmarshalling.
}
}
-73
View File
@@ -1,73 +0,0 @@
package auditing_test
import (
"testing"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apiserver/auditing"
"github.com/stretchr/testify/require"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
func TestDefaultGrafanaPolicyRuleEvaluator(t *testing.T) {
t.Parallel()
evaluator := auditing.NewDefaultGrafanaPolicyRuleEvaluator()
require.NotNil(t, evaluator)
t.Run("returns audit level none for non-resource requests", func(t *testing.T) {
t.Parallel()
attrs := authorizer.AttributesRecord{
ResourceRequest: false,
}
config := evaluator.EvaluatePolicyRule(attrs)
require.Equal(t, auditinternal.LevelNone, config.Level)
})
t.Run("returns audit level none for watch requests", func(t *testing.T) {
t.Parallel()
attrs := authorizer.AttributesRecord{
ResourceRequest: true,
Verb: utils.VerbWatch,
}
config := evaluator.EvaluatePolicyRule(attrs)
require.Equal(t, auditinternal.LevelNone, config.Level)
})
t.Run("returns audit level none for requests from privileged group", func(t *testing.T) {
t.Parallel()
attrs := authorizer.AttributesRecord{
ResourceRequest: true,
Verb: utils.VerbCreate,
User: &user.DefaultInfo{
Groups: []string{"test-group", user.SystemPrivilegedGroup},
},
}
config := evaluator.EvaluatePolicyRule(attrs)
require.Equal(t, auditinternal.LevelNone, config.Level)
})
t.Run("return audit level metadata for other resource requests", func(t *testing.T) {
t.Parallel()
attrs := authorizer.AttributesRecord{
ResourceRequest: true,
Verb: utils.VerbCreate,
User: &user.DefaultInfo{
Name: "test-user",
Groups: []string{"test-group"},
},
}
config := evaluator.EvaluatePolicyRule(attrs)
require.Equal(t, auditinternal.LevelMetadata, config.Level)
})
}
+6 -10
View File
@@ -73,20 +73,16 @@ func RouteOperationName(req *http.Request) (string, bool) {
return "", false
}
func ShouldTraceWithExceptions(req *http.Request) bool {
// Paths that don't need tracing spans applied to them because of the
// little value that would provide us
if strings.HasPrefix(req.URL.Path, "/public/") ||
// Paths that don't need tracing spans applied to them because of the
// little value that would provide us
func SkipTracingPaths(req *http.Request) bool {
return strings.HasPrefix(req.URL.Path, "/public/") ||
req.URL.Path == "/robots.txt" ||
req.URL.Path == "/favicon.ico" ||
req.URL.Path == "/api/health" {
return false
}
return true
req.URL.Path == "/api/health"
}
func ShouldTraceAllPaths(req *http.Request) bool {
func TraceAllPaths(req *http.Request) bool {
return true
}
@@ -39,10 +39,6 @@ func (m mockProvisioningV0alpha1Interface) Jobs(namespace string) client.JobInte
panic("not needed for testing")
}
func (m mockProvisioningV0alpha1Interface) Connections(namespace string) client.ConnectionInterface {
panic("not needed for testing")
}
func (m mockProvisioningV0alpha1Interface) Repositories(namespace string) client.RepositoryInterface {
if m.repositoriesFunc != nil {
return m.repositoriesFunc(namespace)
@@ -31,7 +31,6 @@ import (
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
connectionvalidation "github.com/grafana/grafana/apps/provisioning/pkg/connection"
appcontroller "github.com/grafana/grafana/apps/provisioning/pkg/controller"
clientset "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned"
client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
@@ -355,8 +354,6 @@ func (b *APIBuilder) authorizeResource(ctx context.Context, a authorizer.Attribu
return b.authorizeSettings(id)
case provisioning.JobResourceInfo.GetName(), provisioning.HistoricJobResourceInfo.GetName():
return b.authorizeJobs(id)
case provisioning.ConnectionResourceInfo.GetName():
return b.authorizeConnectionSubresource(a, id)
default:
return b.authorizeDefault(id)
}
@@ -440,28 +437,6 @@ func (b *APIBuilder) authorizeJobs(id identity.Requester) (authorizer.Decision,
return authorizer.DecisionDeny, "admin role is required", nil
}
// authorizeRepositorySubresource handles authorization for connections subresources.
func (b *APIBuilder) authorizeConnectionSubresource(a authorizer.Attributes, id identity.Requester) (authorizer.Decision, string, error) {
switch a.GetSubresource() {
case "":
// Doing something with the connection itself.
if id.GetOrgRole().Includes(identity.RoleAdmin) {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "admin role is required", nil
case "status":
if id.GetOrgRole().Includes(identity.RoleViewer) && a.GetVerb() == apiutils.VerbGet {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "users cannot update the status of a connection", nil
default:
if id.GetIsGrafanaAdmin() {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "unmapped subresource defaults to no access", nil
}
}
// authorizeDefault handles authorization for unmapped resources.
func (b *APIBuilder) authorizeDefault(id identity.Requester) (authorizer.Decision, string, error) {
// We haven't bothered with this kind yet.
@@ -545,19 +520,10 @@ func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI
storage[provisioning.HistoricJobResourceInfo.StoragePath()] = historicJobStore
}
connectionsStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, provisioning.ConnectionResourceInfo, opts.OptsGetter)
if err != nil {
return fmt.Errorf("failed to create connection storage: %w", err)
}
connectionStatusStorage := grafanaregistry.NewRegistryStatusStore(opts.Scheme, connectionsStore)
storage[provisioning.JobResourceInfo.StoragePath()] = jobStore
storage[provisioning.RepositoryResourceInfo.StoragePath()] = repositoryStorage
storage[provisioning.RepositoryResourceInfo.StoragePath("status")] = repositoryStatusStorage
storage[provisioning.ConnectionResourceInfo.StoragePath()] = connectionsStore
storage[provisioning.ConnectionResourceInfo.StoragePath("status")] = connectionStatusStorage
// TODO: Add some logic so that the connectors can registered themselves and we don't have logic all over the place
storage[provisioning.RepositoryResourceInfo.StoragePath("test")] = NewTestConnector(b, repository.NewRepositoryTesterWithExistingChecker(repository.NewSimpleRepositoryTester(b.validator), b.VerifyAgainstExistingRepositories))
storage[provisioning.RepositoryResourceInfo.StoragePath("files")] = NewFilesConnector(b, b.parsers, b.clients, b.access)
@@ -600,11 +566,6 @@ func (b *APIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admis
if ok {
return nil
}
// TODO: complete this as part of https://github.com/grafana/git-ui-sync-project/issues/700
c, ok := obj.(*provisioning.Connection)
if ok {
return connectionvalidation.MutateConnection(c)
}
r, ok := obj.(*provisioning.Repository)
if !ok {
@@ -654,11 +615,6 @@ func (b *APIBuilder) Validate(ctx context.Context, a admission.Attributes, o adm
return nil
}
connection, ok := obj.(*provisioning.Connection)
if ok {
return connectionvalidation.ValidateConnection(connection)
}
// Validate Jobs
job, ok := obj.(*provisioning.Job)
if ok {
+1 -1
View File
@@ -37,7 +37,7 @@ var WireSetExts = wire.NewSet(
// Auditing Options
auditing.ProvideNoopBackend,
auditing.ProvideNoopPolicyRuleProvider,
auditing.ProvideNoopPolicyRuleEvaluator,
)
var provisioningExtras = wire.NewSet(
-150
View File
@@ -1,150 +0,0 @@
package advisor
import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/org"
)
const (
// Check
ActionAdvisorCheckCreate = "advisor.checks:create" // CREATE.
ActionAdvisorCheckWrite = "advisor.checks:write" // UPDATE.
ActionAdvisorCheckRead = "advisor.checks:read" // GET + LIST.
ActionAdvisorCheckDelete = "advisor.checks:delete" // DELETE.
// CheckTypes
ActionAdvisorCheckTypesCreate = "advisor.checktypes:create" // CREATE.
ActionAdvisorCheckTypesWrite = "advisor.checktypes:write" // UPDATE.
ActionAdvisorCheckTypesRead = "advisor.checktypes:read" // GET + LIST.
ActionAdvisorCheckTypesDelete = "advisor.checktypes:delete" // DELETE.
// Register
ActionAdvisorRegisterCreate = "advisor.register:create" // CREATE (register check types).
)
var (
ScopeProviderAdvisorCheck = accesscontrol.NewScopeProvider("advisor.checks")
ScopeProviderAdvisorCheckTypes = accesscontrol.NewScopeProvider("advisor.checktypes")
ScopeProviderAdvisorRegister = accesscontrol.NewScopeProvider("advisor.register")
ScopeAllAdvisorCheck = ScopeProviderAdvisorCheck.GetResourceAllScope()
ScopeAllAdvisorCheckTypes = ScopeProviderAdvisorCheckTypes.GetResourceAllScope()
ScopeAllAdvisorRegister = ScopeProviderAdvisorRegister.GetResourceAllScope()
)
func registerAccessControlRoles(service accesscontrol.Service) error {
// Check
checkReader := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:advisor.checks:reader",
DisplayName: "Advisor Check Reader",
Description: "Read and list advisor checks.",
Group: "Advisor",
Permissions: []accesscontrol.Permission{
{
Action: ActionAdvisorCheckRead,
Scope: ScopeAllAdvisorCheck,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
checkWriter := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:advisor.checks:writer",
DisplayName: "Advisor Check Writer",
Description: "Create, update and delete advisor checks.",
Group: "Advisor",
Permissions: []accesscontrol.Permission{
{
Action: ActionAdvisorCheckCreate,
Scope: ScopeAllAdvisorCheck,
},
{
Action: ActionAdvisorCheckRead,
Scope: ScopeAllAdvisorCheck,
},
{
Action: ActionAdvisorCheckWrite,
Scope: ScopeAllAdvisorCheck,
},
{
Action: ActionAdvisorCheckDelete,
Scope: ScopeAllAdvisorCheck,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
// CheckTypes
checkTypesReader := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:advisor.checktypes:reader",
DisplayName: "Advisor Check Types Reader",
Description: "Read and list advisor check types.",
Group: "Advisor",
Permissions: []accesscontrol.Permission{
{
Action: ActionAdvisorCheckTypesRead,
Scope: ScopeAllAdvisorCheckTypes,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
checkTypesWriter := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:advisor.checktypes:writer",
DisplayName: "Advisor Check Types Writer",
Description: "Create, update and delete advisor check types.",
Group: "Advisor",
Permissions: []accesscontrol.Permission{
{
Action: ActionAdvisorCheckTypesCreate,
Scope: ScopeAllAdvisorCheckTypes,
},
{
Action: ActionAdvisorCheckTypesRead,
Scope: ScopeAllAdvisorCheckTypes,
},
{
Action: ActionAdvisorCheckTypesWrite,
Scope: ScopeAllAdvisorCheckTypes,
},
{
Action: ActionAdvisorCheckTypesDelete,
Scope: ScopeAllAdvisorCheckTypes,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
// Register
registerWriter := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: "fixed:advisor.register:writer",
DisplayName: "Advisor Register Writer",
Description: "Register default advisor check types.",
Group: "Advisor",
Permissions: []accesscontrol.Permission{
{
Action: ActionAdvisorRegisterCreate,
Scope: ScopeAllAdvisorRegister,
},
},
},
Grants: []string{string(org.RoleAdmin)},
}
return service.DeclareFixedRoles(
checkReader,
checkWriter,
checkTypesReader,
checkTypesWriter,
registerWriter,
)
}
+27 -16
View File
@@ -1,17 +1,17 @@
package advisor
import (
"fmt"
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana-app-sdk/app"
appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver"
"github.com/grafana/grafana-app-sdk/simple"
advisorapi "github.com/grafana/grafana/apps/advisor/pkg/apis"
advisorapp "github.com/grafana/grafana/apps/advisor/pkg/app"
"github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/appinstaller"
grafanaauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/setting"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/rest"
)
var (
@@ -20,26 +20,37 @@ var (
)
type AdvisorAppInstaller struct {
*advisorapp.AdvisorAppInstaller
appsdkapiserver.AppInstaller
}
// GetAuthorizer returns the authorizer for the plugins app.
func (a *AdvisorAppInstaller) GetAuthorizer() authorizer.Authorizer {
return advisorapp.GetAuthorizer()
}
func ProvideAppInstaller(
accessControlService accesscontrol.Service,
accessClient authlib.AccessClient,
checkRegistry checkregistry.CheckService,
cfg *setting.Cfg,
orgService org.Service,
) (*AdvisorAppInstaller, error) {
if err := registerAccessControlRoles(accessControlService); err != nil {
return nil, fmt.Errorf("registering access control roles: %w", err)
provider := simple.NewAppProvider(advisorapi.LocalManifest(), nil, advisorapp.New)
pluginConfig := cfg.PluginSettings["grafana-advisor-app"]
specificConfig := checkregistry.AdvisorAppConfig{
CheckRegistry: checkRegistry,
PluginConfig: pluginConfig,
StackID: cfg.StackID,
OrgService: orgService,
}
authorizer := grafanaauthorizer.NewResourceAuthorizer(accessClient)
i, err := advisorapp.ProvideAppInstaller(authorizer, checkRegistry, cfg, orgService)
appCfg := app.Config{
KubeConfig: rest.Config{},
ManifestData: *advisorapi.LocalManifest().ManifestData,
SpecificConfig: specificConfig,
}
installer := &AdvisorAppInstaller{}
i, err := appsdkapiserver.NewDefaultAppInstaller(provider, appCfg, advisorapi.NewGoTypeAssociator())
if err != nil {
return nil, err
}
return &AdvisorAppInstaller{
AdvisorAppInstaller: i,
}, nil
installer.AppInstaller = i
return installer, nil
}
+6 -6
View File
@@ -819,7 +819,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
return nil, err
}
checkregistryService := checkregistry.ProvideService(service15, pluginstoreService, plugincontextProvider, middlewareHandler, plugincheckerService, repoManager, preinstallImpl, managedpluginsNoop, noop, ssosettingsimplService, cfg, pluginerrsStore)
advisorAppInstaller, err := advisor2.ProvideAppInstaller(acimplService, accessClient, checkregistryService, cfg, orgService)
advisorAppInstaller, err := advisor2.ProvideAppInstaller(checkregistryService, cfg, orgService)
if err != nil {
return nil, err
}
@@ -834,8 +834,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller)
builderMetrics := builder.ProvideBuilderMetrics(registerer)
backend := auditing.ProvideNoopBackend()
policyRuleProvider := auditing.ProvideNoopPolicyRuleProvider()
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleProvider)
policyRuleEvaluator := auditing.ProvideNoopPolicyRuleEvaluator()
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleEvaluator)
if err != nil {
return nil, err
}
@@ -1480,7 +1480,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
return nil, err
}
checkregistryService := checkregistry.ProvideService(service15, pluginstoreService, plugincontextProvider, middlewareHandler, plugincheckerService, repoManager, preinstallImpl, managedpluginsNoop, noop, ssosettingsimplService, cfg, pluginerrsStore)
advisorAppInstaller, err := advisor2.ProvideAppInstaller(acimplService, accessClient, checkregistryService, cfg, orgService)
advisorAppInstaller, err := advisor2.ProvideAppInstaller(checkregistryService, cfg, orgService)
if err != nil {
return nil, err
}
@@ -1495,8 +1495,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller)
builderMetrics := builder.ProvideBuilderMetrics(registerer)
backend := auditing.ProvideNoopBackend()
policyRuleProvider := auditing.ProvideNoopPolicyRuleProvider()
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleProvider)
policyRuleEvaluator := auditing.ProvideNoopPolicyRuleEvaluator()
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleEvaluator)
if err != nil {
return nil, err
}
@@ -86,9 +86,6 @@ func newPermissionRegistry() *permissionRegistry {
"plugins": "plugins:id:",
"plugins.plugins": "plugins.plugins:uid:",
"plugins.metas": "plugins.metas:uid:",
"advisor.checks": "advisor.checks:uid:",
"advisor.checktypes": "advisor.checktypes:uid:",
"advisor.register": "advisor.register:uid:",
"provisioners": "provisioners:",
"reports": "reports:id:",
"permissions": "permissions:type:",
-8
View File
@@ -9,7 +9,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
genericapiserver "k8s.io/apiserver/pkg/server"
@@ -60,13 +59,6 @@ type APIGroupAuthorizer interface {
GetAuthorizer() authorizer.Authorizer
}
// APIGroupAuditor allows different API groups to opt-in and provide their own auditing policy evaluator function.
// Auditing is only enabled if this is implemented. If no customization is needed, you can use the default evaluator,
// `pkg/apiserver/auditing.NewDefaultGrafanaPolicyRuleEvaluator()`.
type APIGroupAuditor interface {
GetPolicyRuleEvaluator() audit.PolicyRuleEvaluator
}
type APIGroupMutation interface {
// Mutate allows the builder to make changes to the object before it is persisted.
// Context is used only for timeout/deadline/cancellation and tracing information.
-27
View File
@@ -29,7 +29,6 @@ import (
"k8s.io/klog/v2"
"k8s.io/kube-openapi/pkg/common"
"github.com/grafana/grafana/pkg/apiserver/auditing"
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
@@ -498,32 +497,6 @@ func AddPostStartHooks(
return nil
}
func EvaluatorPolicyRuleFromBuilders(builders []APIGroupBuilder) auditing.PolicyRuleEvaluators {
policyRuleEvaluators := make(auditing.PolicyRuleEvaluators, 0)
for _, b := range builders {
auditor, ok := b.(APIGroupAuditor)
if !ok {
continue
}
policyRuleEvaluator := auditor.GetPolicyRuleEvaluator()
if policyRuleEvaluator == nil {
continue
}
for _, gv := range GetGroupVersions(b) {
if gv.Empty() {
continue
}
policyRuleEvaluators[gv] = policyRuleEvaluator
}
}
return policyRuleEvaluators
}
func allowRegisteringResourceByInfo(allowedResources []string, name string) bool {
// trim any subresources from the name
name = strings.Split(name, "/")[0]
+5 -6
View File
@@ -28,7 +28,6 @@ import (
dataplaneaggregator "github.com/grafana/grafana/pkg/aggregator/apiserver"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apiserver/auditing"
grafanaresponsewriter "github.com/grafana/grafana/pkg/apiserver/endpoints/responsewriter"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/db"
@@ -116,8 +115,8 @@ type service struct {
builderMetrics *builder.BuilderMetrics
dualWriterMetrics *grafanarest.DualWriterMetrics
auditBackend audit.Backend
auditPolicyRuleProvider auditing.PolicyRuleProvider
auditBackend audit.Backend
auditPolicyRuleEvaluator audit.PolicyRuleEvaluator
}
func ProvideService(
@@ -143,7 +142,7 @@ func ProvideService(
appInstallers []appsdkapiserver.AppInstaller,
builderMetrics *builder.BuilderMetrics,
auditBackend audit.Backend,
auditPolicyRuleProvider auditing.PolicyRuleProvider,
auditPolicyRuleEvaluator audit.PolicyRuleEvaluator,
) (*service, error) {
scheme := builder.ProvideScheme()
codecs := builder.ProvideCodecFactory(scheme)
@@ -175,7 +174,7 @@ func ProvideService(
builderMetrics: builderMetrics,
dualWriterMetrics: grafanarest.NewDualWriterMetrics(reg),
auditBackend: auditBackend,
auditPolicyRuleProvider: auditPolicyRuleProvider,
auditPolicyRuleEvaluator: auditPolicyRuleEvaluator,
}
// This will be used when running as a dskit service
s.NamedService = services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer)
@@ -366,7 +365,7 @@ func (s *service) start(ctx context.Context) error {
// Auditing Options
serverConfig.AuditBackend = s.auditBackend
serverConfig.AuditPolicyRuleEvaluator = s.auditPolicyRuleProvider.PolicyRuleProvider(builder.EvaluatorPolicyRuleFromBuilders(s.builders))
serverConfig.AuditPolicyRuleEvaluator = s.auditPolicyRuleEvaluator
// Add OpenAPI specs for each group+version (existing builders)
err = builder.SetupConfig(
-5
View File
@@ -301,11 +301,6 @@ func NewMapperRegistry() MapperRegistry {
"plugins": newResourceTranslation("plugins.plugins", "uid", false, nil),
"metas": newResourceTranslation("plugins.metas", "uid", false, nil),
},
"advisor.grafana.app": {
"checks": newResourceTranslation("advisor.checks", "uid", false, nil),
"checktypes": newResourceTranslation("advisor.checktypes", "uid", false, nil),
"register": newResourceTranslation("advisor.register", "uid", false, nil),
},
})
return mapper
-15
View File
@@ -686,13 +686,6 @@ var (
Owner: grafanaAlertingSquad,
Expression: "false",
},
{
Name: "alertingCompactRulesResponse",
Description: "Returns compact API responses for alert rules list, reducing payload size by omitting the query, notification settings, and metadata",
Stage: FeatureStageExperimental,
Owner: grafanaAlertingSquad,
Expression: "false",
},
{
Name: "jitterAlertRulesWithinGroups",
Description: "Distributes alert rule evaluations more evenly over time, including spreading out rules within the same group. Disables sequential evaluation if enabled.",
@@ -1960,14 +1953,6 @@ var (
Owner: identityAccessTeam,
Expression: "true",
},
{
Name: "pluginInsights",
Description: "Show insights for plugins in the plugin details page",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaPluginsPlatformSquad,
Expression: "false",
},
{
Name: "panelTimeSettings",
Description: "Enables a new panel time settings drawer",
-2
View File
@@ -95,7 +95,6 @@ tableSharedCrosshair,experimental,@grafana/dataviz-squad,false,false,true
kubernetesFeatureToggles,experimental,@grafana/grafana-operator-experience-squad,false,false,true
cloudRBACRoles,preview,@grafana/identity-access-team,false,true,false
alertingQueryOptimization,GA,@grafana/alerting-squad,false,false,false
alertingCompactRulesResponse,experimental,@grafana/alerting-squad,false,false,false
jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,false,true,false
secretsManagementAppPlatform,experimental,@grafana/grafana-operator-experience-squad,false,false,false
secretsManagementAppPlatformUI,experimental,@grafana/grafana-operator-experience-squad,false,false,false
@@ -266,7 +265,6 @@ jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false
pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false
newPanelPadding,preview,@grafana/dashboards-squad,false,false,true
onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false
pluginInsights,experimental,@grafana/plugins-platform-backend,false,false,true
panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false
elasticsearchRawDSLQuery,experimental,@grafana/partner-datasources,false,false,false
kubernetesAnnotations,experimental,@grafana/grafana-backend-services-squad,false,false,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
95 kubernetesFeatureToggles experimental @grafana/grafana-operator-experience-squad false false true
96 cloudRBACRoles preview @grafana/identity-access-team false true false
97 alertingQueryOptimization GA @grafana/alerting-squad false false false
alertingCompactRulesResponse experimental @grafana/alerting-squad false false false
98 jitterAlertRulesWithinGroups preview @grafana/alerting-squad false true false
99 secretsManagementAppPlatform experimental @grafana/grafana-operator-experience-squad false false false
100 secretsManagementAppPlatformUI experimental @grafana/grafana-operator-experience-squad false false false
265 pluginStoreServiceLoading experimental @grafana/plugins-platform-backend false false false
266 newPanelPadding preview @grafana/dashboards-squad false false true
267 onlyStoreActionSets GA @grafana/identity-access-team false false false
pluginInsights experimental @grafana/plugins-platform-backend false false true
268 panelTimeSettings experimental @grafana/dashboards-squad false false false
269 elasticsearchRawDSLQuery experimental @grafana/partner-datasources false false false
270 kubernetesAnnotations experimental @grafana/grafana-backend-services-squad false false false
-4
View File
@@ -279,10 +279,6 @@ const (
// Optimizes eligible queries in order to reduce load on datasources
FlagAlertingQueryOptimization = "alertingQueryOptimization"
// FlagAlertingCompactRulesResponse
// Returns compact API responses for alert rules list, reducing payload size by omitting the query, notification settings, and metadata
FlagAlertingCompactRulesResponse = "alertingCompactRulesResponse"
// FlagJitterAlertRulesWithinGroups
// Distributes alert rule evaluations more evenly over time, including spreading out rules within the same group. Disables sequential evaluation if enabled.
FlagJitterAlertRulesWithinGroups = "jitterAlertRulesWithinGroups"
-30
View File
@@ -195,22 +195,6 @@
"codeowner": "@grafana/alerting-squad"
}
},
{
"metadata": {
"name": "alertingCompactRulesResponse",
"resourceVersion": "1765802340688",
"creationTimestamp": "2025-12-15T12:24:06Z",
"annotations": {
"grafana.app/updatedTimestamp": "2025-12-15 12:39:00.688866597 +0000 UTC"
}
},
"spec": {
"description": "Returns compact API responses for alert rules list, reducing payload size by omitting the query, notification settings, and metadata",
"stage": "experimental",
"codeowner": "@grafana/alerting-squad",
"expression": "false"
}
},
{
"metadata": {
"name": "alertingDisableSendAlertsExternal",
@@ -2683,20 +2667,6 @@
"expression": "false"
}
},
{
"metadata": {
"name": "pluginInsights",
"resourceVersion": "1761300628147",
"creationTimestamp": "2025-10-24T10:10:28Z"
},
"spec": {
"description": "Show insights for plugins in the plugin details page",
"stage": "experimental",
"codeowner": "@grafana/plugins-platform-backend",
"frontend": true,
"expression": "false"
}
},
{
"metadata": {
"name": "pluginInstallAPISync",
+1 -1
View File
@@ -134,7 +134,7 @@ func (s *frontendService) addMiddlewares(m *web.Mux) {
loggermiddleware := loggermw.Provide(s.cfg, s.features)
m.Use(requestmeta.SetupRequestMetadata())
m.Use(middleware.RequestTracing(s.tracer, middleware.ShouldTraceAllPaths))
m.Use(middleware.RequestTracing(s.tracer, middleware.TraceAllPaths))
m.Use(middleware.RequestMetrics(s.features, s.cfg, s.promRegister))
m.UseMiddleware(s.contextMiddleware())
-3
View File
@@ -424,9 +424,6 @@ func (l *LibraryElementService) toLibraryElementError(err error, message string)
if errors.Is(err, model.ErrLibraryElementUIDTooLong) {
return response.Error(http.StatusBadRequest, model.ErrLibraryElementUIDTooLong.Error(), err)
}
if errors.Is(err, model.ErrLibraryElementProvisionedFolder) {
return response.Error(http.StatusConflict, model.ErrLibraryElementProvisionedFolder.Error(), err)
}
if err != nil && strings.Contains(err.Error(), "insufficient permissions") {
return response.Error(http.StatusForbidden, err.Error(), err)
}
-30
View File
@@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/metrics"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -126,20 +125,6 @@ func (l *LibraryElementService) CreateElement(c context.Context, signedInUser id
}
}
if cmd.FolderUID != nil {
f, err := l.folderService.Get(c, &folder.GetFolderQuery{
OrgID: signedInUser.GetOrgID(),
UID: cmd.FolderUID,
SignedInUser: signedInUser,
})
if err != nil {
return model.LibraryElementDTO{}, err
}
if f.ManagedBy == utils.ManagerKindRepo {
return model.LibraryElementDTO{}, model.ErrLibraryElementProvisionedFolder
}
}
updatedModel := cmd.Model
var err error
if cmd.Kind == int64(model.PanelElement) {
@@ -616,21 +601,6 @@ func (l *LibraryElementService) PatchLibraryElement(c context.Context, signedInU
if err := l.requireSupportedElementKind(cmd.Kind); err != nil {
return model.LibraryElementDTO{}, err
}
if cmd.FolderUID != nil {
f, err := l.folderService.Get(c, &folder.GetFolderQuery{
OrgID: signedInUser.GetOrgID(),
UID: cmd.FolderUID,
SignedInUser: signedInUser,
})
if err != nil {
return model.LibraryElementDTO{}, err
}
if f.ManagedBy == utils.ManagerKindRepo {
return model.LibraryElementDTO{}, model.ErrLibraryElementProvisionedFolder
}
}
err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error {
elementInDB, err := l.GetLibraryElement(c, signedInUser, session, uid)
if err != nil {
@@ -161,8 +161,6 @@ var (
ErrLibraryElementInvalidUID = errors.New("uid contains illegal characters")
// errLibraryElementUIDTooLong is an error for when the uid of a library element is invalid
ErrLibraryElementUIDTooLong = errors.New("uid too long, max 40 characters")
// ErrLibraryElementProvisionedFolder indicates that a library element cannot be created on a provisioned folder.
ErrLibraryElementProvisionedFolder = errors.New("resource type not supported in repository-managed folders")
)
// Commands
@@ -2886,189 +2886,6 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
}
})
t.Run("with rule_matcher filter", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule1"), gen.WithLabels(map[string]string{"team": "alerting", "severity": "critical"}), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule2"), gen.WithLabels(map[string]string{"team": "Alerting", "severity": "warning"}), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule3"), gen.WithLabels(map[string]string{"team": "platform", "severity": "critical"}), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule4"), gen.WithLabels(map[string]string{"env": "production"}), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule_special"), gen.WithLabels(map[string]string{"key": `value"with"quotes`}), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule_empty"), gen.WithLabels(map[string]string{"empty": ""}), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule_nonempty"), gen.WithLabels(map[string]string{"empty": "nonempty"}), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule_multiline"), gen.WithLabels(map[string]string{"description": "line1\nline2\\end\"quote"}), gen.WithNoNotificationSettings())
testCases := []struct {
name string
matchers []string
expectedUIDs []string
}{
{
name: "equality matcher filters by team=alerting",
matchers: []string{`{"name":"team","value":"alerting","isRegex":false,"isEqual":true}`},
expectedUIDs: []string{"rule1"},
},
{
name: "inequality matcher filters severity!=warning",
matchers: []string{`{"name":"severity","value":"warning","isRegex":false,"isEqual":false}`},
expectedUIDs: []string{"rule1", "rule3", "rule4", "rule_special", "rule_empty", "rule_nonempty", "rule_multiline"},
},
{
name: "regex matcher filters team=~plat.*",
matchers: []string{`{"name":"team","value":"plat.*","isRegex":true,"isEqual":true}`},
expectedUIDs: []string{"rule3"},
},
{
name: "not-regex matcher filters severity!~warn.*",
matchers: []string{`{"name":"severity","value":"warn.*","isRegex":true,"isEqual":false}`},
expectedUIDs: []string{"rule1", "rule3", "rule4", "rule_special", "rule_empty", "rule_nonempty", "rule_multiline"},
},
{
name: "multiple matchers are ANDed",
matchers: []string{
`{"name":"team","value":"alerting","isRegex":false,"isEqual":true}`,
`{"name":"severity","value":"critical","isRegex":false,"isEqual":true}`,
},
expectedUIDs: []string{"rule1"},
},
{
name: "matcher with non-existent label returns no rules",
matchers: []string{`{"name":"nonexistent","value":"value","isRegex":false,"isEqual":true}`},
expectedUIDs: []string{},
},
{
name: "equality matcher is case-sensitive",
matchers: []string{`{"name":"team","value":"Alerting","isRegex":false,"isEqual":true}`},
expectedUIDs: []string{"rule2"},
},
{
name: "quotes in label value are handled correctly",
matchers: []string{`{"name":"key","value":"value\"with\"quotes","isRegex":false,"isEqual":true}`},
expectedUIDs: []string{"rule_special"},
},
{
name: "no matchers returns all rules",
matchers: []string{},
expectedUIDs: []string{"rule1", "rule2", "rule3", "rule4", "rule_special", "rule_empty", "rule_nonempty", "rule_multiline"},
},
{
name: "empty string value matches correctly",
matchers: []string{`{"name":"empty","value":"","isRegex":false,"isEqual":true}`},
expectedUIDs: []string{"rule1", "rule2", "rule3", "rule4", "rule_special", "rule_empty", "rule_multiline"},
},
{
name: "special characters in label value are handled correctly",
matchers: []string{`{"name":"description","value":"line1\nline2\\end\"quote","isRegex":false,"isEqual":true}`},
expectedUIDs: []string{"rule_multiline"},
},
{
name: "inequality matcher on non-existent label matches all rules",
matchers: []string{`{"name":"nonexistent","value":"value","isRegex":false,"isEqual":false}`},
expectedUIDs: []string{"rule1", "rule2", "rule3", "rule4", "rule_special", "rule_empty", "rule_nonempty", "rule_multiline"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
reqURL := "/api/v1/rules"
for i, matcher := range tc.matchers {
if i == 0 {
reqURL += "?rule_matcher=" + url.QueryEscape(matcher)
} else {
reqURL += "&rule_matcher=" + url.QueryEscape(matcher)
}
}
req, err := http.NewRequest("GET", reqURL, nil)
require.NoError(t, err)
ctx := &contextmodel.ReqContext{
Context: &web.Context{Req: req},
SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions},
}
resp := api.RouteGetRuleStatuses(ctx)
require.Equal(t, http.StatusOK, resp.Status())
var res apimodels.RuleResponse
require.NoError(t, json.Unmarshal(resp.Body(), &res))
require.Equal(t, "success", res.Status)
actualUIDs := []string{}
for _, group := range res.Data.RuleGroups {
for _, rule := range group.Rules {
actualUIDs = append(actualUIDs, rule.UID)
}
}
require.ElementsMatch(t, tc.expectedUIDs, actualUIDs)
})
}
})
t.Run("pagination with rule_matcher in-memory filtering", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
// Create 3 groups with 2 rules each:
// Group 1 & 2: team=backend (won't match filter)
// Group 3: team=frontend (will match filter)
// This tests that pagination continues fetching when early pages are filtered out
group1Key := ngmodels.AlertRuleGroupKey{OrgID: orgID, NamespaceUID: "namespace1", RuleGroup: "group1"}
group2Key := ngmodels.AlertRuleGroupKey{OrgID: orgID, NamespaceUID: "namespace2", RuleGroup: "group2"}
group3Key := ngmodels.AlertRuleGroupKey{OrgID: orgID, NamespaceUID: "namespace3", RuleGroup: "group3"}
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule1"), gen.WithLabels(map[string]string{"team": "security"}), gen.WithGroupKey(group1Key), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule2"), gen.WithLabels(map[string]string{"team": "security"}), gen.WithGroupKey(group1Key), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule3"), gen.WithLabels(map[string]string{"team": "security"}), gen.WithGroupKey(group2Key), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule4"), gen.WithLabels(map[string]string{"team": "security"}), gen.WithGroupKey(group2Key), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule5"), gen.WithLabels(map[string]string{"team": "alerting"}), gen.WithGroupKey(group3Key), gen.WithNoNotificationSettings())
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(),
gen.WithUID("rule6"), gen.WithLabels(map[string]string{"team": "alerting"}), gen.WithGroupKey(group3Key), gen.WithNoNotificationSettings())
// Request with regex rule_matcher filter for team=~"alerting" and group_limit=1 to force pagination
matcher := `{"name":"team","value":"alerting","isRegex":true,"isEqual":true}`
reqURL := "/api/v1/rules?rule_matcher=" + url.QueryEscape(matcher) + "&group_limit=1"
req, err := http.NewRequest("GET", reqURL, nil)
require.NoError(t, err)
ctx := &contextmodel.ReqContext{
Context: &web.Context{Req: req},
SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions},
}
resp := api.RouteGetRuleStatuses(ctx)
require.Equal(t, http.StatusOK, resp.Status())
var res apimodels.RuleResponse
require.NoError(t, json.Unmarshal(resp.Body(), &res))
require.Equal(t, "success", res.Status)
actualUIDs := []string{}
for _, group := range res.Data.RuleGroups {
for _, rule := range group.Rules {
actualUIDs = append(actualUIDs, rule.UID)
}
}
// Should return group3 rules (rule5, rule6), pagination should continue past filtered groups
require.ElementsMatch(t, []string{"rule5", "rule6"}, actualUIDs)
})
}
func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, PrometheusSrv) {
@@ -33,12 +33,6 @@ import (
"go.opentelemetry.io/otel/trace"
)
const (
queryIncludeInternalLabels = "includeInternalLabels"
queryRuleMatcher = "rule_matcher"
queryInstanceMatcher = "matcher"
)
type RuleStoreReader interface {
GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error)
ListAlertRulesStoreV2
@@ -68,20 +62,6 @@ type PrometheusSrv struct {
// Package-level OpenTelemetry tracer per Grafana instrumentation conventions.
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/ngalert/api/prometheus")
// badRequestError returns a Prometheus-compatible error response for bad request data.
func badRequestError(err error) apimodels.RuleResponse {
return apimodels.RuleResponse{
DiscoveryBase: apimodels.DiscoveryBase{
Status: "error",
Error: err.Error(),
ErrorType: apiv1.ErrBadData,
},
Data: apimodels.RuleDiscovery{
RuleGroups: []apimodels.RuleGroup{},
},
}
}
func NewPrometheusSrv(log log.Logger, manager state.AlertInstanceManager, status StatusReader, store RuleStoreReader, authz RuleGroupAccessControlService, provenanceStore ProvenanceStore) *PrometheusSrv {
return &PrometheusSrv{
log,
@@ -93,6 +73,8 @@ func NewPrometheusSrv(log log.Logger, manager state.AlertInstanceManager, status
}
}
const queryIncludeInternalLabels = "includeInternalLabels"
func getBoolWithDefault(vals url.Values, field string, d bool) bool {
f := vals.Get(field)
if f == "" {
@@ -206,15 +188,15 @@ func getPanelIDFromQuery(v url.Values) (int64, error) {
return 0, nil
}
func getMatchersFromQuery(v url.Values, paramName string) (labels.Matchers, error) {
func getMatchersFromQuery(v url.Values) (labels.Matchers, error) {
var matchers labels.Matchers
for _, s := range v[paramName] {
for _, s := range v["matcher"] {
var m labels.Matcher
if err := json.Unmarshal([]byte(s), &m); err != nil {
return nil, err
}
if len(m.Name) == 0 {
return nil, fmt.Errorf("bad %s: the name cannot be blank", paramName)
return nil, errors.New("bad matcher: the name cannot be blank")
}
matchers = append(matchers, &m)
}
@@ -472,7 +454,6 @@ type paginationContext struct {
stateFilterSet map[eval.State]struct{}
healthFilterSet map[string]struct{}
matchers labels.Matchers
ruleLabelMatchers labels.Matchers
labelOptions []ngmodels.LabelOption
limitAlertsPerRule int64
limitRulesPerGroup int64
@@ -495,9 +476,6 @@ func accumulateTotals(dest, source map[string]int64) {
// fetchAndFilterPage fetches one page from the store and applies filters
func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlertRulesStoreV2, span trace.Span, token string, remainingGroups, remainingRules int64) (pageResult, error) {
// Split matchers: only equality/inequality are supported by the store
storeMatchers := filterOutRegexMatchers(ctx.ruleLabelMatchers)
byGroupQuery := ngmodels.ListAlertRulesExtendedQuery{
ListAlertRulesQuery: ngmodels.ListAlertRulesQuery{
OrgID: ctx.opts.OrgID,
@@ -510,7 +488,6 @@ func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlert
DataSourceUIDs: ctx.dataSourceUIDs,
SearchTitle: ctx.title,
SearchRuleGroup: ctx.searchRuleGroup,
LabelMatchers: storeMatchers,
},
RuleType: ctx.ruleType,
Limit: remainingGroups,
@@ -557,8 +534,6 @@ func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlert
filterRulesByHealth(ruleGroup, ctx.healthFilterSet)
}
filterRulesByLabelMatchers(ruleGroup, ctx.ruleLabelMatchers)
if ctx.limitRulesPerGroup > -1 && int64(len(ruleGroup.Rules)) > ctx.limitRulesPerGroup {
ruleGroup.Rules = ruleGroup.Rules[0:ctx.limitRulesPerGroup]
}
@@ -571,17 +546,6 @@ func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlert
return result, nil
}
func filterOutRegexMatchers(matchers labels.Matchers) labels.Matchers {
var result labels.Matchers
for _, m := range matchers {
if m.Type == labels.MatchEqual || m.Type == labels.MatchNotEqual {
result = append(result, m)
}
}
return result
}
// paginateRuleGroups fetches pages until limits are satisfied applying filters at each step
func paginateRuleGroups(log log.Logger, store ListAlertRulesStoreV2, ctx *paginationContext, span trace.Span, maxGroups, maxRules int64, startToken string) ([]apimodels.RuleGroup, map[string]int64, string, error) {
allGroups := []apimodels.RuleGroup{}
@@ -680,30 +644,21 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt
attribute.Int64("limit_rules", limitRulesPerGroup),
attribute.Int64("limit_alerts", limitAlertsPerRule),
)
matchers, err := getMatchersFromQuery(opts.Query, queryInstanceMatcher)
matchers, err := getMatchersFromQuery(opts.Query)
if err != nil {
return badRequestError(err)
ruleResponse.Status = "error"
ruleResponse.Error = err.Error()
ruleResponse.ErrorType = apiv1.ErrBadData
return ruleResponse
}
span.SetAttributes(attribute.Int("matcher_count", len(matchers)))
ruleLabelMatchers, err := getMatchersFromQuery(opts.Query, queryRuleMatcher)
if err != nil {
return badRequestError(err)
}
regexCount := 0
for _, m := range ruleLabelMatchers {
if m.Type == labels.MatchRegexp || m.Type == labels.MatchNotRegexp {
regexCount++
}
}
span.SetAttributes(
attribute.Int("rule_matcher_count", len(ruleLabelMatchers)),
attribute.Int("rule_matcher_regex_count", regexCount),
)
stateFilterSet, err := GetStatesFromQuery(opts.Query)
if err != nil {
return badRequestError(err)
ruleResponse.Status = "error"
ruleResponse.Error = err.Error()
ruleResponse.ErrorType = apiv1.ErrBadData
return ruleResponse
}
span.SetAttributes(
attribute.Int("state_filter_count", len(stateFilterSet)),
@@ -712,7 +667,10 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt
healthFilterSet, err := GetHealthFromQuery(opts.Query)
if err != nil {
return badRequestError(err)
ruleResponse.Status = "error"
ruleResponse.Error = err.Error()
ruleResponse.ErrorType = apiv1.ErrBadData
return ruleResponse
}
span.SetAttributes(
attribute.Int("health_filter_count", len(healthFilterSet)),
@@ -850,7 +808,6 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt
stateFilterSet: stateFilterSet,
healthFilterSet: healthFilterSet,
matchers: matchers,
ruleLabelMatchers: ruleLabelMatchers,
labelOptions: labelOptions,
limitAlertsPerRule: limitAlertsPerRule,
limitRulesPerGroup: limitRulesPerGroup,
@@ -876,7 +833,6 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt
return ruleResponse
}
// nolint:gocyclo
func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts RuleGroupStatusesOptions, ruleStatusMutator RuleStatusMutator, alertStateMutator RuleAlertStateMutator, provenanceRecords map[string]ngmodels.Provenance) apimodels.RuleResponse {
ruleResponse := apimodels.RuleResponse{
DiscoveryBase: apimodels.DiscoveryBase{
@@ -890,30 +846,41 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
dashboardUID := opts.Query.Get("dashboard_uid")
panelID, err := getPanelIDFromQuery(opts.Query)
if err != nil {
return badRequestError(fmt.Errorf("invalid panel_id: %w", err))
ruleResponse.Status = "error"
ruleResponse.Error = fmt.Sprintf("invalid panel_id: %s", err.Error())
ruleResponse.ErrorType = apiv1.ErrBadData
return ruleResponse
}
if dashboardUID == "" && panelID != 0 {
return badRequestError(errors.New("panel_id must be set with dashboard_uid"))
ruleResponse.Status = "error"
ruleResponse.Error = "panel_id must be set with dashboard_uid"
ruleResponse.ErrorType = apiv1.ErrBadData
return ruleResponse
}
limitRulesPerGroup := getInt64WithDefault(opts.Query, "limit_rules", -1)
limitAlertsPerRule := getInt64WithDefault(opts.Query, "limit_alerts", -1)
matchers, err := getMatchersFromQuery(opts.Query, queryInstanceMatcher)
matchers, err := getMatchersFromQuery(opts.Query)
if err != nil {
return badRequestError(err)
}
ruleLabelMatchers, err := getMatchersFromQuery(opts.Query, queryRuleMatcher)
if err != nil {
return badRequestError(err)
ruleResponse.Status = "error"
ruleResponse.Error = err.Error()
ruleResponse.ErrorType = apiv1.ErrBadData
return ruleResponse
}
stateFilterSet, err := GetStatesFromQuery(opts.Query)
if err != nil {
return badRequestError(err)
ruleResponse.Status = "error"
ruleResponse.Error = err.Error()
ruleResponse.ErrorType = apiv1.ErrBadData
return ruleResponse
}
healthFilterSet, err := GetHealthFromQuery(opts.Query)
if err != nil {
return badRequestError(err)
ruleResponse.Status = "error"
ruleResponse.Error = err.Error()
ruleResponse.ErrorType = apiv1.ErrBadData
return ruleResponse
}
var labelOptions []ngmodels.LabelOption
@@ -946,9 +913,6 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
dataSourceUIDs := opts.Query["datasource_uid"]
searchRuleGroup := opts.Query.Get("search.rule_group")
// Split matchers: only equality/inequality are supported by the store
storeMatchers := filterOutRegexMatchers(ruleLabelMatchers)
alertRuleQuery := ngmodels.ListAlertRulesQuery{
OrgID: opts.OrgID,
NamespaceUIDs: namespaceUIDs,
@@ -960,7 +924,6 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
SearchTitle: title,
SearchRuleGroup: searchRuleGroup,
DataSourceUIDs: dataSourceUIDs,
LabelMatchers: storeMatchers,
}
ruleList, err := store.ListAlertRules(opts.Ctx, &alertRuleQuery)
if err != nil {
@@ -1015,10 +978,6 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
filterRulesByHealth(ruleGroup, healthFilterSet)
}
if len(ruleLabelMatchers) > 0 {
filterRulesByLabelMatchers(ruleGroup, ruleLabelMatchers)
}
if limitRulesPerGroup > -1 && int64(len(ruleGroup.Rules)) > limitRulesPerGroup {
ruleGroup.Rules = ruleGroup.Rules[0:limitRulesPerGroup]
}
@@ -1146,30 +1105,6 @@ func filterRulesByHealth(ruleGroup *apimodels.RuleGroup, withHealthFast map[stri
ruleGroup.Rules = filteredRules
}
func filterRulesByLabelMatchers(ruleGroup *apimodels.RuleGroup, matchers labels.Matchers) {
if len(matchers) == 0 {
return
}
filteredRules := make([]apimodels.AlertingRule, 0, len(ruleGroup.Rules))
for _, rule := range ruleGroup.Rules {
ruleLabels := rule.Labels.Map()
matches := true
for _, m := range matchers {
if !m.Matches(ruleLabels[m.Name]) {
matches = false
break
}
}
if matches {
filteredRules = append(filteredRules, rule)
}
}
ruleGroup.Rules = filteredRules
}
// This is the same as matchers.Matches but avoids the need to create a LabelSet
func matchersMatch(matchers []*labels.Matcher, labels map[string]string) bool {
for _, m := range matchers {
@@ -462,10 +462,4 @@ type GetGrafanaRuleStatusesParams struct {
// in: query
// required: false
Matchers []string `json:"matcher"`
// Filter rules by their static labels (not alert instance labels). Each value is a JSON-encoded Prometheus-like matcher (for example, {"type":0,"name":"severity","value":"critical"}).
// For equality matchers with empty string values (e.g., name=""), rules that have the label with an empty value OR rules without the label will match (standard Prometheus behavior).
// in: query
// required: false
RuleLabelMatchers []string `json:"rule_matcher"`
}
@@ -7561,15 +7561,6 @@
},
"name": "matcher",
"type": "array"
},
{
"description": "Filter rules by their static labels (not alert instance labels). Each value is a JSON-encoded Prometheus-like matcher (for example, {\"type\":0,\"name\":\"severity\",\"value\":\"critical\"}).\nFor equality matchers with empty string values (e.g., name=\"\"), rules that have the label with an empty value OR rules without the label will match (standard Prometheus behavior).",
"in": "query",
"items": {
"type": "string"
},
"name": "rule_matcher",
"type": "array"
}
],
"responses": {
@@ -1947,15 +1947,6 @@
"description": "Filter by label matchers encoded as JSON representations of Prometheus matchers (for example, {\"type\":0,\"name\":\"severity\",\"value\":\"critical\"}). Provide one matcher per query string value.",
"name": "matcher",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"description": "Filter rules by their static labels (not alert instance labels). Each value is a JSON-encoded Prometheus-like matcher (for example, {\"type\":0,\"name\":\"severity\",\"value\":\"critical\"}).\nFor equality matchers with empty string values (e.g., name=\"\"), rules that have the label with an empty value OR rules without the label will match (standard Prometheus behavior).",
"name": "rule_matcher",
"in": "query"
}
],
"responses": {
@@ -18,7 +18,6 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/prometheus/alertmanager/pkg/labels"
prommodels "github.com/prometheus/common/model"
"github.com/grafana/grafana-plugin-sdk-go/data"
@@ -1013,10 +1012,6 @@ type ListAlertRulesQuery struct {
SearchRuleGroup string
HasPrometheusRuleDefinition *bool
// LabelMatchers filters rules by their labels.
// Only equality and inequality matchers are supported, no regex operators.
LabelMatchers labels.Matchers
}
type ListAlertRulesExtendedQuery struct {
-39
View File
@@ -11,7 +11,6 @@ import (
"strings"
"github.com/google/uuid"
"github.com/prometheus/alertmanager/pkg/labels"
"golang.org/x/exp/maps"
"github.com/grafana/grafana/pkg/util/xorm"
@@ -803,15 +802,6 @@ func (st DBstore) ListAlertRulesPaginated(ctx context.Context, query *ngmodels.L
return result, nextToken, err
}
func matchersMatchLabels(matchers labels.Matchers, lbls map[string]string) bool {
for _, m := range matchers {
if !m.Matches(lbls[m.Name]) {
return false
}
}
return true
}
// nolint:gocyclo
func (st DBstore) buildListAlertRulesQuery(sess *db.Session, query *ngmodels.ListAlertRulesExtendedQuery) (q *xorm.Session, groupsSet map[string]struct{}, err error) {
q = sess.Table("alert_rule")
@@ -930,13 +920,6 @@ func (st DBstore) buildListAlertRulesQuery(sess *db.Session, query *ngmodels.Lis
}
}
if len(query.LabelMatchers) > 0 {
q, err = st.filterByLabelMatchers(query.LabelMatchers, q)
if err != nil {
return nil, groupsSet, err
}
}
// FIXME: record is nullable but we don't save it as null when it's nil
switch query.RuleType {
case ngmodels.RuleTypeFilterAlerting:
@@ -984,11 +967,6 @@ func (st DBstore) handleRuleRow(rows *xorm.Rows, query *ngmodels.ListAlertRulesE
return nil, false
}
}
if len(query.LabelMatchers) > 0 { // remove false-positive hits from the result
if !matchersMatchLabels(query.LabelMatchers, converted.Labels) {
return nil, false
}
}
// MySQL (and potentially other databases) can use case-insensitive comparison.
// This code makes sure we return groups that only exactly match the filter.
if groupsSet != nil {
@@ -1377,23 +1355,6 @@ func (st DBstore) filterWithPrometheusRuleDefinition(value bool, sess *xorm.Sess
), nil
}
// filterByLabelMatchers adds filtering for equality and inequality label matchers.
// Returns error if regex matchers are passed.
func (st DBstore) filterByLabelMatchers(matchers labels.Matchers, sess *xorm.Session) (*xorm.Session, error) {
for _, m := range matchers {
if m.Type != labels.MatchEqual && m.Type != labels.MatchNotEqual {
return nil, fmt.Errorf("matcher %q %s %q is not supported", m.Name, m.Type, m.Value)
}
sql, args, err := buildLabelMatcherCondition(st.SQLStore.GetDialect(), "labels", m)
if err != nil {
return nil, err
}
sess = sess.And(sql, args...)
}
return sess, nil
}
func (st DBstore) RenameReceiverInNotificationSettings(ctx context.Context, orgID int64, oldReceiver, newReceiver string, validateProvenance func(ngmodels.Provenance) bool, dryRun bool) ([]ngmodels.AlertRuleKey, []ngmodels.AlertRuleKey, error) {
// fetch entire rules because Update method requires it because it copies rules to version table
rules, err := st.ListAlertRules(ctx, &ngmodels.ListAlertRulesQuery{
@@ -1,51 +0,0 @@
package store
import (
"fmt"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
// buildLabelMatcherCondition builds SQL for a label matcher with Prometheus semantics.
// For MySQL/PostgreSQL, it uses JSON functions, and
// for SQLite, it uses GLOB patterns to find matching labels.
func buildLabelMatcherCondition(dialect migrator.Dialect, column string, m *labels.Matcher) (string, []any, error) {
if dialect.DriverName() == migrator.SQLite {
return buildLabelMatcherGlob(column, m)
}
return buildLabelMatcherJSON(dialect, column, m)
}
func buildLabelMatcherGlob(column string, m *labels.Matcher) (string, []any, error) {
switch {
case m.Type == labels.MatchEqual && m.Value == "":
eqSQL, eqArgs, _ := globEquals(column, m.Name, "")
missingSQL, missingArgs, _ := globKeyMissing(column, m.Name)
return "(" + eqSQL + " OR " + missingSQL + ")", append(eqArgs, missingArgs...), nil
case m.Type == labels.MatchEqual:
return globEquals(column, m.Name, m.Value)
case m.Type == labels.MatchNotEqual:
return globNotEquals(column, m.Name, m.Value)
default:
return "", nil, fmt.Errorf("unsupported matcher type: %v", m.Type)
}
}
func buildLabelMatcherJSON(dialect migrator.Dialect, column string, m *labels.Matcher) (string, []any, error) {
switch {
case m.Type == labels.MatchEqual && m.Value == "":
eqSQL, eqArgs := jsonEquals(dialect, column, m.Name, "")
missingSQL, missingArgs := jsonKeyMissing(dialect, column, m.Name)
return "(" + eqSQL + " OR " + missingSQL + ")", append(eqArgs, missingArgs...), nil
case m.Type == labels.MatchEqual:
sql, args := jsonEquals(dialect, column, m.Name, m.Value)
return sql, args, nil
case m.Type == labels.MatchNotEqual:
sql, args := jsonNotEquals(dialect, column, m.Name, m.Value)
return sql, args, nil
default:
return "", nil, fmt.Errorf("unsupported matcher type: %v", m.Type)
}
}
@@ -1,136 +0,0 @@
package store
import (
"testing"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
func TestBuildLabelMatcherGlob(t *testing.T) {
tests := []struct {
name string
matcher *labels.Matcher
wantSQL string
wantArgs []any
wantErr bool
errContains string
}{
{
name: "MatchEqual with non-empty value",
matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: "alerting"},
wantSQL: "labels GLOB ?",
wantArgs: []any{`*"team":"alerting"*`},
},
{
name: "MatchEqual with empty value (Prometheus semantics)",
matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: ""},
wantSQL: `(labels GLOB ? OR labels NOT GLOB ?)`,
wantArgs: []any{`*"team":""*`, `*"team":*`},
},
{
name: "MatchNotEqual",
matcher: &labels.Matcher{Type: labels.MatchNotEqual, Name: "team", Value: "alerting"},
wantSQL: "labels NOT GLOB ?",
wantArgs: []any{`*"team":"alerting"*`},
},
{
name: "unsupported matcher type",
matcher: &labels.Matcher{Type: labels.MatchRegexp, Name: "team", Value: "alert.*"},
wantErr: true,
errContains: "unsupported matcher type",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sql, args, err := buildLabelMatcherGlob("labels", tt.matcher)
if tt.wantErr {
require.Error(t, err)
require.Contains(t, err.Error(), tt.errContains)
return
}
require.NoError(t, err)
require.Equal(t, tt.wantSQL, sql)
require.Equal(t, tt.wantArgs, args)
})
}
}
func TestBuildLabelMatcherJSON(t *testing.T) {
tests := []struct {
name string
dialect migrator.Dialect
matcher *labels.Matcher
wantSQL string
wantArgs []any
wantErr bool
errContains string
}{
{
name: "MySQL MatchEqual with non-empty value",
dialect: migrator.NewMysqlDialect(),
matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: "alerting"},
wantSQL: "JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ?",
wantArgs: []any{"team", "alerting"},
},
{
name: "MySQL MatchEqual with empty value",
dialect: migrator.NewMysqlDialect(),
matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: ""},
wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ? OR JSON_EXTRACT(labels, CONCAT('$.', ?)) IS NULL)",
wantArgs: []any{"team", "", "team"},
},
{
name: "MySQL MatchNotEqual",
dialect: migrator.NewMysqlDialect(),
matcher: &labels.Matcher{Type: labels.MatchNotEqual, Name: "team", Value: "alerting"},
wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) != ?)",
wantArgs: []any{"team", "team", "alerting"},
},
{
name: "PostgreSQL MatchEqual with non-empty value",
dialect: migrator.NewPostgresDialect(),
matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: "alerting"},
wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) = ?",
wantArgs: []any{"team", "alerting"},
},
{
name: "PostgreSQL MatchEqual with empty value",
dialect: migrator.NewPostgresDialect(),
matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: ""},
wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) = ? OR jsonb_extract_path_text(labels::jsonb, ?) IS NULL)",
wantArgs: []any{"team", "", "team"},
},
{
name: "PostgreSQL MatchNotEqual",
dialect: migrator.NewPostgresDialect(),
matcher: &labels.Matcher{Type: labels.MatchNotEqual, Name: "team", Value: "alerting"},
wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) IS NULL OR jsonb_extract_path_text(labels::jsonb, ?) != ?)",
wantArgs: []any{"team", "team", "alerting"},
},
{
name: "unsupported matcher type",
dialect: migrator.NewMysqlDialect(),
matcher: &labels.Matcher{Type: labels.MatchRegexp, Name: "team", Value: "alert.*"},
wantErr: true,
errContains: "unsupported matcher type",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sql, args, err := buildLabelMatcherJSON(tt.dialect, "labels", tt.matcher)
if tt.wantErr {
require.Error(t, err)
require.Contains(t, err.Error(), tt.errContains)
return
}
require.NoError(t, err)
require.Equal(t, tt.wantSQL, sql)
require.Equal(t, tt.wantArgs, args)
})
}
}
@@ -13,7 +13,6 @@ import (
"github.com/benbjohnson/clock"
"github.com/google/uuid"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -2386,157 +2385,6 @@ func TestIntegration_ListAlertRules(t *testing.T) {
})
}
})
t.Run("filter by LabelMatchers", func(t *testing.T) {
sqlStore := db.InitTestDB(t)
folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures())
store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, b)
ruleLower := createRule(t, store, ruleGen.With(
ruleGen.WithLabels(map[string]string{"team": "alerting", "severity": "warning"}),
ruleGen.WithTitle("rule_lowercase")))
ruleUpper := createRule(t, store, ruleGen.With(
ruleGen.WithLabels(map[string]string{"team": "Alerting", "severity": "critical"}),
ruleGen.WithTitle("rule_uppercase")))
ruleSpecial := createRule(t, store, ruleGen.With(
ruleGen.WithLabels(map[string]string{"key": `value"with"quotes`}),
ruleGen.WithTitle("rule_special")))
ruleGlob := createRule(t, store, ruleGen.With(
ruleGen.WithLabels(map[string]string{"glob": "*[?]"}),
ruleGen.WithTitle("rule_glob")))
ruleSpecialChars := createRule(t, store, ruleGen.With(
ruleGen.WithLabels(map[string]string{"json": "line1\nline2\\end\"quote"}),
ruleGen.WithTitle("rule_special_chars")))
ruleEmpty := createRule(t, store, ruleGen.With(
ruleGen.WithLabels(map[string]string{"empty": ""}),
ruleGen.WithTitle("rule_empty")))
ruleNonempty := createRule(t, store, ruleGen.With(
ruleGen.WithLabels(map[string]string{"empty": "nonempty"}),
ruleGen.WithTitle("rule_nonempty")))
tc := []struct {
name string
labelMatchers labels.Matchers
expectedRules []*models.AlertRule
}{
{
name: "equality matcher is case-sensitive",
labelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "team", "alerting"); return m }(),
},
expectedRules: []*models.AlertRule{ruleLower},
},
{
name: "equality matcher matches uppercase when specified",
labelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "team", "Alerting"); return m }(),
},
expectedRules: []*models.AlertRule{ruleUpper},
},
{
name: "inequality matcher is case-sensitive",
labelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchNotEqual, "team", "alerting"); return m }(),
},
expectedRules: []*models.AlertRule{ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNonempty},
},
{
name: "special characters in labels are handled correctly",
labelMatchers: labels.Matchers{
func() *labels.Matcher {
m, _ := labels.NewMatcher(labels.MatchEqual, "key", `value"with"quotes`)
return m
}(),
},
expectedRules: []*models.AlertRule{ruleSpecial},
},
{
name: "matcher with non-existent label returns no rules",
labelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "nonexistent", "value"); return m }(),
},
expectedRules: []*models.AlertRule{},
},
{
name: "multiple matchers are ANDed",
labelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "team", "Alerting"); return m }(),
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "severity", "critical"); return m }(),
},
expectedRules: []*models.AlertRule{ruleUpper},
},
{
name: "GLOB special characters are escaped correctly",
labelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "glob", "*[?]"); return m }(),
},
expectedRules: []*models.AlertRule{ruleGlob},
},
{
name: "JSON escape characters are handled correctly",
labelMatchers: labels.Matchers{
func() *labels.Matcher {
m, _ := labels.NewMatcher(labels.MatchEqual, "json", "line1\nline2\\end\"quote")
return m
}(),
},
expectedRules: []*models.AlertRule{ruleSpecialChars},
},
{
name: "empty string value matches correctly",
labelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "empty", ""); return m }(),
},
expectedRules: []*models.AlertRule{ruleLower, ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty},
},
{
name: "inequality matcher on non-existent label matches all rules",
labelMatchers: labels.Matchers{
func() *labels.Matcher {
m, _ := labels.NewMatcher(labels.MatchNotEqual, "nonexistent", "value")
return m
}(),
},
expectedRules: []*models.AlertRule{ruleLower, ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNonempty},
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
query := &models.ListAlertRulesQuery{
OrgID: orgID,
LabelMatchers: tt.labelMatchers,
}
result, err := store.ListAlertRules(context.Background(), query)
require.NoError(t, err)
require.ElementsMatch(t, tt.expectedRules, result)
})
}
t.Run("regex matcher returns error from store", func(t *testing.T) {
query := &models.ListAlertRulesQuery{
OrgID: orgID,
LabelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchRegexp, "team", "alert.*"); return m }(),
},
}
_, err := store.ListAlertRules(context.Background(), query)
require.Error(t, err)
require.ErrorContains(t, err, "is not supported")
})
t.Run("not-regex matcher returns error from store", func(t *testing.T) {
query := &models.ListAlertRulesQuery{
OrgID: orgID,
LabelMatchers: labels.Matchers{
func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchNotRegexp, "team", "alert.*"); return m }(),
},
}
_, err := store.ListAlertRules(context.Background(), query)
require.Error(t, err)
require.ErrorContains(t, err, "is not supported")
})
})
}
func TestIntegration_ListAlertRulesPaginated(t *testing.T) {
-101
View File
@@ -1,101 +0,0 @@
package store
import (
"encoding/json"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
// JSON functions for MySQL/PostgreSQL
func jsonEquals(dialect migrator.Dialect, column, key, value string) (string, []any) {
switch dialect.DriverName() {
case migrator.MySQL:
return fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(%s, CONCAT('$.', ?))) = ?", column), []any{key, value}
case migrator.Postgres:
return fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?) = ?", column), []any{key, value}
default:
return "", nil
}
}
func jsonNotEquals(dialect migrator.Dialect, column, key, value string) (string, []any) {
var jx string
switch dialect.DriverName() {
case migrator.MySQL:
jx = fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(%s, CONCAT('$.', ?)))", column)
case migrator.Postgres:
jx = fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?)", column)
default:
return "", nil
}
return fmt.Sprintf("(%s IS NULL OR %s != ?)", jx, jx), []any{key, key, value}
}
func jsonKeyMissing(dialect migrator.Dialect, column, key string) (string, []any) {
switch dialect.DriverName() {
case migrator.MySQL:
return fmt.Sprintf("JSON_EXTRACT(%s, CONCAT('$.', ?)) IS NULL", column), []any{key}
case migrator.Postgres:
return fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?) IS NULL", column), []any{key}
default:
return "", nil
}
}
// GLOB functions for SQLite
func globEquals(column, key, value string) (string, []any, error) {
pattern, err := buildGlobPattern(key, value)
if err != nil {
return "", nil, err
}
return column + " GLOB ?", []any{"*" + pattern + "*"}, nil
}
func globNotEquals(column, key, value string) (string, []any, error) {
pattern, err := buildGlobPattern(key, value)
if err != nil {
return "", nil, err
}
return column + " NOT GLOB ?", []any{"*" + pattern + "*"}, nil
}
func globKeyMissing(column, key string) (string, []any, error) {
pattern, err := buildGlobKeyPattern(key)
if err != nil {
return "", nil, err
}
return column + " NOT GLOB ?", []any{"*" + pattern + "*"}, nil
}
// Search for `"key":"value"`
func buildGlobPattern(key, value string) (string, error) {
keyJSON, err := json.Marshal(key)
if err != nil {
return "", fmt.Errorf("failed to marshal key: %w", err)
}
valueJSON, err := json.Marshal(value)
if err != nil {
return "", fmt.Errorf("failed to marshal value: %w", err)
}
return escapeGlobPattern(fmt.Sprintf(`%s:%s`, string(keyJSON), string(valueJSON))), nil
}
// Search for `"key":`
func buildGlobKeyPattern(key string) (string, error) {
keyJSON, err := json.Marshal(key)
if err != nil {
return "", fmt.Errorf("failed to marshal key: %w", err)
}
return escapeGlobPattern(string(keyJSON) + ":"), nil
}
func escapeGlobPattern(pattern string) string {
pattern = strings.ReplaceAll(pattern, "[", "[[]")
pattern = strings.ReplaceAll(pattern, "*", "[*]")
pattern = strings.ReplaceAll(pattern, "?", "[?]")
return pattern
}
-185
View File
@@ -1,185 +0,0 @@
package store
import (
"testing"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/stretchr/testify/require"
)
func TestJsonEquals(t *testing.T) {
tests := []struct {
name string
dialect migrator.Dialect
column string
key string
value string
wantSQL string
wantArgs []any
}{
{
name: "MySQL",
dialect: migrator.NewMysqlDialect(),
column: "labels",
key: "team",
value: "alerting",
wantSQL: "JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ?",
wantArgs: []any{"team", "alerting"},
},
{
name: "PostgreSQL",
dialect: migrator.NewPostgresDialect(),
column: "labels",
key: "team",
value: "alerting",
wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) = ?",
wantArgs: []any{"team", "alerting"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sql, args := jsonEquals(tt.dialect, tt.column, tt.key, tt.value)
require.Equal(t, tt.wantSQL, sql)
require.Equal(t, tt.wantArgs, args)
})
}
}
func TestJsonNotEquals(t *testing.T) {
tests := []struct {
name string
dialect migrator.Dialect
column string
key string
value string
wantSQL string
wantArgs []any
}{
{
name: "MySQL",
dialect: migrator.NewMysqlDialect(),
column: "labels",
key: "team",
value: "alerting",
wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) != ?)",
wantArgs: []any{"team", "team", "alerting"},
},
{
name: "PostgreSQL",
dialect: migrator.NewPostgresDialect(),
column: "labels",
key: "team",
value: "alerting",
wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) IS NULL OR jsonb_extract_path_text(labels::jsonb, ?) != ?)",
wantArgs: []any{"team", "team", "alerting"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sql, args := jsonNotEquals(tt.dialect, tt.column, tt.key, tt.value)
require.Equal(t, tt.wantSQL, sql)
require.Equal(t, tt.wantArgs, args)
})
}
}
func TestJsonKeyMissing(t *testing.T) {
tests := []struct {
name string
dialect migrator.Dialect
column string
key string
wantSQL string
wantArgs []any
}{
{
name: "MySQL",
dialect: migrator.NewMysqlDialect(),
column: "labels",
key: "team",
wantSQL: "JSON_EXTRACT(labels, CONCAT('$.', ?)) IS NULL",
wantArgs: []any{"team"},
},
{
name: "PostgreSQL",
dialect: migrator.NewPostgresDialect(),
column: "labels",
key: "team",
wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) IS NULL",
wantArgs: []any{"team"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sql, args := jsonKeyMissing(tt.dialect, tt.column, tt.key)
require.Equal(t, tt.wantSQL, sql)
require.Equal(t, tt.wantArgs, args)
})
}
}
func TestGlobEquals(t *testing.T) {
sql, args, err := globEquals("labels", "team", "alerting")
require.NoError(t, err)
require.Equal(t, "labels GLOB ?", sql)
require.Equal(t, []any{`*"team":"alerting"*`}, args)
}
func TestGlobNotEquals(t *testing.T) {
sql, args, err := globNotEquals("labels", "team", "alerting")
require.NoError(t, err)
require.Equal(t, "labels NOT GLOB ?", sql)
require.Equal(t, []any{`*"team":"alerting"*`}, args)
}
func TestGlobKeyMissing(t *testing.T) {
sql, args, err := globKeyMissing("labels", "team")
require.NoError(t, err)
require.Equal(t, "labels NOT GLOB ?", sql)
require.Equal(t, []any{`*"team":*`}, args)
}
func TestBuildGlobPattern(t *testing.T) {
tests := []struct {
name string
key string
value string
expected string
}{
{
name: "simple key-value",
key: "team",
value: "alerting",
expected: `"team":"alerting"`,
},
{
name: "empty value",
key: "empty",
value: "",
expected: `"empty":""`,
},
{
name: "special GLOB chars are escaped",
key: "key",
value: "*[?]",
expected: `"key":"[*][[][?]]"`,
},
{
name: "special chars are escaped",
key: "key",
value: "line1\nline2\\end\"quote",
expected: `"key":"line1\nline2\\end\"quote"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pattern, err := buildGlobPattern(tt.key, tt.value)
require.NoError(t, err)
require.Equal(t, tt.expected, pattern)
})
}
}
-15
View File
@@ -219,7 +219,6 @@ func (f *RuleStore) ListAlertRulesByGroup(_ context.Context, q *models.ListAlert
RuleUIDs: q.RuleUIDs,
ReceiverName: q.ReceiverName,
HasPrometheusRuleDefinition: q.HasPrometheusRuleDefinition,
LabelMatchers: q.LabelMatchers,
}
ruleList, err := f.listAlertRules(query)
@@ -356,20 +355,6 @@ func (f *RuleStore) listAlertRules(q *models.ListAlertRulesQuery) (models.RulesG
if q.ReceiverName != "" && (len(r.NotificationSettings) < 1 || r.NotificationSettings[0].Receiver != q.ReceiverName) {
continue
}
if len(q.LabelMatchers) > 0 {
matches := true
for _, m := range q.LabelMatchers {
if !m.Matches(r.Labels[m.Name]) {
matches = false
break
}
}
if !matches {
continue
}
}
copyR := models.CopyRule(r)
ruleList = append(ruleList, copyR)
}
+4 -65
View File
@@ -7,7 +7,6 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"sort"
"testing"
"time"
@@ -365,6 +364,8 @@ func TestIntegrationPrometheusRules(t *testing.T) {
func TestIntegrationPrometheusRulesPagination(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
testinfra.SQLiteIntegrationTest(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
@@ -387,30 +388,23 @@ func TestIntegrationPrometheusRulesPagination(t *testing.T) {
require.NoError(t, err)
// Create 3 rule groups with different numbers of rules
// Group 1: 5 rules with team=backend
// Group 2: 3 rules with team=frontend
// Group 3: 2 rules with team=platform
// Group 1: 5 rules, Group 2: 3 rules, Group 3: 2 rules (total: 10 rules)
for groupIdx := 1; groupIdx <= 3; groupIdx++ {
var rulesCount int
var team string
switch groupIdx {
case 1:
rulesCount = 5
team = "backend"
case 2:
rulesCount = 3
team = "frontend"
case 3:
rulesCount = 2
team = "platform"
}
rules := make([]apimodels.PostableExtendedRuleNode, rulesCount)
for i := 0; i < rulesCount; i++ {
rules[i] = apimodels.PostableExtendedRuleNode{
ApiRuleNode: &apimodels.ApiRuleNode{
For: &interval,
Labels: map[string]string{"team": team},
For: &interval,
},
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
Title: fmt.Sprintf("rule-%d-%d", groupIdx, i+1),
@@ -520,61 +514,6 @@ func TestIntegrationPrometheusRulesPagination(t *testing.T) {
require.Equal(t, http.StatusOK, resp.StatusCode)
require.Len(t, result.Data.RuleGroups, 0, "should return no groups")
})
t.Run("with rule_matcher filter returns only matching rules", func(t *testing.T) {
matcher := url.QueryEscape(`{"name":"team","value":"frontend","isRegex":false,"isEqual":true}`)
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules?rule_matcher=%s", grafanaListedAddr, matcher)
// nolint:gosec
resp, err := http.Get(promRulesURL)
require.NoError(t, err)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
var result apimodels.RuleResponse
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
// Should only return group-2 (team=frontend, 3 rules)
foundGroups := []string{}
total := 0
for _, group := range result.Data.RuleGroups {
foundGroups = append(foundGroups, group.Name)
total += len(group.Rules)
}
require.Equal(t, []string{"group-2"}, foundGroups)
require.Equal(t, 3, total)
})
t.Run("with rule_matcher regex filter", func(t *testing.T) {
// Filter with regex team=~plat.* (should match group-3 with team=platform)
matcher := url.QueryEscape(`{"name":"team","value":"plat.*","isRegex":true,"isEqual":true}`)
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules?rule_matcher=%s", grafanaListedAddr, matcher)
// nolint:gosec
resp, err := http.Get(promRulesURL)
require.NoError(t, err)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
var result apimodels.RuleResponse
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
// Should only return group-3 (team=platform matches plat.*)
foundGroups := []string{}
total := 0
for _, group := range result.Data.RuleGroups {
foundGroups = append(foundGroups, group.Name)
total += len(group.Rules)
}
require.Equal(t, []string{"group-3"}, foundGroups)
require.Equal(t, 2, total)
})
}
func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) {
File diff suppressed because it is too large Load Diff
@@ -1,413 +0,0 @@
package provisioning
import (
"context"
"errors"
"testing"
"github.com/grafana/grafana/pkg/util/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestIntegrationProvisioning_ConnectionCRUDL(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := runGrafana(t)
createOptions := metav1.CreateOptions{FieldValidation: "Strict"}
ctx := context.Background()
t.Run("should perform CRUDL requests on connection", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "github",
"github": map[string]any{
"appID": "123456",
"installationID": "454545",
},
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
},
}}
// CREATE
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.NoError(t, err, "failed to create resource")
// READ
output, err := helper.Connections.Resource.Get(ctx, "connection", metav1.GetOptions{})
require.NoError(t, err, "failed to read back resource")
assert.Equal(t, "connection", output.GetName(), "name should be equal")
assert.Equal(t, "default", output.GetNamespace(), "namespace should be equal")
spec := output.Object["spec"].(map[string]any)
assert.Equal(t, "github", spec["type"], "type should be equal")
assert.Equal(t, "https://github.com/settings/installations/454545", spec["url"], "url should be equal")
require.Contains(t, spec, "github")
githubInfo := spec["github"].(map[string]any)
assert.Equal(t, "123456", githubInfo["appID"], "appID should be equal")
assert.Equal(t, "454545", githubInfo["installationID"], "installationID should be equal")
require.Contains(t, output.Object, "secure", "object should contain secure")
assert.Contains(t, output.Object["secure"], "privateKey", "secure should contain PrivateKey")
// LIST
list, err := helper.Connections.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err, "failed to list resource")
assert.Equal(t, 1, len(list.Items), "should have one connection")
assert.Equal(t, "connection", list.Items[0].GetName(), "name should be equal")
// UPDATE
updatedConnection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "github",
"github": map[string]any{
"appID": "456789",
"installationID": "454545",
},
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
},
}}
res, err := helper.Connections.Resource.Update(ctx, updatedConnection, metav1.UpdateOptions{})
require.NoError(t, err, "failed to update resource")
spec = res.Object["spec"].(map[string]any)
require.Contains(t, spec, "github")
githubInfo = spec["github"].(map[string]any)
assert.Equal(t, "456789", githubInfo["appID"], "appID should be updated")
// DELETE
require.NoError(t, helper.Connections.Resource.Delete(ctx, "connection", metav1.DeleteOptions{}), "failed to delete resource")
list, err = helper.Connections.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err, "failed to list resources")
assert.Equal(t, 0, len(list.Items), "should have no connections")
})
t.Run("viewer can't create or get connection", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "github",
"github": map[string]any{
"appID": "123456",
"installationID": "454545",
},
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
},
}}
result := helper.ViewerREST.Post().
Namespace("default").
Resource("connections").
Body(connection).
Do(t.Context())
require.NotNil(t, result.Error())
err := &k8serrors.StatusError{}
require.True(t, errors.As(result.Error(), &err))
assert.Equal(t, metav1.StatusReasonForbidden, err.Status().Reason)
assert.Contains(t, err.Status().Message, "User \"viewer\" cannot create resource \"connections\"")
assert.Contains(t, err.Status().Message, "admin role is required")
result = helper.ViewerREST.Get().
Namespace("default").
Resource("connections").
Name("connection").
Do(t.Context())
require.NotNil(t, result.Error())
err = &k8serrors.StatusError{}
require.True(t, errors.As(result.Error(), &err))
assert.Equal(t, metav1.StatusReasonForbidden, err.Status().Reason)
assert.Contains(t, err.Status().Message, "User \"viewer\" cannot get resource \"connections\"")
assert.Contains(t, err.Status().Message, "admin role is required")
})
}
func TestIntegrationProvisioning_ConnectionValidation(t *testing.T) {
helper := runGrafana(t)
createOptions := metav1.CreateOptions{FieldValidation: "Strict"}
ctx := context.Background()
t.Run("should fail when type is empty", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "",
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "type must be specified")
})
t.Run("should fail when type is invalid", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "some-invalid-type",
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "spec.type: Unsupported value: \"some-invalid-type\"")
})
t.Run("should fail when type is github but 'github' field is not there", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "github",
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "github info must be specified for GitHub connection")
})
t.Run("should fail when type is github but private key is not there", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "github",
"github": map[string]any{
"appID": "123456",
"installationID": "454545",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "privateKey must be specified for GitHub connection")
})
t.Run("should fail when type is github but a client Secret is specified", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "github",
"github": map[string]any{
"appID": "123456",
"installationID": "454545",
},
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
"clientSecret": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "clientSecret is forbidden in GitHub connection")
})
t.Run("should fail when type is bitbucket but 'bitbucket' field is not there", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "bitbucket",
},
"secure": map[string]any{
"clientSecret": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "bitbucket info must be specified in Bitbucket connection")
})
t.Run("should fail when type is bitbucket but client secret is not there", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "bitbucket",
"bitbucket": map[string]any{
"clientID": "123456",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "clientSecret must be specified for Bitbucket connection")
})
t.Run("should fail when type is bitbucket but a private key is specified", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "bitbucket",
"bitbucket": map[string]any{
"clientID": "123456",
},
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
"clientSecret": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "privateKey is forbidden in Bitbucket connection")
})
t.Run("should fail when type is gitlab but 'gitlab' field is not there", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "gitlab",
},
"secure": map[string]any{
"clientSecret": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "gitlab info must be specified in Gitlab connection")
})
t.Run("should fail when type is gitlab but client secret is not there", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "gitlab",
"gitlab": map[string]any{
"clientID": "123456",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "clientSecret must be specified for Gitlab connection")
})
t.Run("should fail when type is gitlab but a private key is specified", func(t *testing.T) {
connection := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "provisioning.grafana.app/v0alpha1",
"kind": "Connection",
"metadata": map[string]any{
"name": "connection",
"namespace": "default",
},
"spec": map[string]any{
"type": "gitlab",
"gitlab": map[string]any{
"clientID": "123456",
},
},
"secure": map[string]any{
"privateKey": map[string]any{
"create": "someSecret",
},
"clientSecret": map[string]any{
"create": "someSecret",
},
},
}}
_, err := helper.Connections.Resource.Create(ctx, connection, createOptions)
require.Error(t, err, "failed to create resource")
assert.Contains(t, err.Error(), "privateKey is forbidden in Gitlab connection")
})
}
@@ -53,7 +53,6 @@ type provisioningTestHelper struct {
ProvisioningPath string
Repositories *apis.K8sResourceClient
Connections *apis.K8sResourceClient
Jobs *apis.K8sResourceClient
Folders *apis.K8sResourceClient
DashboardsV0 *apis.K8sResourceClient
@@ -704,11 +703,6 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
Namespace: "default", // actually org1
GVR: provisioning.RepositoryResourceInfo.GroupVersionResource(),
})
connections := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: "default", // actually org1
GVR: provisioning.ConnectionResourceInfo.GroupVersionResource(),
})
jobs := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
Namespace: "default", // actually org1
@@ -769,7 +763,6 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
K8sTestHelper: helper,
Repositories: repositories,
Connections: connections,
AdminREST: adminClient,
EditorREST: editorClient,
ViewerREST: viewerClient,
@@ -964,49 +957,3 @@ func (h *provisioningTestHelper) CleanupAllRepos(t *testing.T) {
assert.Equal(collect, 0, len(list.Items), "repositories should be cleaned up")
}, waitTimeoutDefault, waitIntervalDefault, "repositories should be cleaned up between subtests")
}
func postHelper(t *testing.T, helper apis.K8sTestHelper, path string, body interface{}, user apis.User) (map[string]interface{}, int, error) {
return requestHelper(t, helper, http.MethodPost, path, body, user)
}
func patchHelper(t *testing.T, helper apis.K8sTestHelper, path string, body interface{}, user apis.User) (map[string]interface{}, int, error) {
return requestHelper(t, helper, http.MethodPatch, path, body, user)
}
func requestHelper(
t *testing.T,
helper apis.K8sTestHelper,
method string,
path string,
body interface{},
user apis.User,
) (map[string]interface{}, int, error) {
bodyJSON, err := json.Marshal(body)
require.NoError(t, err)
resp := apis.DoRequest(&helper, apis.RequestParams{
User: user,
Method: method,
Path: path,
Body: bodyJSON,
ContentType: "application/json",
}, &struct{}{})
if resp.Response.StatusCode != http.StatusOK {
res := map[string]interface{}{}
err := json.Unmarshal(resp.Body, &res)
if err != nil {
return nil, 0, fmt.Errorf("failed to unmarshal response JSON: %v", err)
}
return res, resp.Response.StatusCode, fmt.Errorf("failure when making request: %s", resp.Response.Status)
}
var result map[string]interface{}
err = json.Unmarshal(resp.Body, &result)
if err != nil {
return nil, 0, fmt.Errorf("failed to unmarshal response JSON: %v", err)
}
return result, resp.Response.StatusCode, nil
}

Some files were not shown because too many files have changed in this diff Show More