diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3c85792624d..350790df440 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -83,6 +83,7 @@ /apps/dashboard/ @grafana/grafana-app-platform-squad @grafana/dashboards-squad /apps/folder/ @grafana/grafana-app-platform-squad /apps/playlist/ @grafana/grafana-app-platform-squad +/apps/plugins/ @grafana/plugins-platform-backend /apps/preferences/ @grafana/grafana-app-platform-squad @grafana/grafana-frontend-platform /apps/shorturl/ @grafana/sharing-squad /apps/secret/ @grafana/grafana-operator-experience-squad @@ -1109,6 +1110,7 @@ embed.go @grafana/grafana-as-code /pkg/registry/apis/userstorage @grafana/grafana-app-platform-squad @grafana/plugins-platform-backend /pkg/registry/apps/advisor @grafana/plugins-platform-backend /pkg/registry/apps/alerting @grafana/alerting-backend +/pkg/registry/apps/plugins @grafana/plugins-platform-backend /pkg/codegen/ @grafana/grafana-as-code /pkg/codegen/generators @grafana/grafana-as-code /pkg/kinds/*/*_gen.go @grafana/grafana-as-code diff --git a/Dockerfile b/Dockerfile index e7c0ec0fadb..b059c1c1a47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,6 +83,7 @@ COPY pkg/storage/unified/apistore pkg/storage/unified/apistore COPY pkg/semconv pkg/semconv COPY pkg/aggregator pkg/aggregator COPY apps/playlist apps/playlist +COPY apps/plugins apps/plugins COPY apps/shorturl apps/shorturl COPY apps/provisioning apps/provisioning COPY apps/secret apps/secret diff --git a/apps/plugins/Makefile b/apps/plugins/Makefile new file mode 100644 index 00000000000..230bfd4149a --- /dev/null +++ b/apps/plugins/Makefile @@ -0,0 +1,9 @@ +include ../sdk.mk + +.PHONY: generate # Run Grafana App SDK code generation +generate: install-app-sdk update-app-sdk + @$(APP_SDK_BIN) generate \ + --source=./kinds/ \ + --gogenpath=./pkg/apis \ + --grouping=group \ + --defencoding=none \ No newline at end of file diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod new file mode 100644 index 00000000000..2c287058eec --- /dev/null +++ b/apps/plugins/go.mod @@ -0,0 +1,99 @@ +module github.com/grafana/grafana/apps/plugins + +go 1.24.4 + +require ( + github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 + github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde + k8s.io/apimachinery v0.33.3 + k8s.io/apiserver v0.33.3 + k8s.io/klog/v2 v2.130.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/getkin/kin-openapi v0.132.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-test/deep v1.1.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 // indirect + github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/onsi/ginkgo/v2 v2.22.2 // indirect + github.com/onsi/gomega v1.36.2 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/spf13/pflag v1.0.7 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/term v0.33.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/time v0.11.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/grpc v1.74.2 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.33.3 // indirect + k8s.io/apiextensions-apiserver v0.33.3 // indirect + k8s.io/client-go v0.33.3 // indirect + k8s.io/component-base v0.33.3 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // 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/v4 v4.7.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect +) diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum new file mode 100644 index 00000000000..28130d1dc5b --- /dev/null +++ b/apps/plugins/go.sum @@ -0,0 +1,287 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= +github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43 h1:vVPT0i5Y1vI6qzecYStV2yk7cHKrC3Pc7AgvwT5KydQ= +github.com/grafana/authlib v0.0.0-20250710201142-9542f2f28d43/go.mod h1:1fWkOiL+m32NBgRHZtlZGz2ji868tPZACYbqP3nBRJI= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/oUn6Cr90QbJYpQJ4FnjyAIG9Ex5GtTZIzw= +github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= +github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= +github.com/grafana/grafana-app-sdk v0.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU= +github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= +github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4= +github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde h1:ydSrBIOCxJQ84+JU+cyYsOLL40QeXrB7rYfsY/ezU4w= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde/go.mod h1:3MwgP0ISxGviTy3ZUJZsNz/56NNtHztMlH+gcxDt6Tw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= +github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= +k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= +k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= +k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= +k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= +k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= +k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= +k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= +k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= +k8s.io/component-base v0.33.3 h1:mlAuyJqyPlKZM7FyaoM/LcunZaaY353RXiOd2+B5tGA= +k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/apps/plugins/kinds/cue.mod/module.cue b/apps/plugins/kinds/cue.mod/module.cue new file mode 100644 index 00000000000..95dc1c62455 --- /dev/null +++ b/apps/plugins/kinds/cue.mod/module.cue @@ -0,0 +1,4 @@ +module: "github.com/grafana/grafana/apps/plugins/kinds" +language: { + version: "v0.9.0" +} diff --git a/apps/plugins/kinds/manifest.cue b/apps/plugins/kinds/manifest.cue new file mode 100644 index 00000000000..f16ba2d1a72 --- /dev/null +++ b/apps/plugins/kinds/manifest.cue @@ -0,0 +1,19 @@ +package plugins + +manifest: { + appName: "plugins" + groupOverride: "plugins.grafana.app" + versions: { + "v0alpha1": { + served: false + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + pluginMetaV0Alpha1, + pluginInstallV0Alpha1, + ] + } + } +} diff --git a/apps/plugins/kinds/plugininstall.cue b/apps/plugins/kinds/plugininstall.cue new file mode 100644 index 00000000000..82181fda1d4 --- /dev/null +++ b/apps/plugins/kinds/plugininstall.cue @@ -0,0 +1,13 @@ +package plugins + +pluginInstallV0Alpha1: { + kind: "PluginInstall" + plural: "plugininstalls" + scope: "Namespaced" + schema: { + spec: { + id: string + version: string + } + } +} diff --git a/apps/plugins/kinds/pluginmeta.cue b/apps/plugins/kinds/pluginmeta.cue new file mode 100644 index 00000000000..93f41095707 --- /dev/null +++ b/apps/plugins/kinds/pluginmeta.cue @@ -0,0 +1,220 @@ +package plugins + +import ( + "time" +) + +pluginMetaV0Alpha1: { + kind: "PluginMeta" + plural: "pluginsmeta" + scope: "Namespaced" + schema: { + spec: { + pluginJSON: #JSONData, + } + } +} + +// JSON configuration schema for Grafana plugins +// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json +#JSONData: { + // Unique name of the plugin + id: string + + // Plugin type + type: "app" | "datasource" | "panel" | "renderer" + + // Human-readable name of the plugin + name: string + + // Metadata for the plugin + info: #Info + + // Dependency information + dependencies: #Dependencies + + // Optional fields + alerting?: bool + annotations?: bool + autoEnabled?: bool + backend?: bool + buildMode?: string + builtIn?: bool + category?: "tsdb" | "logging" | "cloud" | "tracing" | "profiling" | "sql" | "enterprise" | "iot" | "other" + enterpriseFeatures?: #EnterpriseFeatures + executable?: string + hideFromList?: bool + // +listType=atomic + includes?: [...#Include] + logs?: bool + metrics?: bool + multiValueFilterOperators?: bool + pascalName?: string + preload?: bool + queryOptions?: #QueryOptions + // +listType=atomic + routes?: [...#Route] + skipDataQuery?: bool + state?: "alpha" | "beta" + streaming?: bool + tracing?: bool + iam?: #IAM + // +listType=atomic + roles?: [...#Role] + extensions?: #Extensions +} + +#Info: { + // Required fields + // +listType=set + keywords: [...string] + logos: { + small: string + large: string + } + updated: string & time.Time + version: string + // Optional fields + author?: { + name?: string + email?: string + url?: string + } + description?: string + // +listType=atomic + links?: [...{ + name?: string + url?: string + }] + // +listType=atomic + screenshots?: [...{ + name?: string + path?: string + }] +} + +#Dependencies: { + // Required field + grafanaDependency: string + + // Optional fields + grafanaVersion?: string + // +listType=set + // +listMapKey=id + plugins?: [...{ + id: string + type: "app" | "datasource" | "panel" + name: string + }] + extensions?: { + // +listType=set + exposedComponents?: [...string] + } +} + +#EnterpriseFeatures: { + healthDiagnosticsErrors?: bool | *false + // Allow additional properties + [string]: _ +} + +#Include: { + uid?: string + type?: "dashboard" | "page" | "panel" | "datasource" + name?: string + component?: string + role?: "Admin" | "Editor" | "Viewer" + action?: string + path?: string + addToNav?: bool + defaultNav?: bool + icon?: string +} + +#QueryOptions: { + maxDataPoints?: bool + minInterval?: bool + cacheTimeout?: bool +} + +#Route: { + path?: string + method?: string + url?: string + reqSignedIn?: bool + reqRole?: string + reqAction?: string + // +listType=atomic + headers?: [...string] + body?: [string]: _ + tokenAuth?: { + url?: string + // +listType=set + scopes?: [...string] + params?: [string]: _ + } + jwtTokenAuth?: { + url?: string + // +listType=set + scopes?: [...string] + params?: [string]: _ + } + // +listType=atomic + urlParams?: [...{ + name?: string + content?: string + }] +} + +#IAM: { + // +listType=atomic + permissions?: [...{ + action?: string + scope?: string + }] +} + +#Role: { + role?: { + name?: string + description?: string + // +listType=atomic + permissions?: [...{ + action?: string + scope?: string + }] + } + // +listType=set + grants?: [...string] +} + +#Extensions: { + // +listType=atomic + addedComponents?: [...{ + // +listType=set + targets: [...string] + title: string + description?: string + }] + // +listType=atomic + addedLinks?: [...{ + // +listType=set + targets: [...string] + title: string + description?: string + }] + // +listType=set + // +listMapKey=id + exposedComponents?: [...{ + id: string + title?: string + description?: string + }] + // +listType=set + // +listMapKey=id + extensionPoints?: [...{ + id: string + title?: string + description?: string + }] +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/constants.go b/apps/plugins/pkg/apis/plugins/v0alpha1/constants.go new file mode 100644 index 00000000000..664e44131ab --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/constants.go @@ -0,0 +1,18 @@ +package v0alpha1 + +import "k8s.io/apimachinery/pkg/runtime/schema" + +const ( + // APIGroup is the API group used by all kinds in this package + APIGroup = "plugins.grafana.app" + // APIVersion is the API version used by all kinds in this package + APIVersion = "v0alpha1" +) + +var ( + // GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package + GroupVersion = schema.GroupVersion{ + Group: APIGroup, + Version: APIVersion, + } +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go new file mode 100644 index 00000000000..48b7e98fd2f --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// PluginInstallJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type PluginInstallJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*PluginInstallJSONCodec) Read(reader io.Reader, into resource.Object) error { + return json.NewDecoder(reader).Decode(into) +} + +// Write writes JSON-encoded bytes into `writer` marshaled from `from` +func (*PluginInstallJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &PluginInstallJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go new file mode 100644 index 00000000000..0e944597301 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go @@ -0,0 +1,31 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// metadata contains embedded CommonMetadata and can be extended with custom string fields +// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +// without external reference as using the CommonMetadata reference breaks thema codegen. +type PluginInstallMetadata struct { + UpdateTimestamp time.Time `json:"updateTimestamp"` + CreatedBy string `json:"createdBy"` + Uid string `json:"uid"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + ResourceVersion string `json:"resourceVersion"` + Generation int64 `json:"generation"` + UpdatedBy string `json:"updatedBy"` + Labels map[string]string `json:"labels"` +} + +// NewPluginInstallMetadata creates a new PluginInstallMetadata object. +func NewPluginInstallMetadata() *PluginInstallMetadata { + return &PluginInstallMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go new file mode 100644 index 00000000000..6ceaa603baa --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go @@ -0,0 +1,319 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "fmt" + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "time" +) + +// +k8s:openapi-gen=true +type PluginInstall struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the PluginInstall + Spec PluginInstallSpec `json:"spec" yaml:"spec"` + + Status PluginInstallStatus `json:"status" yaml:"status"` +} + +func (o *PluginInstall) GetSpec() any { + return o.Spec +} + +func (o *PluginInstall) SetSpec(spec any) error { + cast, ok := spec.(PluginInstallSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *PluginInstall) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *PluginInstall) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *PluginInstall) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(PluginInstallStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type PluginInstallStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *PluginInstall) GetStaticMetadata() resource.StaticMetadata { + gvk := o.GroupVersionKind() + return resource.StaticMetadata{ + Name: o.ObjectMeta.Name, + Namespace: o.ObjectMeta.Namespace, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + } +} + +func (o *PluginInstall) SetStaticMetadata(metadata resource.StaticMetadata) { + o.Name = metadata.Name + o.Namespace = metadata.Namespace + o.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.Group, + Version: metadata.Version, + Kind: metadata.Kind, + }) +} + +func (o *PluginInstall) GetCommonMetadata() resource.CommonMetadata { + dt := o.DeletionTimestamp + var deletionTimestamp *time.Time + if dt != nil { + deletionTimestamp = &dt.Time + } + // Legacy ExtraFields support + extraFields := make(map[string]any) + if o.Annotations != nil { + extraFields["annotations"] = o.Annotations + } + if o.ManagedFields != nil { + extraFields["managedFields"] = o.ManagedFields + } + if o.OwnerReferences != nil { + extraFields["ownerReferences"] = o.OwnerReferences + } + return resource.CommonMetadata{ + UID: string(o.UID), + ResourceVersion: o.ResourceVersion, + Generation: o.Generation, + Labels: o.Labels, + CreationTimestamp: o.CreationTimestamp.Time, + DeletionTimestamp: deletionTimestamp, + Finalizers: o.Finalizers, + UpdateTimestamp: o.GetUpdateTimestamp(), + CreatedBy: o.GetCreatedBy(), + UpdatedBy: o.GetUpdatedBy(), + ExtraFields: extraFields, + } +} + +func (o *PluginInstall) SetCommonMetadata(metadata resource.CommonMetadata) { + o.UID = types.UID(metadata.UID) + o.ResourceVersion = metadata.ResourceVersion + o.Generation = metadata.Generation + o.Labels = metadata.Labels + o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) + if metadata.DeletionTimestamp != nil { + dt := metav1.NewTime(*metadata.DeletionTimestamp) + o.DeletionTimestamp = &dt + } else { + o.DeletionTimestamp = nil + } + o.Finalizers = metadata.Finalizers + if o.Annotations == nil { + o.Annotations = make(map[string]string) + } + if !metadata.UpdateTimestamp.IsZero() { + o.SetUpdateTimestamp(metadata.UpdateTimestamp) + } + if metadata.CreatedBy != "" { + o.SetCreatedBy(metadata.CreatedBy) + } + if metadata.UpdatedBy != "" { + o.SetUpdatedBy(metadata.UpdatedBy) + } + // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields + if metadata.ExtraFields != nil { + if annotations, ok := metadata.ExtraFields["annotations"]; ok { + if cast, ok := annotations.(map[string]string); ok { + o.Annotations = cast + } + } + if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { + if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { + o.ManagedFields = cast + } + } + if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { + if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { + o.OwnerReferences = cast + } + } + } +} + +func (o *PluginInstall) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *PluginInstall) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *PluginInstall) GetUpdateTimestamp() time.Time { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) + return parsed +} + +func (o *PluginInstall) SetUpdateTimestamp(updateTimestamp time.Time) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) +} + +func (o *PluginInstall) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *PluginInstall) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *PluginInstall) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *PluginInstall) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginInstall) DeepCopy() *PluginInstall { + cpy := &PluginInstall{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginInstall) DeepCopyInto(dst *PluginInstall) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) + o.Status.DeepCopyInto(&dst.Status) +} + +// Interface compliance compile-time check +var _ resource.Object = &PluginInstall{} + +// +k8s:openapi-gen=true +type PluginInstallList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []PluginInstall `json:"items" yaml:"items"` +} + +func (o *PluginInstallList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginInstallList) Copy() resource.ListObject { + cpy := &PluginInstallList{ + TypeMeta: o.TypeMeta, + Items: make([]PluginInstall, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*PluginInstall); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *PluginInstallList) GetItems() []resource.Object { + items := make([]resource.Object, len(o.Items)) + for i := 0; i < len(o.Items); i++ { + items[i] = &o.Items[i] + } + return items +} + +func (o *PluginInstallList) SetItems(items []resource.Object) { + o.Items = make([]PluginInstall, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*PluginInstall) + } +} + +func (o *PluginInstallList) DeepCopy() *PluginInstallList { + cpy := &PluginInstallList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginInstallList) DeepCopyInto(dst *PluginInstallList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &PluginInstallList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *PluginInstallSpec) DeepCopy() *PluginInstallSpec { + cpy := &PluginInstallSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *PluginInstallSpec) DeepCopyInto(dst *PluginInstallSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of PluginInstallStatus +func (s *PluginInstallStatus) DeepCopy() *PluginInstallStatus { + cpy := &PluginInstallStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies PluginInstallStatus into another PluginInstallStatus object +func (s *PluginInstallStatus) DeepCopyInto(dst *PluginInstallStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go new file mode 100644 index 00000000000..85ea40cd157 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaPluginInstall = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginInstall{}, &PluginInstallList{}, resource.WithKind("PluginInstall"), + resource.WithPlural("plugininstalls"), resource.WithScope(resource.NamespacedScope)) + kindPluginInstall = resource.Kind{ + Schema: schemaPluginInstall, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &PluginInstallJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func PluginInstallKind() resource.Kind { + return kindPluginInstall +} + +// Schema returns a resource.SimpleSchema representation of PluginInstall +func PluginInstallSchema() *resource.SimpleSchema { + return schemaPluginInstall +} + +// Interface compliance checks +var _ resource.Schema = kindPluginInstall diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go new file mode 100644 index 00000000000..c815a69eac2 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go @@ -0,0 +1,14 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginInstallSpec struct { + Id string `json:"id"` + Version string `json:"version"` +} + +// NewPluginInstallSpec creates a new PluginInstallSpec object. +func NewPluginInstallSpec() *PluginInstallSpec { + return &PluginInstallSpec{} +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go new file mode 100644 index 00000000000..77fc07860b0 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginInstallstatusOperatorState struct { + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State PluginInstallStatusOperatorStateState `json:"state"` + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` +} + +// NewPluginInstallstatusOperatorState creates a new PluginInstallstatusOperatorState object. +func NewPluginInstallstatusOperatorState() *PluginInstallstatusOperatorState { + return &PluginInstallstatusOperatorState{} +} + +// +k8s:openapi-gen=true +type PluginInstallStatus struct { + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]PluginInstallstatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewPluginInstallStatus creates a new PluginInstallStatus object. +func NewPluginInstallStatus() *PluginInstallStatus { + return &PluginInstallStatus{} +} + +// +k8s:openapi-gen=true +type PluginInstallStatusOperatorStateState string + +const ( + PluginInstallStatusOperatorStateStateSuccess PluginInstallStatusOperatorStateState = "success" + PluginInstallStatusOperatorStateStateInProgress PluginInstallStatusOperatorStateState = "in_progress" + PluginInstallStatusOperatorStateStateFailed PluginInstallStatusOperatorStateState = "failed" +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go new file mode 100644 index 00000000000..77fb6f918a2 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// PluginMetaJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type PluginMetaJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*PluginMetaJSONCodec) Read(reader io.Reader, into resource.Object) error { + return json.NewDecoder(reader).Decode(into) +} + +// Write writes JSON-encoded bytes into `writer` marshaled from `from` +func (*PluginMetaJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &PluginMetaJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go new file mode 100644 index 00000000000..7d3b3c9c6b8 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go @@ -0,0 +1,31 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// metadata contains embedded CommonMetadata and can be extended with custom string fields +// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +// without external reference as using the CommonMetadata reference breaks thema codegen. +type PluginMetaMetadata struct { + UpdateTimestamp time.Time `json:"updateTimestamp"` + CreatedBy string `json:"createdBy"` + Uid string `json:"uid"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + ResourceVersion string `json:"resourceVersion"` + Generation int64 `json:"generation"` + UpdatedBy string `json:"updatedBy"` + Labels map[string]string `json:"labels"` +} + +// NewPluginMetaMetadata creates a new PluginMetaMetadata object. +func NewPluginMetaMetadata() *PluginMetaMetadata { + return &PluginMetaMetadata{ + Finalizers: []string{}, + Labels: map[string]string{}, + } +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go new file mode 100644 index 00000000000..dac431ebf12 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go @@ -0,0 +1,319 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "fmt" + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "time" +) + +// +k8s:openapi-gen=true +type PluginMeta struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + + // Spec is the spec of the PluginMeta + Spec PluginMetaSpec `json:"spec" yaml:"spec"` + + Status PluginMetaStatus `json:"status" yaml:"status"` +} + +func (o *PluginMeta) GetSpec() any { + return o.Spec +} + +func (o *PluginMeta) SetSpec(spec any) error { + cast, ok := spec.(PluginMetaSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *PluginMeta) GetSubresources() map[string]any { + return map[string]any{ + "status": o.Status, + } +} + +func (o *PluginMeta) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.Status, true + default: + return nil, false + } +} + +func (o *PluginMeta) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(PluginMetaStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type PluginMetaStatus", value) + } + o.Status = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *PluginMeta) GetStaticMetadata() resource.StaticMetadata { + gvk := o.GroupVersionKind() + return resource.StaticMetadata{ + Name: o.ObjectMeta.Name, + Namespace: o.ObjectMeta.Namespace, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + } +} + +func (o *PluginMeta) SetStaticMetadata(metadata resource.StaticMetadata) { + o.Name = metadata.Name + o.Namespace = metadata.Namespace + o.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.Group, + Version: metadata.Version, + Kind: metadata.Kind, + }) +} + +func (o *PluginMeta) GetCommonMetadata() resource.CommonMetadata { + dt := o.DeletionTimestamp + var deletionTimestamp *time.Time + if dt != nil { + deletionTimestamp = &dt.Time + } + // Legacy ExtraFields support + extraFields := make(map[string]any) + if o.Annotations != nil { + extraFields["annotations"] = o.Annotations + } + if o.ManagedFields != nil { + extraFields["managedFields"] = o.ManagedFields + } + if o.OwnerReferences != nil { + extraFields["ownerReferences"] = o.OwnerReferences + } + return resource.CommonMetadata{ + UID: string(o.UID), + ResourceVersion: o.ResourceVersion, + Generation: o.Generation, + Labels: o.Labels, + CreationTimestamp: o.CreationTimestamp.Time, + DeletionTimestamp: deletionTimestamp, + Finalizers: o.Finalizers, + UpdateTimestamp: o.GetUpdateTimestamp(), + CreatedBy: o.GetCreatedBy(), + UpdatedBy: o.GetUpdatedBy(), + ExtraFields: extraFields, + } +} + +func (o *PluginMeta) SetCommonMetadata(metadata resource.CommonMetadata) { + o.UID = types.UID(metadata.UID) + o.ResourceVersion = metadata.ResourceVersion + o.Generation = metadata.Generation + o.Labels = metadata.Labels + o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) + if metadata.DeletionTimestamp != nil { + dt := metav1.NewTime(*metadata.DeletionTimestamp) + o.DeletionTimestamp = &dt + } else { + o.DeletionTimestamp = nil + } + o.Finalizers = metadata.Finalizers + if o.Annotations == nil { + o.Annotations = make(map[string]string) + } + if !metadata.UpdateTimestamp.IsZero() { + o.SetUpdateTimestamp(metadata.UpdateTimestamp) + } + if metadata.CreatedBy != "" { + o.SetCreatedBy(metadata.CreatedBy) + } + if metadata.UpdatedBy != "" { + o.SetUpdatedBy(metadata.UpdatedBy) + } + // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields + if metadata.ExtraFields != nil { + if annotations, ok := metadata.ExtraFields["annotations"]; ok { + if cast, ok := annotations.(map[string]string); ok { + o.Annotations = cast + } + } + if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { + if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { + o.ManagedFields = cast + } + } + if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { + if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { + o.OwnerReferences = cast + } + } + } +} + +func (o *PluginMeta) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *PluginMeta) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *PluginMeta) GetUpdateTimestamp() time.Time { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) + return parsed +} + +func (o *PluginMeta) SetUpdateTimestamp(updateTimestamp time.Time) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) +} + +func (o *PluginMeta) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *PluginMeta) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *PluginMeta) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *PluginMeta) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginMeta) DeepCopy() *PluginMeta { + cpy := &PluginMeta{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginMeta) DeepCopyInto(dst *PluginMeta) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) + o.Spec.DeepCopyInto(&dst.Spec) + o.Status.DeepCopyInto(&dst.Status) +} + +// Interface compliance compile-time check +var _ resource.Object = &PluginMeta{} + +// +k8s:openapi-gen=true +type PluginMetaList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []PluginMeta `json:"items" yaml:"items"` +} + +func (o *PluginMetaList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *PluginMetaList) Copy() resource.ListObject { + cpy := &PluginMetaList{ + TypeMeta: o.TypeMeta, + Items: make([]PluginMeta, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*PluginMeta); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *PluginMetaList) GetItems() []resource.Object { + items := make([]resource.Object, len(o.Items)) + for i := 0; i < len(o.Items); i++ { + items[i] = &o.Items[i] + } + return items +} + +func (o *PluginMetaList) SetItems(items []resource.Object) { + o.Items = make([]PluginMeta, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*PluginMeta) + } +} + +func (o *PluginMetaList) DeepCopy() *PluginMetaList { + cpy := &PluginMetaList{} + o.DeepCopyInto(cpy) + return cpy +} + +func (o *PluginMetaList) DeepCopyInto(dst *PluginMetaList) { + resource.CopyObjectInto(dst, o) +} + +// Interface compliance compile-time check +var _ resource.ListObject = &PluginMetaList{} + +// Copy methods for all subresource types + +// DeepCopy creates a full deep copy of Spec +func (s *PluginMetaSpec) DeepCopy() *PluginMetaSpec { + cpy := &PluginMetaSpec{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies Spec into another Spec object +func (s *PluginMetaSpec) DeepCopyInto(dst *PluginMetaSpec) { + resource.CopyObjectInto(dst, s) +} + +// DeepCopy creates a full deep copy of PluginMetaStatus +func (s *PluginMetaStatus) DeepCopy() *PluginMetaStatus { + cpy := &PluginMetaStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies PluginMetaStatus into another PluginMetaStatus object +func (s *PluginMetaStatus) DeepCopyInto(dst *PluginMetaStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go new file mode 100644 index 00000000000..a4022c8de97 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaPluginMeta = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginMeta{}, &PluginMetaList{}, resource.WithKind("PluginMeta"), + resource.WithPlural("pluginmetas"), resource.WithScope(resource.NamespacedScope)) + kindPluginMeta = resource.Kind{ + Schema: schemaPluginMeta, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &PluginMetaJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func PluginMetaKind() resource.Kind { + return kindPluginMeta +} + +// Schema returns a resource.SimpleSchema representation of PluginMeta +func PluginMetaSchema() *resource.SimpleSchema { + return schemaPluginMeta +} + +// Interface compliance checks +var _ resource.Schema = kindPluginMeta diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go new file mode 100644 index 00000000000..8020e34efda --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go @@ -0,0 +1,477 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// JSON configuration schema for Grafana plugins +// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json +// +k8s:openapi-gen=true +type PluginMetaJSONData struct { + // Unique name of the plugin + Id string `json:"id"` + // Plugin type + Type PluginMetaJSONDataType `json:"type"` + // Human-readable name of the plugin + Name string `json:"name"` + // Metadata for the plugin + Info PluginMetaInfo `json:"info"` + // Dependency information + Dependencies PluginMetaDependencies `json:"dependencies"` + // Optional fields + Alerting *bool `json:"alerting,omitempty"` + Annotations *bool `json:"annotations,omitempty"` + AutoEnabled *bool `json:"autoEnabled,omitempty"` + Backend *bool `json:"backend,omitempty"` + BuildMode *string `json:"buildMode,omitempty"` + BuiltIn *bool `json:"builtIn,omitempty"` + Category *PluginMetaJSONDataCategory `json:"category,omitempty"` + EnterpriseFeatures *PluginMetaEnterpriseFeatures `json:"enterpriseFeatures,omitempty"` + Executable *string `json:"executable,omitempty"` + HideFromList *bool `json:"hideFromList,omitempty"` + // +listType=atomic + Includes []PluginMetaInclude `json:"includes,omitempty"` + Logs *bool `json:"logs,omitempty"` + Metrics *bool `json:"metrics,omitempty"` + MultiValueFilterOperators *bool `json:"multiValueFilterOperators,omitempty"` + PascalName *string `json:"pascalName,omitempty"` + Preload *bool `json:"preload,omitempty"` + QueryOptions *PluginMetaQueryOptions `json:"queryOptions,omitempty"` + // +listType=atomic + Routes []PluginMetaRoute `json:"routes,omitempty"` + SkipDataQuery *bool `json:"skipDataQuery,omitempty"` + State *PluginMetaJSONDataState `json:"state,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + Tracing *bool `json:"tracing,omitempty"` + Iam *PluginMetaIAM `json:"iam,omitempty"` + // +listType=atomic + Roles []PluginMetaRole `json:"roles,omitempty"` + Extensions *PluginMetaExtensions `json:"extensions,omitempty"` +} + +// NewPluginMetaJSONData creates a new PluginMetaJSONData object. +func NewPluginMetaJSONData() *PluginMetaJSONData { + return &PluginMetaJSONData{ + Info: *NewPluginMetaInfo(), + Dependencies: *NewPluginMetaDependencies(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaInfo struct { + // Required fields + // +listType=set + Keywords []string `json:"keywords"` + Logos PluginMetaV0alpha1InfoLogos `json:"logos"` + Updated time.Time `json:"updated"` + Version string `json:"version"` + // Optional fields + Author *PluginMetaV0alpha1InfoAuthor `json:"author,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Links []PluginMetaV0alpha1InfoLinks `json:"links,omitempty"` + // +listType=atomic + Screenshots []PluginMetaV0alpha1InfoScreenshots `json:"screenshots,omitempty"` +} + +// NewPluginMetaInfo creates a new PluginMetaInfo object. +func NewPluginMetaInfo() *PluginMetaInfo { + return &PluginMetaInfo{ + Keywords: []string{}, + Logos: *NewPluginMetaV0alpha1InfoLogos(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaDependencies struct { + // Required field + GrafanaDependency string `json:"grafanaDependency"` + // Optional fields + GrafanaVersion *string `json:"grafanaVersion,omitempty"` + // +listType=set + // +listMapKey=id + Plugins []PluginMetaV0alpha1DependenciesPlugins `json:"plugins,omitempty"` + Extensions *PluginMetaV0alpha1DependenciesExtensions `json:"extensions,omitempty"` +} + +// NewPluginMetaDependencies creates a new PluginMetaDependencies object. +func NewPluginMetaDependencies() *PluginMetaDependencies { + return &PluginMetaDependencies{} +} + +// +k8s:openapi-gen=true +type PluginMetaEnterpriseFeatures struct { + // Allow additional properties + HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` +} + +// NewPluginMetaEnterpriseFeatures creates a new PluginMetaEnterpriseFeatures object. +func NewPluginMetaEnterpriseFeatures() *PluginMetaEnterpriseFeatures { + return &PluginMetaEnterpriseFeatures{ + HealthDiagnosticsErrors: (func(input bool) *bool { return &input })(false), + } +} + +// +k8s:openapi-gen=true +type PluginMetaInclude struct { + Uid *string `json:"uid,omitempty"` + Type *PluginMetaIncludeType `json:"type,omitempty"` + Name *string `json:"name,omitempty"` + Component *string `json:"component,omitempty"` + Role *PluginMetaIncludeRole `json:"role,omitempty"` + Action *string `json:"action,omitempty"` + Path *string `json:"path,omitempty"` + AddToNav *bool `json:"addToNav,omitempty"` + DefaultNav *bool `json:"defaultNav,omitempty"` + Icon *string `json:"icon,omitempty"` +} + +// NewPluginMetaInclude creates a new PluginMetaInclude object. +func NewPluginMetaInclude() *PluginMetaInclude { + return &PluginMetaInclude{} +} + +// +k8s:openapi-gen=true +type PluginMetaQueryOptions struct { + MaxDataPoints *bool `json:"maxDataPoints,omitempty"` + MinInterval *bool `json:"minInterval,omitempty"` + CacheTimeout *bool `json:"cacheTimeout,omitempty"` +} + +// NewPluginMetaQueryOptions creates a new PluginMetaQueryOptions object. +func NewPluginMetaQueryOptions() *PluginMetaQueryOptions { + return &PluginMetaQueryOptions{} +} + +// +k8s:openapi-gen=true +type PluginMetaRoute struct { + Path *string `json:"path,omitempty"` + Method *string `json:"method,omitempty"` + Url *string `json:"url,omitempty"` + ReqSignedIn *bool `json:"reqSignedIn,omitempty"` + ReqRole *string `json:"reqRole,omitempty"` + ReqAction *string `json:"reqAction,omitempty"` + // +listType=atomic + Headers []string `json:"headers,omitempty"` + Body map[string]interface{} `json:"body,omitempty"` + TokenAuth *PluginMetaV0alpha1RouteTokenAuth `json:"tokenAuth,omitempty"` + JwtTokenAuth *PluginMetaV0alpha1RouteJwtTokenAuth `json:"jwtTokenAuth,omitempty"` + // +listType=atomic + UrlParams []PluginMetaV0alpha1RouteUrlParams `json:"urlParams,omitempty"` +} + +// NewPluginMetaRoute creates a new PluginMetaRoute object. +func NewPluginMetaRoute() *PluginMetaRoute { + return &PluginMetaRoute{} +} + +// +k8s:openapi-gen=true +type PluginMetaIAM struct { + // +listType=atomic + Permissions []PluginMetaV0alpha1IAMPermissions `json:"permissions,omitempty"` +} + +// NewPluginMetaIAM creates a new PluginMetaIAM object. +func NewPluginMetaIAM() *PluginMetaIAM { + return &PluginMetaIAM{} +} + +// +k8s:openapi-gen=true +type PluginMetaRole struct { + Role *PluginMetaV0alpha1RoleRole `json:"role,omitempty"` + // +listType=set + Grants []string `json:"grants,omitempty"` +} + +// NewPluginMetaRole creates a new PluginMetaRole object. +func NewPluginMetaRole() *PluginMetaRole { + return &PluginMetaRole{} +} + +// +k8s:openapi-gen=true +type PluginMetaExtensions struct { + // +listType=atomic + AddedComponents []PluginMetaV0alpha1ExtensionsAddedComponents `json:"addedComponents,omitempty"` + // +listType=atomic + AddedLinks []PluginMetaV0alpha1ExtensionsAddedLinks `json:"addedLinks,omitempty"` + // +listType=set + // +listMapKey=id + ExposedComponents []PluginMetaV0alpha1ExtensionsExposedComponents `json:"exposedComponents,omitempty"` + // +listType=set + // +listMapKey=id + ExtensionPoints []PluginMetaV0alpha1ExtensionsExtensionPoints `json:"extensionPoints,omitempty"` +} + +// NewPluginMetaExtensions creates a new PluginMetaExtensions object. +func NewPluginMetaExtensions() *PluginMetaExtensions { + return &PluginMetaExtensions{} +} + +// +k8s:openapi-gen=true +type PluginMetaSpec struct { + PluginJSON PluginMetaJSONData `json:"pluginJSON"` +} + +// NewPluginMetaSpec creates a new PluginMetaSpec object. +func NewPluginMetaSpec() *PluginMetaSpec { + return &PluginMetaSpec{ + PluginJSON: *NewPluginMetaJSONData(), + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoLogos struct { + Small string `json:"small"` + Large string `json:"large"` +} + +// NewPluginMetaV0alpha1InfoLogos creates a new PluginMetaV0alpha1InfoLogos object. +func NewPluginMetaV0alpha1InfoLogos() *PluginMetaV0alpha1InfoLogos { + return &PluginMetaV0alpha1InfoLogos{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoAuthor struct { + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewPluginMetaV0alpha1InfoAuthor creates a new PluginMetaV0alpha1InfoAuthor object. +func NewPluginMetaV0alpha1InfoAuthor() *PluginMetaV0alpha1InfoAuthor { + return &PluginMetaV0alpha1InfoAuthor{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoLinks struct { + Name *string `json:"name,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewPluginMetaV0alpha1InfoLinks creates a new PluginMetaV0alpha1InfoLinks object. +func NewPluginMetaV0alpha1InfoLinks() *PluginMetaV0alpha1InfoLinks { + return &PluginMetaV0alpha1InfoLinks{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1InfoScreenshots struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` +} + +// NewPluginMetaV0alpha1InfoScreenshots creates a new PluginMetaV0alpha1InfoScreenshots object. +func NewPluginMetaV0alpha1InfoScreenshots() *PluginMetaV0alpha1InfoScreenshots { + return &PluginMetaV0alpha1InfoScreenshots{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesPlugins struct { + Id string `json:"id"` + Type PluginMetaV0alpha1DependenciesPluginsType `json:"type"` + Name string `json:"name"` +} + +// NewPluginMetaV0alpha1DependenciesPlugins creates a new PluginMetaV0alpha1DependenciesPlugins object. +func NewPluginMetaV0alpha1DependenciesPlugins() *PluginMetaV0alpha1DependenciesPlugins { + return &PluginMetaV0alpha1DependenciesPlugins{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesExtensions struct { + // +listType=set + ExposedComponents []string `json:"exposedComponents,omitempty"` +} + +// NewPluginMetaV0alpha1DependenciesExtensions creates a new PluginMetaV0alpha1DependenciesExtensions object. +func NewPluginMetaV0alpha1DependenciesExtensions() *PluginMetaV0alpha1DependenciesExtensions { + return &PluginMetaV0alpha1DependenciesExtensions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewPluginMetaV0alpha1RouteTokenAuth creates a new PluginMetaV0alpha1RouteTokenAuth object. +func NewPluginMetaV0alpha1RouteTokenAuth() *PluginMetaV0alpha1RouteTokenAuth { + return &PluginMetaV0alpha1RouteTokenAuth{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteJwtTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewPluginMetaV0alpha1RouteJwtTokenAuth creates a new PluginMetaV0alpha1RouteJwtTokenAuth object. +func NewPluginMetaV0alpha1RouteJwtTokenAuth() *PluginMetaV0alpha1RouteJwtTokenAuth { + return &PluginMetaV0alpha1RouteJwtTokenAuth{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RouteUrlParams struct { + Name *string `json:"name,omitempty"` + Content *string `json:"content,omitempty"` +} + +// NewPluginMetaV0alpha1RouteUrlParams creates a new PluginMetaV0alpha1RouteUrlParams object. +func NewPluginMetaV0alpha1RouteUrlParams() *PluginMetaV0alpha1RouteUrlParams { + return &PluginMetaV0alpha1RouteUrlParams{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1IAMPermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewPluginMetaV0alpha1IAMPermissions creates a new PluginMetaV0alpha1IAMPermissions object. +func NewPluginMetaV0alpha1IAMPermissions() *PluginMetaV0alpha1IAMPermissions { + return &PluginMetaV0alpha1IAMPermissions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RoleRolePermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewPluginMetaV0alpha1RoleRolePermissions creates a new PluginMetaV0alpha1RoleRolePermissions object. +func NewPluginMetaV0alpha1RoleRolePermissions() *PluginMetaV0alpha1RoleRolePermissions { + return &PluginMetaV0alpha1RoleRolePermissions{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1RoleRole struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Permissions []PluginMetaV0alpha1RoleRolePermissions `json:"permissions,omitempty"` +} + +// NewPluginMetaV0alpha1RoleRole creates a new PluginMetaV0alpha1RoleRole object. +func NewPluginMetaV0alpha1RoleRole() *PluginMetaV0alpha1RoleRole { + return &PluginMetaV0alpha1RoleRole{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsAddedComponents struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsAddedComponents creates a new PluginMetaV0alpha1ExtensionsAddedComponents object. +func NewPluginMetaV0alpha1ExtensionsAddedComponents() *PluginMetaV0alpha1ExtensionsAddedComponents { + return &PluginMetaV0alpha1ExtensionsAddedComponents{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsAddedLinks struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsAddedLinks creates a new PluginMetaV0alpha1ExtensionsAddedLinks object. +func NewPluginMetaV0alpha1ExtensionsAddedLinks() *PluginMetaV0alpha1ExtensionsAddedLinks { + return &PluginMetaV0alpha1ExtensionsAddedLinks{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsExposedComponents struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsExposedComponents creates a new PluginMetaV0alpha1ExtensionsExposedComponents object. +func NewPluginMetaV0alpha1ExtensionsExposedComponents() *PluginMetaV0alpha1ExtensionsExposedComponents { + return &PluginMetaV0alpha1ExtensionsExposedComponents{} +} + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1ExtensionsExtensionPoints struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewPluginMetaV0alpha1ExtensionsExtensionPoints creates a new PluginMetaV0alpha1ExtensionsExtensionPoints object. +func NewPluginMetaV0alpha1ExtensionsExtensionPoints() *PluginMetaV0alpha1ExtensionsExtensionPoints { + return &PluginMetaV0alpha1ExtensionsExtensionPoints{} +} + +// +k8s:openapi-gen=true +type PluginMetaJSONDataType string + +const ( + PluginMetaJSONDataTypeApp PluginMetaJSONDataType = "app" + PluginMetaJSONDataTypeDatasource PluginMetaJSONDataType = "datasource" + PluginMetaJSONDataTypePanel PluginMetaJSONDataType = "panel" + PluginMetaJSONDataTypeRenderer PluginMetaJSONDataType = "renderer" +) + +// +k8s:openapi-gen=true +type PluginMetaJSONDataCategory string + +const ( + PluginMetaJSONDataCategoryTsdb PluginMetaJSONDataCategory = "tsdb" + PluginMetaJSONDataCategoryLogging PluginMetaJSONDataCategory = "logging" + PluginMetaJSONDataCategoryCloud PluginMetaJSONDataCategory = "cloud" + PluginMetaJSONDataCategoryTracing PluginMetaJSONDataCategory = "tracing" + PluginMetaJSONDataCategoryProfiling PluginMetaJSONDataCategory = "profiling" + PluginMetaJSONDataCategorySql PluginMetaJSONDataCategory = "sql" + PluginMetaJSONDataCategoryEnterprise PluginMetaJSONDataCategory = "enterprise" + PluginMetaJSONDataCategoryIot PluginMetaJSONDataCategory = "iot" + PluginMetaJSONDataCategoryOther PluginMetaJSONDataCategory = "other" +) + +// +k8s:openapi-gen=true +type PluginMetaJSONDataState string + +const ( + PluginMetaJSONDataStateAlpha PluginMetaJSONDataState = "alpha" + PluginMetaJSONDataStateBeta PluginMetaJSONDataState = "beta" +) + +// +k8s:openapi-gen=true +type PluginMetaIncludeType string + +const ( + PluginMetaIncludeTypeDashboard PluginMetaIncludeType = "dashboard" + PluginMetaIncludeTypePage PluginMetaIncludeType = "page" + PluginMetaIncludeTypePanel PluginMetaIncludeType = "panel" + PluginMetaIncludeTypeDatasource PluginMetaIncludeType = "datasource" +) + +// +k8s:openapi-gen=true +type PluginMetaIncludeRole string + +const ( + PluginMetaIncludeRoleAdmin PluginMetaIncludeRole = "Admin" + PluginMetaIncludeRoleEditor PluginMetaIncludeRole = "Editor" + PluginMetaIncludeRoleViewer PluginMetaIncludeRole = "Viewer" +) + +// +k8s:openapi-gen=true +type PluginMetaV0alpha1DependenciesPluginsType string + +const ( + PluginMetaV0alpha1DependenciesPluginsTypeApp PluginMetaV0alpha1DependenciesPluginsType = "app" + PluginMetaV0alpha1DependenciesPluginsTypeDatasource PluginMetaV0alpha1DependenciesPluginsType = "datasource" + PluginMetaV0alpha1DependenciesPluginsTypePanel PluginMetaV0alpha1DependenciesPluginsType = "panel" +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go new file mode 100644 index 00000000000..60fa37dbb32 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginMetastatusOperatorState struct { + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State PluginMetaStatusOperatorStateState `json:"state"` + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` +} + +// NewPluginMetastatusOperatorState creates a new PluginMetastatusOperatorState object. +func NewPluginMetastatusOperatorState() *PluginMetastatusOperatorState { + return &PluginMetastatusOperatorState{} +} + +// +k8s:openapi-gen=true +type PluginMetaStatus struct { + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]PluginMetastatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewPluginMetaStatus creates a new PluginMetaStatus object. +func NewPluginMetaStatus() *PluginMetaStatus { + return &PluginMetaStatus{} +} + +// +k8s:openapi-gen=true +type PluginMetaStatusOperatorStateState string + +const ( + PluginMetaStatusOperatorStateStateSuccess PluginMetaStatusOperatorStateState = "success" + PluginMetaStatusOperatorStateStateInProgress PluginMetaStatusOperatorStateState = "in_progress" + PluginMetaStatusOperatorStateStateFailed PluginMetaStatusOperatorStateState = "failed" +) diff --git a/apps/plugins/pkg/apis/plugins_manifest.go b/apps/plugins/pkg/apis/plugins_manifest.go new file mode 100644 index 00000000000..2449dc9a726 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins_manifest.go @@ -0,0 +1,87 @@ +// +// This file is generated by grafana-app-sdk +// DO NOT EDIT +// + +package apis + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + + v0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +var ( + rawSchemaPluginMetav0alpha1 = []byte(`{"spec":{"properties":{"pluginJSON":{"properties":{"alerting":{"description":"Optional fields","type":"boolean"},"annotations":{"type":"boolean"},"autoEnabled":{"type":"boolean"},"backend":{"type":"boolean"},"buildMode":{"type":"string"},"builtIn":{"type":"boolean"},"category":{"enum":["tsdb","logging","cloud","tracing","profiling","sql","enterprise","iot","other"],"type":"string"},"dependencies":{"description":"Dependency information","properties":{"extensions":{"properties":{"exposedComponents":{"description":"+listType=set","items":{"type":"string"},"type":"array"}},"type":"object"},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","items":{"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"enum":["app","datasource","panel"],"type":"string"}},"required":["id","type","name"],"type":"object"},"type":"array"}},"required":["grafanaDependency"],"type":"object"},"enterpriseFeatures":{"additionalProperties":{"description":"Allow additional properties"},"properties":{"healthDiagnosticsErrors":{"default":false,"description":"Allow additional properties","type":"boolean"}},"type":"object"},"executable":{"type":"string"},"extensions":{"properties":{"addedComponents":{"description":"+listType=atomic","items":{"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedLinks":{"description":"+listType=atomic","items":{"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","items":{"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","items":{"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"}},"type":"object"},"hideFromList":{"type":"boolean"},"iam":{"properties":{"permissions":{"description":"+listType=atomic","items":{"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"id":{"description":"Unique name of the plugin","type":"string"},"includes":{"description":"+listType=atomic","items":{"properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"enum":["Admin","Editor","Viewer"],"type":"string"},"type":{"enum":["dashboard","page","panel","datasource"],"type":"string"},"uid":{"type":"string"}},"type":"object"},"type":"array"},"info":{"description":"Metadata for the plugin","properties":{"author":{"description":"Optional fields","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","items":{"type":"string"},"type":"array"},"links":{"description":"+listType=atomic","items":{"properties":{"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"logos":{"properties":{"large":{"type":"string"},"small":{"type":"string"}},"required":["small","large"],"type":"object"},"screenshots":{"description":"+listType=atomic","items":{"properties":{"name":{"type":"string"},"path":{"type":"string"}},"type":"object"},"type":"array"},"updated":{"format":"date-time","type":"string"},"version":{"type":"string"}},"required":["keywords","logos","updated","version"],"type":"object"},"logs":{"type":"boolean"},"metrics":{"type":"boolean"},"multiValueFilterOperators":{"type":"boolean"},"name":{"description":"Human-readable name of the plugin","type":"string"},"pascalName":{"type":"string"},"preload":{"type":"boolean"},"queryOptions":{"properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"type":"object"},"roles":{"description":"+listType=atomic","items":{"properties":{"grants":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"role":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","items":{"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"},"type":"array"},"routes":{"description":"+listType=atomic","items":{"properties":{"body":{"type":"object","x-kubernetes-preserve-unknown-fields":true},"headers":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"jwtTokenAuth":{"properties":{"params":{"type":"object","x-kubernetes-preserve-unknown-fields":true},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"properties":{"params":{"type":"object","x-kubernetes-preserve-unknown-fields":true},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","items":{"properties":{"content":{"type":"string"},"name":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"type":"array"},"skipDataQuery":{"type":"boolean"},"state":{"enum":["alpha","beta"],"type":"string"},"streaming":{"type":"boolean"},"tracing":{"type":"boolean"},"type":{"description":"Plugin type","enum":["app","datasource","panel","renderer"],"type":"string"}},"required":["id","type","name","info","dependencies"],"type":"object"}},"required":["pluginJSON"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPluginMetav0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPluginMetav0alpha1, &versionSchemaPluginMetav0alpha1) + rawSchemaPluginInstallv0alpha1 = []byte(`{"spec":{"properties":{"id":{"type":"string"},"version":{"type":"string"}},"required":["id","version"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPluginInstallv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPluginInstallv0alpha1, &versionSchemaPluginInstallv0alpha1) +) + +var appManifestData = app.ManifestData{ + AppName: "plugins", + Group: "plugins.grafana.app", + Versions: []app.ManifestVersion{ + { + Name: "v0alpha1", + Served: false, + Kinds: []app.ManifestVersionKind{ + { + Kind: "PluginMeta", + Plural: "PluginMetas", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaPluginMetav0alpha1, + }, + + { + Kind: "PluginInstall", + Plural: "PluginInstalls", + Scope: "Namespaced", + Conversion: false, + Schema: &versionSchemaPluginInstallv0alpha1, + }, + }, + }, + }, +} + +func LocalManifest() app.Manifest { + return app.NewEmbeddedManifest(appManifestData) +} + +func RemoteManifest() app.Manifest { + return app.NewAPIServerManifest("plugins") +} + +var kindVersionToGoType = map[string]resource.Kind{ + "PluginMeta/v0alpha1": v0alpha1.PluginMetaKind(), + "PluginInstall/v0alpha1": v0alpha1.PluginInstallKind(), +} + +// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. +// If there is no association for the provided Kind and Version, exists will return false. +func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) { + goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] + return goType, exists +} + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} diff --git a/apps/plugins/pkg/app/app.go b/apps/plugins/pkg/app/app.go new file mode 100644 index 00000000000..99fc20a8d2c --- /dev/null +++ b/apps/plugins/pkg/app/app.go @@ -0,0 +1,65 @@ +package app + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + "github.com/grafana/grafana-app-sdk/simple" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" + + pluginsapi "github.com/grafana/grafana/apps/plugins/pkg/apis" +) + +func New(cfg app.Config) (app.App, error) { + managedKinds := []simple.AppManagedKind{} + for _, kinds := range GetKinds() { + for _, k := range kinds { + managedKinds = append(managedKinds, simple.AppManagedKind{ + Kind: k, + }) + } + } + + simpleConfig := simple.AppConfig{ + Name: "plugins", + KubeConfig: cfg.KubeConfig, + InformerConfig: simple.AppInformerConfig{ + ErrorHandler: func(ctx context.Context, err error) { + klog.ErrorS(err, "Informer processing error") + }, + }, + ManagedKinds: managedKinds, + } + + a, err := simple.NewApp(simpleConfig) + if err != nil { + return nil, err + } + + err = a.ValidateManifest(cfg.ManifestData) + if err != nil { + return nil, err + } + + return a, nil +} + +func GetKinds() map[schema.GroupVersion][]resource.Kind { + kinds := make(map[schema.GroupVersion][]resource.Kind) + manifest := pluginsapi.LocalManifest() + for _, v := range manifest.ManifestData.Versions { + gv := schema.GroupVersion{ + Group: manifest.ManifestData.Group, + Version: v.Name, + } + for _, k := range v.Kinds { + kind, ok := pluginsapi.ManifestGoTypeAssociator(k.Kind, v.Name) + if ok { + kinds[gv] = append(kinds[gv], kind) + } + } + } + return kinds +} diff --git a/apps/plugins/pkg/app/authorizer.go b/apps/plugins/pkg/app/authorizer.go new file mode 100644 index 00000000000..b6a800694f7 --- /dev/null +++ b/apps/plugins/pkg/app/authorizer.go @@ -0,0 +1,32 @@ +package app + +import ( + "context" + + "k8s.io/apiserver/pkg/authorization/authorizer" + + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +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 + } + + // require a user + 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 + }) +} diff --git a/apps/plugins/pkg/app/storage.go b/apps/plugins/pkg/app/storage.go new file mode 100644 index 00000000000..9fca277abfc --- /dev/null +++ b/apps/plugins/pkg/app/storage.go @@ -0,0 +1,75 @@ +package app + +import ( + "context" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" + + claims "github.com/grafana/authlib/types" + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" +) + +var ( + _ rest.Scoper = (*PluginMetaStorage)(nil) + _ rest.SingularNameProvider = (*PluginMetaStorage)(nil) + _ rest.Getter = (*PluginMetaStorage)(nil) + _ rest.Lister = (*PluginMetaStorage)(nil) + _ rest.Storage = (*PluginMetaStorage)(nil) + _ rest.TableConvertor = (*PluginMetaStorage)(nil) +) + +type PluginMetaStorage struct { + gr schema.GroupResource + namespacer claims.NamespaceFormatter + tableConverter rest.TableConvertor +} + +func NewPluginMetaStorage( + namespacer claims.NamespaceFormatter, +) *PluginMetaStorage { + gr := schema.GroupResource{ + Group: pluginsv0alpha1.PluginMetaKind().Group(), + Resource: strings.ToLower(pluginsv0alpha1.PluginMetaKind().Plural()), + } + return &PluginMetaStorage{ + gr: gr, + namespacer: namespacer, + tableConverter: rest.NewDefaultTableConvertor(gr), + } +} + +func (s *PluginMetaStorage) New() runtime.Object { + return pluginsv0alpha1.PluginMetaKind().ZeroValue() +} + +func (s *PluginMetaStorage) Destroy() {} + +func (s *PluginMetaStorage) NamespaceScoped() bool { + return true +} + +func (s *PluginMetaStorage) GetSingularName() string { + return strings.ToLower(pluginsv0alpha1.PluginMetaKind().Kind()) +} + +func (s *PluginMetaStorage) NewList() runtime.Object { + return pluginsv0alpha1.PluginMetaKind().ZeroListValue() +} + +func (s *PluginMetaStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +} + +func (s *PluginMetaStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + return s.NewList(), nil +} + +func (s *PluginMetaStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return nil, apierrors.NewNotFound(s.gr, name) +} diff --git a/go.work b/go.work index 51f59db084a..50347ae04ba 100644 --- a/go.work +++ b/go.work @@ -12,6 +12,7 @@ use ( ./apps/iam ./apps/investigations ./apps/playlist + ./apps/plugins ./apps/provisioning ./apps/secret ./apps/shorturl diff --git a/go.work.sum b/go.work.sum index 7471f2149c3..34c0540f8ce 100644 --- a/go.work.sum +++ b/go.work.sum @@ -553,6 +553,7 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbO github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE= github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.6 h1:UucmvNRPE75F3KzT68GHhKzOPwttxiFkh1d5LTTywW8= github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.6/go.mod h1:XGripOBEUAcge8IUWR/NMAB5qO9k82tkbpoewBpyjYQ= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= @@ -969,12 +970,12 @@ github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/ github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= -github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= +github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4= github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= -github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0= github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss= @@ -1661,7 +1662,6 @@ golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0Y golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= @@ -1721,14 +1721,12 @@ golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFK golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -1822,7 +1820,6 @@ google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI= @@ -1850,7 +1847,6 @@ honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34= honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= k8s.io/code-generator v0.33.1 h1:ZLzIRdMsh3Myfnx9BaooX6iQry29UJjVfVG+BuS+UMw= k8s.io/code-generator v0.33.1/go.mod h1:HUKT7Ubp6bOgIbbaPIs9lpd2Q02uqkMCMx9/GjDrWpY= k8s.io/code-generator v0.33.2 h1:PCJ0Y6viTCxxJHMOyGqYwWEteM4q6y1Hqo2rNpl6jF4= @@ -1887,4 +1883,3 @@ sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2Lfw sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index 62aec11ad97..3d2d4144557 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apps/alerting/notifications" "github.com/grafana/grafana/pkg/registry/apps/investigations" "github.com/grafana/grafana/pkg/registry/apps/playlist" + "github.com/grafana/grafana/pkg/registry/apps/plugins" "github.com/grafana/grafana/pkg/registry/apps/shorturl" "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/apiserver/builder" @@ -27,9 +28,13 @@ import ( func ProvideAppInstallers( features featuremgmt.FeatureToggles, playlistAppInstaller *playlist.PlaylistAppInstaller, + pluginsApplInstaller *plugins.PluginsAppInstaller, shorturlAppInstaller *shorturl.ShortURLAppInstaller, ) []appsdkapiserver.AppInstaller { - installers := []appsdkapiserver.AppInstaller{playlistAppInstaller} + installers := []appsdkapiserver.AppInstaller{ + playlistAppInstaller, + pluginsApplInstaller, + } if features.IsEnabledGlobally(featuremgmt.FlagKubernetesShortURLs) { installers = append(installers, shorturlAppInstaller) } diff --git a/pkg/registry/apps/plugins/register.go b/pkg/registry/apps/plugins/register.go new file mode 100644 index 00000000000..ae2f9c871f6 --- /dev/null +++ b/pkg/registry/apps/plugins/register.go @@ -0,0 +1,76 @@ +package plugins + +import ( + "github.com/grafana/grafana-app-sdk/app" + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "github.com/grafana/grafana-app-sdk/simple" + "github.com/grafana/grafana/apps/plugins/pkg/apis" + "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + restclient "k8s.io/client-go/rest" + + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" + pluginsapp "github.com/grafana/grafana/apps/plugins/pkg/app" +) + +var ( + _ appsdkapiserver.AppInstaller = (*PluginsAppInstaller)(nil) + _ appinstaller.AuthorizerProvider = (*PluginsAppInstaller)(nil) +) + +type PluginsAppInstaller struct { + appsdkapiserver.AppInstaller + cfg *setting.Cfg +} + +func RegisterAppInstaller( + cfg *setting.Cfg, + features featuremgmt.FeatureToggles, +) (*PluginsAppInstaller, error) { + installer := &PluginsAppInstaller{ + cfg: cfg, + } + specificConfig := any(nil) + provider := simple.NewAppProvider(apis.LocalManifest(), specificConfig, pluginsapp.New) + appConfig := app.Config{ + KubeConfig: restclient.Config{}, // this will be overridden by the installer's InitializeApp method + ManifestData: *apis.LocalManifest().ManifestData, + SpecificConfig: specificConfig, + } + i, err := appsdkapiserver.NewDefaultAppInstaller(provider, appConfig, apis.ManifestGoTypeAssociator, apis.ManifestCustomRouteResponsesAssociator) + if err != nil { + return nil, err + } + installer.AppInstaller = i + return installer, nil +} + +func (p *PluginsAppInstaller) InstallAPIs( + server appsdkapiserver.GenericAPIServer, + restOptsGetter generic.RESTOptionsGetter, +) error { + pluginMetaGVR := schema.GroupVersionResource{ + Group: pluginsv0alpha1.GroupVersion.Group, + Version: pluginsv0alpha1.GroupVersion.Version, + Resource: pluginsv0alpha1.PluginMetaKind().Plural(), + } + replacedStorage := map[schema.GroupVersionResource]rest.Storage{ + pluginMetaGVR: pluginsapp.NewPluginMetaStorage(request.GetNamespaceMapper(p.cfg)), + } + wrappedServer := &customStorageWrapper{ + wrapped: server, + replace: replacedStorage, + } + return p.AppInstaller.InstallAPIs(wrappedServer, restOptsGetter) +} + +// GetAuthorizer returns the authorizer for the plugins app. +func (p *PluginsAppInstaller) GetAuthorizer() authorizer.Authorizer { + return pluginsapp.GetAuthorizer() +} diff --git a/pkg/registry/apps/plugins/storage.go b/pkg/registry/apps/plugins/storage.go new file mode 100644 index 00000000000..b7092053fd0 --- /dev/null +++ b/pkg/registry/apps/plugins/storage.go @@ -0,0 +1,32 @@ +package plugins + +import ( + "fmt" + + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" + genericserver "k8s.io/apiserver/pkg/server" +) + +var _ appsdkapiserver.GenericAPIServer = (*customStorageWrapper)(nil) + +type customStorageWrapper struct { + wrapped appsdkapiserver.GenericAPIServer + replace map[schema.GroupVersionResource]rest.Storage +} + +func (c *customStorageWrapper) InstallAPIGroup( + apiGroupInfo *genericserver.APIGroupInfo, +) error { + if apiGroupInfo == nil || apiGroupInfo.VersionedResourcesStorageMap == nil { + return fmt.Errorf("apiGroupInfo cannot be nil") + } + for gvr, storage := range c.replace { + if _, ok := apiGroupInfo.VersionedResourcesStorageMap[gvr.Version]; !ok { + apiGroupInfo.VersionedResourcesStorageMap[gvr.Version] = map[string]rest.Storage{} + } + apiGroupInfo.VersionedResourcesStorageMap[gvr.Version][gvr.Resource] = storage + } + return c.wrapped.InstallAPIGroup(apiGroupInfo) +} diff --git a/pkg/registry/apps/wireset.go b/pkg/registry/apps/wireset.go index b7ff8837665..ca134eeaf64 100644 --- a/pkg/registry/apps/wireset.go +++ b/pkg/registry/apps/wireset.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apps/alerting/notifications" "github.com/grafana/grafana/pkg/registry/apps/investigations" "github.com/grafana/grafana/pkg/registry/apps/playlist" + "github.com/grafana/grafana/pkg/registry/apps/plugins" "github.com/grafana/grafana/pkg/registry/apps/shorturl" ) @@ -17,5 +18,6 @@ var WireSet = wire.NewSet( investigations.RegisterApp, advisor.RegisterApp, notifications.RegisterApp, + plugins.RegisterAppInstaller, shorturl.RegisterAppInstaller, ) diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index d1d7b99f4af..6ef87327da4 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -77,6 +77,7 @@ import ( notifications2 "github.com/grafana/grafana/pkg/registry/apps/alerting/notifications" "github.com/grafana/grafana/pkg/registry/apps/investigations" "github.com/grafana/grafana/pkg/registry/apps/playlist" + "github.com/grafana/grafana/pkg/registry/apps/plugins" "github.com/grafana/grafana/pkg/registry/apps/shorturl" "github.com/grafana/grafana/pkg/registry/backgroundsvcs" "github.com/grafana/grafana/pkg/registry/usagestatssvcs" @@ -690,11 +691,15 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser if err != nil { return nil, err } + pluginsAppInstaller, err := plugins.RegisterAppInstaller(cfg, featureToggles) + if err != nil { + return nil, err + } shortURLAppInstaller, err := shorturl.RegisterAppInstaller(cfg, shortURLService) if err != nil { return nil, err } - v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, shortURLAppInstaller) + v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, pluginsAppInstaller, shortURLAppInstaller) builderMetrics := builder.ProvideBuilderMetrics(registerer) apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) if err != nil { @@ -1255,11 +1260,15 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { if err != nil { return nil, err } + pluginsAppInstaller, err := plugins.RegisterAppInstaller(cfg, featureToggles) + if err != nil { + return nil, err + } shortURLAppInstaller, err := shorturl.RegisterAppInstaller(cfg, shortURLService) if err != nil { return nil, err } - v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, shortURLAppInstaller) + v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, pluginsAppInstaller, shortURLAppInstaller) builderMetrics := builder.ProvideBuilderMetrics(registerer) apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) if err != nil { diff --git a/pkg/tests/apis/plugins/discovery_test.go b/pkg/tests/apis/plugins/discovery_test.go new file mode 100644 index 00000000000..6df8501f4c0 --- /dev/null +++ b/pkg/tests/apis/plugins/discovery_test.go @@ -0,0 +1,90 @@ +package plugins + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPluginsIntegrationDiscovery(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + t.Run("discovery", func(t *testing.T) { + helper := setupHelper(t) + disco := helper.GetGroupVersionInfoJSON("plugins.grafana.app") + require.JSONEq(t, `[ + { + "version": "v0alpha1", + "freshness": "Current", + "resources": [ + { + "resource": "plugininstalls", + "responseKind": { + "group": "", + "kind": "PluginInstall", + "version": "" + }, + "scope": "Namespaced", + "singularResource": "plugininstalls", + "subresources": [ + { + "responseKind": { + "group": "", + "kind": "PluginInstall", + "version": "" + }, + "subresource": "status", + "verbs": [ + "get", + "patch", + "update" + ] + } + ], + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + }, + { + "resource": "pluginmetas", + "responseKind": { + "group": "", + "kind": "PluginMeta", + "version": "" + }, + "scope": "Namespaced", + "singularResource": "pluginmeta", + "subresources": [ + { + "responseKind": { + "group": "", + "kind": "PluginMeta", + "version": "" + }, + "subresource": "status", + "verbs": [ + "get", + "patch", + "update" + ] + } + ], + "verbs": [ + "get", + "list" + ] + } + ] + } + ]`, disco) + }) +} diff --git a/pkg/tests/apis/plugins/plugininstalls_test.go b/pkg/tests/apis/plugins/plugininstalls_test.go new file mode 100644 index 00000000000..00bdeec8666 --- /dev/null +++ b/pkg/tests/apis/plugins/plugininstalls_test.go @@ -0,0 +1,313 @@ +package plugins + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/grafana/grafana/pkg/tests/apis" + "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/grafana/grafana/pkg/tests/testsuite" +) + +var gvrPluginInstalls = schema.GroupVersionResource{ + Group: "plugins.grafana.app", + Version: "v0alpha1", + Resource: "plugininstalls", +} + +func TestMain(m *testing.M) { + testsuite.Run(m) +} + +func TestIntegrationPluginInstalls(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + t.Run("create plugin install", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginInstalls, + }) + pluginName := "test-plugin-create" + pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "%s"}, + "spec": {"version": "1.0.0"} + }`, pluginName)) + created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + require.NoError(t, err) + require.NotNil(t, created) + require.Equal(t, pluginName, created.GetName()) + }) + + t.Run("create plugin install with status is ignored", func(t *testing.T) { + t.Skip("status is not ignored on create. this might require a change in the SDK. skipping for now") + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginInstalls, + }) + pluginName := "test-plugin-create-with-status" + pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "%s"}, + "spec": {"version": "1.0.0"}, + "status": { + "operatorStates": { + "test-operator": { + "lastEvaluation": "1", + "state": "success" + } + } + } + }`, pluginName)) + created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + require.NoError(t, err) + require.NotNil(t, created) + require.Equal(t, pluginName, created.GetName()) + // Status should be empty as it's ignored on create + status, found, err := unstructured.NestedMap(created.Object, "status") + require.NoError(t, err) + require.True(t, found) // status field should exist + require.Empty(t, status) // but it should be empty + }) + + t.Run("get plugin install", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginInstalls, + }) + pluginName := "test-plugin-get" + pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "%s"}, + "spec": {"version": "1.0.0"} + }`, pluginName)) + created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + require.NoError(t, err) + fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, fetched) + require.Equal(t, pluginName, fetched.GetName()) + require.Equal(t, created.Object, fetched.Object) + }) + + t.Run("update plugin install", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginInstalls, + }) + pluginName := "test-plugin-update" + pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "%s"}, + "spec": {"version": "1.0.0"} + }`, pluginName)) + created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + require.NoError(t, err) + updatedSpec := created.DeepCopy() + updatedSpec.Object["spec"] = map[string]interface{}{ + "version": "2.0.0", + } + updated, err := client.Resource.Update(ctx, updatedSpec, metav1.UpdateOptions{}) + require.NoError(t, err) + require.NotNil(t, updated) + require.Equal(t, "2.0.0", updated.Object["spec"].(map[string]interface{})["version"]) + }) + + t.Run("update plugin install with status is ignored", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginInstalls, + }) + pluginName := "test-plugin-update-with-status" + pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "%s"}, + "spec": {"version": "1.0.0"} + }`, pluginName)) + created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + require.NoError(t, err) + + // Try to update the status via a normal update + withStatus := created.DeepCopy() + withStatus.Object["status"] = map[string]interface{}{ + "operatorStates": map[string]interface{}{ + "test-operator": map[string]interface{}{ + "lastEvaluation": "1", + "state": "success", + }, + }, + } + updated, err := client.Resource.Update(ctx, withStatus, metav1.UpdateOptions{}) + require.NoError(t, err) + require.NotNil(t, updated) + + // The status should not have been updated + status, found, err := unstructured.NestedMap(updated.Object, "status") + require.NoError(t, err) + require.True(t, found) + require.Empty(t, status) + + // also check with get + fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, fetched) + status, found, err = unstructured.NestedMap(fetched.Object, "status") + require.NoError(t, err) + require.True(t, found) + require.Empty(t, status) + }) + + t.Run("update plugin install status", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginInstalls, + }) + pluginName := "test-plugin-status" + pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "%s"}, + "spec": {"version": "1.0.0"} + }`, pluginName)) + created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + require.NoError(t, err) + + // Update the status + status := created.DeepCopy() + statusPayload := map[string]interface{}{ + "operatorStates": map[string]interface{}{ + "test-operator": map[string]interface{}{ + "lastEvaluation": "1", + "state": "success", + }, + }, + } + status.Object["status"] = statusPayload + updated, err := client.Resource.UpdateStatus(ctx, status, metav1.UpdateOptions{}) + require.NoError(t, err) + require.NotNil(t, updated) + + // Check the status on the returned object + actualStatus, found, err := unstructured.NestedMap(updated.Object, "status") + require.NoError(t, err) + require.True(t, found) + require.Equal(t, statusPayload, actualStatus) + + // Get the status to ensure it persisted + fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, fetched) + actualStatus, found, err = unstructured.NestedMap(fetched.Object, "status") + require.NoError(t, err) + require.True(t, found) + require.Equal(t, statusPayload, actualStatus) + }) + + t.Run("list plugin installs", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginInstalls, + }) + pluginName := "test-plugin-list" + pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "%s"}, + "spec": {"version": "1.0.0"} + }`, pluginName)) + created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + require.NoError(t, err) + list, err := client.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + expectedItems := []unstructured.Unstructured{*created} + require.ElementsMatch(t, expectedItems, list.Items) + }) + + t.Run("delete plugin install", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginInstalls, + }) + pluginName := "test-plugin-delete" + pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "%s"}, + "spec": {"version": "1.0.0"} + }`, pluginName)) + _, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + require.NoError(t, err) + err = client.Resource.Delete(ctx, pluginName, metav1.DeleteOptions{}) + require.NoError(t, err) + _, err = client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) + statusError := helper.AsStatusError(err) + require.Equal(t, metav1.StatusReasonNotFound, statusError.Status().Reason) + }) + + t.Run("insufficient permissions", func(t *testing.T) { + helper := setupHelper(t) + for _, user := range []apis.User{ + helper.Org1.Editor, + helper.Org1.Viewer, + } { + t.Run(fmt.Sprintf("with basic role: %s", user.Identity.GetOrgRole()), func(t *testing.T) { + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: user, + GVR: gvrPluginInstalls, + }) + pluginInstall := helper.LoadYAMLOrJSON(`{ + "apiVersion": "plugins.grafana.app/v0alpha1", + "kind": "PluginInstall", + "metadata": {"name": "test-plugin"}, + "spec": {"version": "1.0.0"} + }`) + _, err := client.Resource.Create(context.Background(), pluginInstall, metav1.CreateOptions{}) + statusError := helper.AsStatusError(err) + require.Equal(t, metav1.StatusReasonForbidden, statusError.Status().Reason) + err = client.Resource.Delete(context.Background(), "test-plugin", metav1.DeleteOptions{}) + statusError = helper.AsStatusError(err) + require.Equal(t, metav1.StatusReasonForbidden, statusError.Status().Reason) + }) + } + }) +} + +func setupHelper(t *testing.T) *apis.K8sTestHelper { + t.Helper() + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: true, + DisableAnonymous: true, + APIServerRuntimeConfig: "plugins.grafana.app/v0alpha1=true", + }) + t.Cleanup(func() { helper.Shutdown() }) + return helper +} diff --git a/pkg/tests/apis/plugins/pluginsmeta_test.go b/pkg/tests/apis/plugins/pluginsmeta_test.go new file mode 100644 index 00000000000..30a40ce677c --- /dev/null +++ b/pkg/tests/apis/plugins/pluginsmeta_test.go @@ -0,0 +1,48 @@ +package plugins + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/grafana/grafana/pkg/tests/apis" +) + +var gvrPluginMeta = schema.GroupVersionResource{ + Group: "plugins.grafana.app", + Version: "v0alpha1", + Resource: "pluginmetas", +} + +func TestIntegrationPluginMeta(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + t.Run("list plugin metas", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginMeta, + }) + list, err := client.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.NotNil(t, list) + require.Empty(t, list.Items) + }) + + t.Run("get plugin meta", func(t *testing.T) { + helper := setupHelper(t) + ctx := context.Background() + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvrPluginMeta, + }) + _, err := client.Resource.Get(ctx, "example", metav1.GetOptions{}) + require.Error(t, err) + }) +} diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index eb9b460c6b8..1aa4380467a 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -525,6 +525,12 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { require.NoError(t, err) } + if opts.APIServerRuntimeConfig != "" { + section, err := getOrCreateSection("grafana-apiserver") + require.NoError(t, err) + _, err = section.NewKey("runtime_config", opts.APIServerRuntimeConfig) + require.NoError(t, err) + } dbSection, err := getOrCreateSection("database") require.NoError(t, err) _, err = dbSection.NewKey("query_retries", fmt.Sprintf("%d", queryRetries)) @@ -582,6 +588,7 @@ type GrafanaOpts struct { LicensePath string EnableRecordingRules bool EnableSCIM bool + APIServerRuntimeConfig string // When "unified-grpc" is selected it will also start the grpc server APIServerStorageType options.StorageType