diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 30bba5db0ff..d8a1e4104bf 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -85,6 +85,7 @@ # Git Sync frontend owned by frontend team as a whole. /apps/alerting/ @grafana/alerting-backend +/apps/quotas/ @grafana/grafana-search-and-storage /apps/dashboard/ @grafana/grafana-app-platform-squad @grafana/dashboards-squad /apps/folder/ @grafana/grafana-app-platform-squad /apps/playlist/ @grafana/grafana-app-platform-squad diff --git a/Dockerfile b/Dockerfile index d3f63dd0544..558672951e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -93,6 +93,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/quotas apps/quotas COPY apps/plugins apps/plugins COPY apps/shorturl apps/shorturl COPY apps/annotation apps/annotation diff --git a/apps/alerting/historian/go.sum b/apps/alerting/historian/go.sum index b4d2d1dc2e2..9c00f19a029 100644 --- a/apps/alerting/historian/go.sum +++ b/apps/alerting/historian/go.sum @@ -224,6 +224,8 @@ github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba h1:psKWNETD5nGxmF github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLmAhwA4m6iGUD4w1YkydEWWjazn9qxCFT8W0= diff --git a/apps/example/kinds/manifest.cue b/apps/example/kinds/manifest.cue index 934d419623d..65947eaa151 100644 --- a/apps/example/kinds/manifest.cue +++ b/apps/example/kinds/manifest.cue @@ -34,7 +34,7 @@ manifest: { v0alpha1: { kinds: [examplev0alpha1] - // This is explicitly set to false to keep the example app disabled by default. + // This is explicitly set to false to keep the example app disabled by default. // It can be enabled via conf overrides, or by setting this value to true and regenerating. served: false } @@ -48,14 +48,14 @@ v1alpha1: { // served indicates whether this particular version is served by the API server. // served should be set to false before a version is removed from the manifest entirely. // served defaults to true if not present. - // This is explicitly set to false to keep the example app disabled by default. + // This is explicitly set to false to keep the example app disabled by default. // It can be enabled via conf overrides, or by setting this value to true and regenerating. served: false // routes contains resource routes for the version, which are split into 'namespaced' and 'cluster' scoped routes. // This allows you to add additional non-storage- and non-kind- based handlers for your app. // These should only be used if the behavior cannot be accomplished by reconciliation on storage events or subresource routes on a kind. routes: { - // namespaced contains namespace-scoped resource routes for the version, + // namespaced contains namespace-scoped resource routes for the version, // which are exposed as HTTP handlers on '/namespaces//'. namespaced: { "/something": { @@ -72,7 +72,7 @@ v1alpha1: { } } } - // cluster contains cluster-scoped resource routes for the version, + // cluster contains cluster-scoped resource routes for the version, // which are exposed as HTTP handlers on '/'. cluster: { "/other": { @@ -113,4 +113,4 @@ v1alpha1: { enabled: true } } -} \ No newline at end of file +} diff --git a/apps/quotas/Makefile b/apps/quotas/Makefile new file mode 100644 index 00000000000..230bfd4149a --- /dev/null +++ b/apps/quotas/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/quotas/go.mod b/apps/quotas/go.mod new file mode 100644 index 00000000000..1ab18fe8876 --- /dev/null +++ b/apps/quotas/go.mod @@ -0,0 +1,92 @@ +module github.com/grafana/grafana/apps/quotas + +go 1.25.3 + +require ( + github.com/grafana/grafana-app-sdk v0.48.5 + github.com/grafana/grafana-app-sdk/logging v0.48.3 + k8s.io/apimachinery v0.34.2 + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/getkin/kin-openapi v0.133.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-test/deep v1.1.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gnostic-models v0.7.0 // 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/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // 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.3-0.20250322232337-35a7c28c31ee // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/onsi/ginkgo/v2 v2.22.2 // indirect + github.com/onsi/gomega v1.36.2 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/grpc v1.77.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.34.2 // indirect + k8s.io/apiextensions-apiserver v0.34.2 // indirect + k8s.io/client-go v0.34.2 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/apps/quotas/go.sum b/apps/quotas/go.sum new file mode 100644 index 00000000000..5787fa55023 --- /dev/null +++ b/apps/quotas/go.sum @@ -0,0 +1,252 @@ +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/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.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/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.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/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.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-logr/logr v1.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.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +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-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +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.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/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/grafana-app-sdk v0.48.2 h1:CQQDhwo1fWaXQVKvxxOcK6azbuY3E2TgJHNAZlYYn7U= +github.com/grafana/grafana-app-sdk v0.48.2/go.mod h1:LDOvQ7OOyHLcXdSa0InATCa5OMoYAd6E1+rGLrMgHuk= +github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= +github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng= +github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/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/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.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= +github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +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= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= +k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= +k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= +k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= +k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= +k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= +k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +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-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/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 v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/apps/quotas/kinds/cue.mod/module.cue b/apps/quotas/kinds/cue.mod/module.cue new file mode 100644 index 00000000000..6e70f424d2e --- /dev/null +++ b/apps/quotas/kinds/cue.mod/module.cue @@ -0,0 +1,2 @@ +module: "github.com/grafana/grafana/apps/quotas/kinds" +language: version: "v0.8.2" diff --git a/apps/quotas/kinds/manifest.cue b/apps/quotas/kinds/manifest.cue new file mode 100644 index 00000000000..8f799ff070c --- /dev/null +++ b/apps/quotas/kinds/manifest.cue @@ -0,0 +1,92 @@ +package kinds + +manifest: { + // appName is the unique name of your app. It is used to reference the app from other config objects, + // and to generate the group used by your app in the app platform API. + appName: "quotas" + // groupOverride can be used to specify a non-appName-based API group. + // By default, an app's API group is LOWER(REPLACE(appName, '-', '')).ext.grafana.com, + // but there are cases where this needs to be changed. + // Keep in mind that changing this after an app is deployed can cause problems with clients and/or kind data. + groupOverride: "quotas.grafana.app" + + // versions is a map of versions supported by your app. Version names should follow the format "v" or + // "v(alpha|beta)". Each version contains the kinds your app manages for that version. + // If your app needs access to kinds managed by another app, use permissions.accessKinds to allow your app access. + versions: { + "v0alpha1": v0alpha1 + } + // extraPermissions contains any additional permissions your app may require to function. + // Your app will always have all permissions for each kind it manages (the items defined in 'kinds'). + extraPermissions: { + // If your app needs access to additional kinds supplied by other apps, you can list them here + accessKinds: [ + // Here is an example for your app accessing the playlist kind for reads and watch + // { + // group: "playlist.grafana.app" + // resource: "playlists" + // actions: ["get","list","watch"] + // } + ] + } +} + +// v1alpha1 is the v1alpha1 version of the app's API. +// It includes kinds which the v1alpha1 API serves, and (future) custom routes served globally from the v1alpha1 version. +v0alpha1: { + // kinds is the list of kinds served by this version + kinds: [] + // [OPTIONAL] + // served indicates whether this particular version is served by the API server. + // served should be set to false before a version is removed from the manifest entirely. + // served defaults to true if not present. + served: true + + routes: { + // namespaced contains namespace-scoped resource routes for the version, + // which are exposed as HTTP handlers on '/namespaces//'. + namespaced: { + "/usage": { + "GET": { + response: { + namespace: string + resource: string + group: string + usage: int64 + limit: int64 + } + request: { + query: { + group: string + resource: string + } + } + } + } + } + } + + // [OPTIONAL] + // Codegen is a trait that tells the grafana-app-sdk, or other code generation tooling, how to process this kind. + // If not present, default values within the codegen trait are used. + // If you wish to specify codegen per-version, put this section in the version's object + // (for example, v1alpha1) instead. + codegen: { + // [OPTIONAL] + // ts contains TypeScript code generation properties for the kind + ts: { + // [OPTIONAL] + // enabled indicates whether the CLI should generate front-end TypeScript code for the kind. + // Defaults to true if not present. + enabled: true + } + // [OPTIONAL] + // go contains go code generation properties for the kind + go: { + // [OPTIONAL] + // enabled indicates whether the CLI should generate back-end go code for the kind. + // Defaults to true if not present. + enabled: true + } + } +} diff --git a/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_request_params_object_gen.go b/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_request_params_object_gen.go new file mode 100644 index 00000000000..b19b40d5e02 --- /dev/null +++ b/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_request_params_object_gen.go @@ -0,0 +1,33 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +type GetUsageRequestParamsObject struct { + metav1.TypeMeta `json:",inline"` + GetUsageRequestParams `json:",inline"` +} + +func NewGetUsageRequestParamsObject() *GetUsageRequestParamsObject { + return &GetUsageRequestParamsObject{} +} + +func (o *GetUsageRequestParamsObject) DeepCopyObject() runtime.Object { + dst := NewGetUsageRequestParamsObject() + o.DeepCopyInto(dst) + return dst +} + +func (o *GetUsageRequestParamsObject) DeepCopyInto(dst *GetUsageRequestParamsObject) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + dstGetUsageRequestParams := GetUsageRequestParams{} + _ = resource.CopyObjectInto(&dstGetUsageRequestParams, &o.GetUsageRequestParams) +} + +var _ runtime.Object = NewGetUsageRequestParamsObject() diff --git a/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_request_params_types_gen.go b/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_request_params_types_gen.go new file mode 100644 index 00000000000..45394a7f20f --- /dev/null +++ b/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_request_params_types_gen.go @@ -0,0 +1,13 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +type GetUsageRequestParams struct { + Group string `json:"group"` + Resource string `json:"resource"` +} + +// NewGetUsageRequestParams creates a new GetUsageRequestParams object. +func NewGetUsageRequestParams() *GetUsageRequestParams { + return &GetUsageRequestParams{} +} diff --git a/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_response_body_types_gen.go b/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_response_body_types_gen.go new file mode 100644 index 00000000000..eb87d022edd --- /dev/null +++ b/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_response_body_types_gen.go @@ -0,0 +1,17 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type GetUsageBody struct { + Namespace string `json:"namespace"` + Resource string `json:"resource"` + Group string `json:"group"` + Usage int64 `json:"usage"` + Limit int64 `json:"limit"` +} + +// NewGetUsageBody creates a new GetUsageBody object. +func NewGetUsageBody() *GetUsageBody { + return &GetUsageBody{} +} diff --git a/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_response_object_types_gen.go b/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_response_object_types_gen.go new file mode 100644 index 00000000000..87d6be2e587 --- /dev/null +++ b/apps/quotas/pkg/apis/quotas/v0alpha1/getusage_response_object_types_gen.go @@ -0,0 +1,37 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// +k8s:openapi-gen=true +type GetUsage struct { + metav1.TypeMeta `json:",inline"` + GetUsageBody `json:",inline"` +} + +func NewGetUsage() *GetUsage { + return &GetUsage{} +} + +func (t *GetUsageBody) DeepCopyInto(dst *GetUsageBody) { + _ = resource.CopyObjectInto(dst, t) +} + +func (o *GetUsage) DeepCopyObject() runtime.Object { + dst := NewGetUsage() + o.DeepCopyInto(dst) + return dst +} + +func (o *GetUsage) DeepCopyInto(dst *GetUsage) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + o.GetUsageBody.DeepCopyInto(&dst.GetUsageBody) +} + +var _ runtime.Object = NewGetUsage() diff --git a/apps/quotas/pkg/apis/quotas_manifest.go b/apps/quotas/pkg/apis/quotas_manifest.go new file mode 100644 index 00000000000..e72524d59f3 --- /dev/null +++ b/apps/quotas/pkg/apis/quotas_manifest.go @@ -0,0 +1,213 @@ +// +// This file is generated by grafana-app-sdk +// DO NOT EDIT +// + +package apis + +import ( + "fmt" + "strings" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kube-openapi/pkg/spec3" + "k8s.io/kube-openapi/pkg/validation/spec" + + v0alpha1 "github.com/grafana/grafana/apps/quotas/pkg/apis/quotas/v0alpha1" +) + +var appManifestData = app.ManifestData{ + AppName: "quotas", + Group: "quotas.grafana.app", + PreferredVersion: "v0alpha1", + Versions: []app.ManifestVersion{ + { + Name: "v0alpha1", + Served: true, + Kinds: []app.ManifestVersionKind{}, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{ + "/usage": { + Get: &spec3.Operation{ + OperationProps: spec3.OperationProps{ + + OperationId: "getUsage", + + Parameters: []*spec3.Parameter{ + + { + ParameterProps: spec3.ParameterProps{ + Name: "group", + In: "query", + Required: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + }, + }, + + { + ParameterProps: spec3.ParameterProps{ + Name: "resource", + In: "query", + Required: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + }, + }, + }, + + Responses: &spec3.Responses{ + ResponsesProps: spec3.ResponsesProps{ + Default: &spec3.Response{ + ResponseProps: spec3.ResponseProps{ + Description: "Default OK response", + Content: map[string]*spec3.MediaType{ + "application/json": { + MediaTypeProps: spec3.MediaTypeProps{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + }, + }, + "group": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + }, + }, + "limit": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "usage": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + }, + }, + }, + Required: []string{ + "namespace", + "resource", + "group", + "usage", + "limit", + "apiVersion", + "kind", + }, + }}, + }}, + }, + }, + }, + }}, + }, + }, + }, + }, + Cluster: map[string]spec3.PathProps{}, + Schemas: map[string]spec.Schema{}, + }, + }, + }, +} + +func LocalManifest() app.Manifest { + return app.NewEmbeddedManifest(appManifestData) +} + +func RemoteManifest() app.Manifest { + return app.NewAPIServerManifest("quotas") +} + +var kindVersionToGoType = map[string]resource.Kind{} + +// 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{ + "v0alpha1||/usage|GET": v0alpha1.GetUsage{}, +} + +// 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. +// Resource routes (those without a kind) should prefix their route with "/" if the route is namespaced (otherwise the route is assumed to be cluster-scope) +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 +} + +var customRouteToGoParamsType = map[string]runtime.Object{} + +func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +var customRouteToGoRequestBodyType = map[string]any{} + +func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +type GoTypeAssociator struct{} + +func NewGoTypeAssociator() *GoTypeAssociator { + return &GoTypeAssociator{} +} + +func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) { + return ManifestGoTypeAssociator(kind, version) +} +func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteResponsesAssociator(kind, version, path, verb) +} +func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) { + return ManifestCustomRouteQueryAssociator(kind, version, path, verb) +} +func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb) +} diff --git a/apps/quotas/pkg/app/app.go b/apps/quotas/pkg/app/app.go new file mode 100644 index 00000000000..d4862661b60 --- /dev/null +++ b/apps/quotas/pkg/app/app.go @@ -0,0 +1,123 @@ +package app + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana-app-sdk/operator" + "github.com/grafana/grafana-app-sdk/resource" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + + unifiedStorage "github.com/grafana/grafana/pkg/storage/unified/resource" + + "github.com/grafana/grafana-app-sdk/simple" + quotasv0alpha1 "github.com/grafana/grafana/apps/quotas/pkg/apis/quotas/v0alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type QuotasAppConfig struct { + ResourceClient unifiedStorage.ResourceClient +} + +type QuotasHandler struct { + ResourceClient unifiedStorage.ResourceClient +} + +func NewQuotasHandler(cfg *QuotasAppConfig) *QuotasHandler { + return &QuotasHandler{ + ResourceClient: cfg.ResourceClient, + } +} + +// GetQuota handles requests for the GET /usage resource route +func (h *QuotasHandler) GetQuota(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error { + if !request.URL.Query().Has("group") { + // TODO its returning a 500 instead of 400 bad request + writer.WriteHeader(http.StatusBadRequest) + return fmt.Errorf("missing required query parameters: group") + } + if !request.URL.Query().Has("resource") { + writer.WriteHeader(http.StatusBadRequest) + return fmt.Errorf("missing required query parameters: resource") + } + group := request.URL.Query().Get("group") + res := request.URL.Query().Get("resource") + + quotaReq := &resourcepb.QuotaUsageRequest{ + Key: &resourcepb.ResourceKey{ + Namespace: request.ResourceIdentifier.Namespace, + Group: group, + Resource: res, + }, + } + quota, err := h.ResourceClient.GetQuotaUsage(ctx, quotaReq) + if err != nil { + return err + } + + writer.Header().Set("Content-Type", "application/json") + return json.NewEncoder(writer).Encode(quotasv0alpha1.GetUsage{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "quotas.grafana.com/v0alpha1", + Kind: "Quotas", + }, + GetUsageBody: quotasv0alpha1.GetUsageBody{ + Namespace: request.ResourceIdentifier.Namespace, + Group: group, + Resource: res, + Usage: quota.Usage, + Limit: quota.Limit, + }, + }) +} + +func New(cfg app.Config) (app.App, error) { + appConfig, ok := cfg.SpecificConfig.(*QuotasAppConfig) + if !ok { + return nil, fmt.Errorf("expected QuotasAppConfig but got %T", cfg.SpecificConfig) + } + handler := NewQuotasHandler(appConfig) + + simpleConfig := simple.AppConfig{ + Name: "quotas", + KubeConfig: cfg.KubeConfig, + InformerConfig: simple.AppInformerConfig{ + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + logging.FromContext(ctx).Error("Informer processing error", "error", err) + }, + }, + }, + ManagedKinds: []simple.AppManagedKind{}, + VersionedCustomRoutes: map[string]simple.AppVersionRouteHandlers{ + "v0alpha1": { + { + Namespaced: true, + Path: "usage", + Method: "GET", + }: handler.GetQuota, + }, + }, + } + + 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 { + return map[schema.GroupVersion][]resource.Kind{} +} diff --git a/apps/quotas/pkg/app/app_test.go b/apps/quotas/pkg/app/app_test.go new file mode 100644 index 00000000000..252c2c4e240 --- /dev/null +++ b/apps/quotas/pkg/app/app_test.go @@ -0,0 +1,71 @@ +package app + +import ( + "context" + "net/http/httptest" + "net/url" + "testing" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestGetQuota(t *testing.T) { + t.Run("will return error when resource param is missing", func(t *testing.T) { + clientMock := resource.NewMockResourceClient(t) + handler := NewQuotasHandler(&QuotasAppConfig{ + ResourceClient: clientMock, + }) + url, err := url.Parse("http://localhost:3000/apis/quotas.grafana.app/v0alpha1/namespaces/stacks-1/usage?group=dashboard.grafana.app") + require.NoError(t, err) + req := &app.CustomRouteRequest{ + URL: url, + Method: "GET", + } + recorder := &httptest.ResponseRecorder{} + err = handler.GetQuota(context.Background(), recorder, req) + require.Error(t, err) + }) + + t.Run("will return error when group param is missing", func(t *testing.T) { + clientMock := resource.NewMockResourceClient(t) + handler := NewQuotasHandler(&QuotasAppConfig{ + ResourceClient: clientMock, + }) + url, err := url.Parse("http://localhost:3000/apis/quotas.grafana.app/v0alpha1/namespaces/stacks-1/usage?resource=dashboards") + require.NoError(t, err) + req := &app.CustomRouteRequest{ + URL: url, + Method: "GET", + } + recorder := &httptest.ResponseRecorder{} + err = handler.GetQuota(context.Background(), recorder, req) + require.Error(t, err) + }) + + t.Run("will return quotas response when params are valid", func(t *testing.T) { + clientMock := resource.NewMockResourceClient(t) + clientMock.On("GetQuotaUsage", mock.Anything, mock.Anything, mock.Anything).Return(&resourcepb.QuotaUsageResponse{ + Error: nil, + Usage: 1, + Limit: 2, + }, nil) + handler := NewQuotasHandler(&QuotasAppConfig{ + ResourceClient: clientMock, + }) + url, err := url.Parse("http://localhost:3000/apis/quotas.grafana.app/v0alpha1/namespaces/stacks-1/usage?group=dashboard.grafana.app&resource=dashboards") + require.NoError(t, err) + req := &app.CustomRouteRequest{ + URL: url, + Method: "GET", + } + recorder := &httptest.ResponseRecorder{} + err = handler.GetQuota(context.Background(), recorder, req) + require.NoError(t, err) + + require.Equal(t, 200, recorder.Code) + }) +} diff --git a/apps/quotas/plugin/src/generated/quota/v0alpha1/quota_object_gen.ts b/apps/quotas/plugin/src/generated/quota/v0alpha1/quota_object_gen.ts new file mode 100644 index 00000000000..70f306a1b08 --- /dev/null +++ b/apps/quotas/plugin/src/generated/quota/v0alpha1/quota_object_gen.ts @@ -0,0 +1,49 @@ +/* + * This file was generated by grafana-app-sdk. DO NOT EDIT. + */ +import { Spec } from './types.spec.gen'; +import { Status } from './types.status.gen'; + +export interface Metadata { + name: string; + namespace: string; + generateName?: string; + selfLink?: string; + uid?: string; + resourceVersion?: string; + generation?: number; + creationTimestamp?: string; + deletionTimestamp?: string; + deletionGracePeriodSeconds?: number; + labels?: Record; + annotations?: Record; + ownerReferences?: OwnerReference[]; + finalizers?: string[]; + managedFields?: ManagedFieldsEntry[]; +} + +export interface OwnerReference { + apiVersion: string; + kind: string; + name: string; + uid: string; + controller?: boolean; + blockOwnerDeletion?: boolean; +} + +export interface ManagedFieldsEntry { + manager?: string; + operation?: string; + apiVersion?: string; + time?: string; + fieldsType?: string; + subresource?: string; +} + +export interface Quota { + kind: string; + apiVersion: string; + metadata: Metadata; + spec: Spec; + status: Status; +} diff --git a/apps/quotas/plugin/src/generated/quota/v0alpha1/types.metadata.gen.ts b/apps/quotas/plugin/src/generated/quota/v0alpha1/types.metadata.gen.ts new file mode 100644 index 00000000000..4377f3c1d08 --- /dev/null +++ b/apps/quotas/plugin/src/generated/quota/v0alpha1/types.metadata.gen.ts @@ -0,0 +1,30 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +// 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. +export interface Metadata { + updateTimestamp: string; + createdBy: string; + uid: string; + creationTimestamp: string; + deletionTimestamp?: string; + finalizers: string[]; + resourceVersion: string; + generation: number; + updatedBy: string; + labels: Record; +} + +export const defaultMetadata = (): Metadata => ({ + updateTimestamp: "", + createdBy: "", + uid: "", + creationTimestamp: "", + finalizers: [], + resourceVersion: "", + generation: 0, + updatedBy: "", + labels: {}, +}); + diff --git a/apps/quotas/plugin/src/generated/quota/v0alpha1/types.spec.gen.ts b/apps/quotas/plugin/src/generated/quota/v0alpha1/types.spec.gen.ts new file mode 100644 index 00000000000..9209753fa64 --- /dev/null +++ b/apps/quotas/plugin/src/generated/quota/v0alpha1/types.spec.gen.ts @@ -0,0 +1,14 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +export interface Spec { + count: string; + limit: string; + kind: string; +} + +export const defaultSpec = (): Spec => ({ + count: "", + limit: "", + kind: "", +}); + diff --git a/apps/quotas/plugin/src/generated/quota/v0alpha1/types.status.gen.ts b/apps/quotas/plugin/src/generated/quota/v0alpha1/types.status.gen.ts new file mode 100644 index 00000000000..01be8df7961 --- /dev/null +++ b/apps/quotas/plugin/src/generated/quota/v0alpha1/types.status.gen.ts @@ -0,0 +1,30 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +export interface OperatorState { + // lastEvaluation is the ResourceVersion last evaluated + lastEvaluation: string; + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + state: "success" | "in_progress" | "failed"; + // descriptiveState is an optional more descriptive state field which has no requirements on format + descriptiveState?: string; + // details contains any extra information that is operator-specific + details?: Record; +} + +export const defaultOperatorState = (): OperatorState => ({ + lastEvaluation: "", + state: "success", +}); + +export interface Status { + // 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?: Record; + // additionalFields is reserved for future use + additionalFields?: Record; +} + +export const defaultStatus = (): Status => ({ +}); + diff --git a/go.mod b/go.mod index e589e5a18a5..af798993f31 100644 --- a/go.mod +++ b/go.mod @@ -665,7 +665,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect - github.com/ebitengine/purego v0.8.4 // indirect + github.com/ebitengine/purego v0.8.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/swag/conv v0.25.1 // indirect github.com/go-openapi/swag/fileutils v0.25.1 // indirect @@ -686,7 +686,7 @@ require ( github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/shirou/gopsutil/v4 v4.25.6 // indirect + github.com/shirou/gopsutil/v4 v4.25.3 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.8.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect diff --git a/go.sum b/go.sum index 91104bfeaf4..b08b87734c7 100644 --- a/go.sum +++ b/go.sum @@ -646,7 +646,6 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0p github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2 h1:qFYgLH2zZe3WHpQgUrzeazC+ebDebwAQqS9yE1cP5Bs= github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2/go.mod h1:09/ALd1AXCTCOfcJYD8+jIYKmFmi6PVCkTsipC18F7E= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -1075,7 +1074,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3 github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= @@ -1142,8 +1140,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= @@ -2393,8 +2391,8 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3 h1:/4/IJi5iyTdh6mqOUaASW148HQpujYiHl0Wl78dSOSc= github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3/go.mod h1:aJIMhRsunltJR926EB2MUg8qHemFQDreSB33pyto2Ps= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= +github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= diff --git a/go.work b/go.work index ecfab4e96ce..884f393673e 100644 --- a/go.work +++ b/go.work @@ -23,6 +23,7 @@ use ( ./apps/plugins ./apps/preferences ./apps/provisioning + ./apps/quotas ./apps/scope ./apps/secret ./apps/shorturl diff --git a/go.work.sum b/go.work.sum index 3017d8eb878..eaa5da46cf0 100644 --- a/go.work.sum +++ b/go.work.sum @@ -267,6 +267,8 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6 h1:K72hopUosKG3ntOPNG4OzzbuhxGuVf06 git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk= github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas= @@ -602,6 +604,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= @@ -680,8 +684,6 @@ github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6 github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= -github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/efficientgo/tools/core v0.0.0-20220225185207-fe763185946b h1:ZHiD4/yE4idlbqvAO6iYCOYRzOMRpxkW+FKasRA3tsQ= github.com/efficientgo/tools/core v0.0.0-20220225185207-fe763185946b/go.mod h1:OmVcnJopJL8d3X3sSXTiypGoUSgFq1aDGmlrdi9dn/M= github.com/elastic/elastic-transport-go/v8 v8.6.1 h1:h2jQRqH6eLGiBSN4eZbQnJLtL4bC5b4lfVFRjw2R4e4= @@ -873,7 +875,6 @@ github.com/grafana/grafana-app-sdk v0.41.0 h1:SYHN3U7B1myRKY3UZZDkFsue9TDmAOap0U github.com/grafana/grafana-app-sdk v0.41.0/go.mod h1:Wg/3vEZfok1hhIWiHaaJm+FwkosfO98o8KbeLFEnZpY= github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc= github.com/grafana/grafana-app-sdk v0.47.0/go.mod h1:kywXmkppq0oReUMzkjTW8Fq2EBzyN7v914jttTWnWxA= -github.com/grafana/grafana-app-sdk v0.48.2/go.mod h1:LDOvQ7OOyHLcXdSa0InATCa5OMoYAd6E1+rGLrMgHuk= 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= @@ -887,9 +888,10 @@ github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5 github.com/grafana/grafana-app-sdk/logging v0.46.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-app-sdk/logging v0.48.0 h1:xolkQxBlA2LQF4hprKIAeu+zUem1DigYZ6XC1TOhFJE= github.com/grafana/grafana-app-sdk/logging v0.48.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= -github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-app-sdk/logging v0.48.2 h1:tI+a9slUvxKUgweXDzUqkca2LWV3g1UdaSvwt8nQNHg= github.com/grafana/grafana-app-sdk/logging v0.48.2/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/logging v0.48.5 h1:vWiTZrsSscbC5IQq2heWXm0dXhvg40nXIeUTCdE9qsc= +github.com/grafana/grafana-app-sdk/logging v0.48.5/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-app-sdk/plugin v0.41.0 h1:ShUvGpAVzM3UxcsfwS6l/lwW4ytDeTbCQXf8w2P8Yp8= github.com/grafana/grafana-app-sdk/plugin v0.41.0/go.mod h1:YIhimVfAqtOp3kdhxOanaSZjypVKh/bYxf9wfFfhDm0= github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0= @@ -956,7 +958,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9K github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -1375,7 +1376,6 @@ github.com/richardartoul/molecule v1.0.0/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+u github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= @@ -1412,8 +1412,6 @@ github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKl github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= -github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= -github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= @@ -1593,7 +1591,6 @@ go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5queth go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/collector v0.121.0/go.mod h1:M4TlnmkjIgishm2DNCk9K3hMKTmAsY9w8cNFsp9EchM= go.opentelemetry.io/collector v0.124.0/go.mod h1:QzERYfmHUedawjr8Ph/CBEEkVqWS8IlxRLAZt+KHlCg= go.opentelemetry.io/collector/client v1.29.0/go.mod h1:LCUoEV2KCTKA1i+/txZaGsSPVWUcqeOV6wCfNsAippE= @@ -1880,7 +1877,6 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= @@ -2081,7 +2077,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go. google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= -google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822 h1:zWFRixYR5QlotL+Uv3YfsPRENIrQFXiGs+iwqel6fOQ= @@ -2112,7 +2107,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= @@ -2135,7 +2129,6 @@ google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7E google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= 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= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= @@ -2250,7 +2243,6 @@ sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ih sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index eba76d2c198..9b03299aab9 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -261,6 +261,10 @@ export interface FeatureToggles { */ kubernetesCorrelations?: boolean; /** + * Adds support for Kubernetes unified storage quotas + */ + kubernetesUnifiedStorageQuotas?: boolean; + /** * Adds support for Kubernetes logs drilldown */ kubernetesLogsDrilldown?: boolean; diff --git a/pkg/extensions/enterprise_imports.go b/pkg/extensions/enterprise_imports.go index 472652cc103..fbff17523fc 100644 --- a/pkg/extensions/enterprise_imports.go +++ b/pkg/extensions/enterprise_imports.go @@ -56,7 +56,8 @@ import ( _ "github.com/grafana/e2e" _ "github.com/grafana/gofpdf" _ "github.com/grafana/gomemcache/memcache" + _ "github.com/grafana/tempo/pkg/traceql" + _ "github.com/grafana/grafana/apps/alerting/alertenrichment/pkg/apis/alertenrichment/v1beta1" _ "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1" - _ "github.com/grafana/tempo/pkg/traceql" ) diff --git a/pkg/registry/apis/dashboard/legacy/client.go b/pkg/registry/apis/dashboard/legacy/client.go index 913dd02d14d..258d0d1a519 100644 --- a/pkg/registry/apis/dashboard/legacy/client.go +++ b/pkg/registry/apis/dashboard/legacy/client.go @@ -95,3 +95,7 @@ func (d *directResourceClient) BulkProcess(ctx context.Context, opts ...grpc.Cal func (b *directResourceClient) RebuildIndexes(ctx context.Context, req *resourcepb.RebuildIndexesRequest, opts ...grpc.CallOption) (*resourcepb.RebuildIndexesResponse, error) { return nil, fmt.Errorf("not implemented") } + +func (b *directResourceClient) GetQuotaUsage(ctx context.Context, req *resourcepb.QuotaUsageRequest, opts ...grpc.CallOption) (*resourcepb.QuotaUsageResponse, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index ab7f05e7d00..406494b9d36 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -1103,3 +1103,7 @@ func (m *MockClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) ( func (m *MockClient) UpdateIndex(ctx context.Context, reason string) error { return nil } + +func (m *MockClient) GetQuotaUsage(ctx context.Context, req *resourcepb.QuotaUsageRequest, opts ...grpc.CallOption) (*resourcepb.QuotaUsageResponse, error) { + return nil, nil +} diff --git a/pkg/registry/apis/iam/team_search_test.go b/pkg/registry/apis/iam/team_search_test.go index ccc1abbf18c..76efed1c067 100644 --- a/pkg/registry/apis/iam/team_search_test.go +++ b/pkg/registry/apis/iam/team_search_test.go @@ -284,3 +284,6 @@ func (m *MockClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) ( func (m *MockClient) UpdateIndex(ctx context.Context, reason string) error { return nil } +func (m *MockClient) GetQuotaUsage(ctx context.Context, in *resourcepb.QuotaUsageRequest, opts ...grpc.CallOption) (*resourcepb.QuotaUsageResponse, error) { + return nil, nil +} diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index 9ea31109495..a1ec8aafd65 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -3,6 +3,8 @@ package appregistry import ( "context" + "github.com/grafana/grafana/pkg/registry/apps/quotas" + "github.com/open-feature/go-sdk/openfeature" "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" @@ -44,12 +46,17 @@ func ProvideAppInstallers( exampleAppInstaller *example.ExampleAppInstaller, advisorAppInstaller *advisor.AdvisorAppInstaller, alertingHistorianAppInstaller *historian.AlertingHistorianAppInstaller, + quotasAppInstaller *quotas.QuotasAppInstaller, ) []appsdkapiserver.AppInstaller { + featureClient := openfeature.NewDefaultClient() installers := []appsdkapiserver.AppInstaller{ playlistAppInstaller, pluginsApplInstaller, exampleAppInstaller, } + if featureClient.Boolean(context.Background(), featuremgmt.FlagKubernetesUnifiedStorageQuotas, false, openfeature.TransactionContext(context.Background())) { + installers = append(installers, quotasAppInstaller) + } //nolint:staticcheck // not yet migrated to OpenFeature if features.IsEnabledGlobally(featuremgmt.FlagKubernetesShortURLs) { installers = append(installers, shorturlAppInstaller) diff --git a/pkg/registry/apps/apps_test.go b/pkg/registry/apps/apps_test.go index 6a6f0c9a2aa..737e7c8aa9a 100644 --- a/pkg/registry/apps/apps_test.go +++ b/pkg/registry/apps/apps_test.go @@ -3,6 +3,7 @@ package appregistry import ( "testing" + "github.com/grafana/grafana/pkg/registry/apps/quotas" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/registry/apps/advisor" @@ -27,6 +28,7 @@ func TestProvideAppInstallers_Table(t *testing.T) { exampleAppInstaller := &example.ExampleAppInstaller{} advisorAppInstaller := &advisor.AdvisorAppInstaller{} historianAppInstaller := &historian.AlertingHistorianAppInstaller{} + quotasAppInstaller := "as.QuotasAppInstaller{} tests := []struct { name string @@ -43,7 +45,7 @@ func TestProvideAppInstallers_Table(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { features := featuremgmt.WithFeatures(tt.flags...) - got := ProvideAppInstallers(features, playlistInstaller, pluginsInstaller, nil, tt.rulesInst, correlationsAppInstaller, notificationsAppInstaller, nil, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, historianAppInstaller) + got := ProvideAppInstallers(features, playlistInstaller, pluginsInstaller, nil, tt.rulesInst, correlationsAppInstaller, notificationsAppInstaller, nil, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, historianAppInstaller, quotasAppInstaller) if tt.expectRulesApp { require.Contains(t, got, tt.rulesInst) } else { diff --git a/pkg/registry/apps/quotas/register.go b/pkg/registry/apps/quotas/register.go new file mode 100644 index 00000000000..b81d4fef5cf --- /dev/null +++ b/pkg/registry/apps/quotas/register.go @@ -0,0 +1,50 @@ +package quotas + +import ( + "github.com/grafana/grafana/apps/quotas/pkg/apis" + "github.com/grafana/grafana/pkg/storage/unified/resource" + restclient "k8s.io/client-go/rest" + + "github.com/grafana/grafana-app-sdk/app" + appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "github.com/grafana/grafana-app-sdk/simple" + quotasapp "github.com/grafana/grafana/apps/quotas/pkg/app" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" +) + +var ( + _ appsdkapiserver.AppInstaller = (*QuotasAppInstaller)(nil) +) + +type QuotasAppInstaller struct { + appsdkapiserver.AppInstaller + cfg *setting.Cfg +} + +func RegisterAppInstaller( + cfg *setting.Cfg, + features featuremgmt.FeatureToggles, + resourceClient resource.ResourceClient, +) (*QuotasAppInstaller, error) { + installer := &QuotasAppInstaller{ + cfg: cfg, + } + specificConfig := "asapp.QuotasAppConfig{ + ResourceClient: resourceClient, + } + provider := simple.NewAppProvider(apis.LocalManifest(), specificConfig, quotasapp.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.NewGoTypeAssociator()) + if err != nil { + return nil, err + } + installer.AppInstaller = i + + return installer, nil +} diff --git a/pkg/registry/apps/wireset.go b/pkg/registry/apps/wireset.go index c57fe84d018..a91fa138309 100644 --- a/pkg/registry/apps/wireset.go +++ b/pkg/registry/apps/wireset.go @@ -2,6 +2,7 @@ package appregistry import ( "github.com/google/wire" + "github.com/grafana/grafana/pkg/registry/apps/quotas" "github.com/grafana/grafana/pkg/registry/apps/alerting/historian" "github.com/grafana/grafana/pkg/registry/apps/alerting/notifications" @@ -29,5 +30,6 @@ var WireSet = wire.NewSet( historian.RegisterAppInstaller, logsdrilldown.RegisterAppInstaller, annotation.RegisterAppInstaller, + quotas.RegisterAppInstaller, example.RegisterAppInstaller, ) diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index d3cf6da6b5a..8500130aeea 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -89,6 +89,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apps/logsdrilldown" "github.com/grafana/grafana/pkg/registry/apps/playlist" "github.com/grafana/grafana/pkg/registry/apps/plugins" + "github.com/grafana/grafana/pkg/registry/apps/quotas" "github.com/grafana/grafana/pkg/registry/apps/shorturl" "github.com/grafana/grafana/pkg/registry/backgroundsvcs" "github.com/grafana/grafana/pkg/registry/usagestatssvcs" @@ -824,7 +825,11 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller) + quotasAppInstaller, err := quotas.RegisterAppInstaller(cfg, featureToggles, resourceClient) + if err != nil { + return nil, err + } + v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller) builderMetrics := builder.ProvideBuilderMetrics(registerer) apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) if err != nil { @@ -1477,7 +1482,11 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller) + quotasAppInstaller, err := quotas.RegisterAppInstaller(cfg, featureToggles, resourceClient) + if err != nil { + return nil, err + } + v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller) builderMetrics := builder.ProvideBuilderMetrics(registerer) apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) if err != nil { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 680680791a5..c8ac36bc585 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -414,6 +414,13 @@ var ( Owner: grafanaDataProSquad, RequiresRestart: true, }, + { + Name: "kubernetesUnifiedStorageQuotas", + Description: "Adds support for Kubernetes unified storage quotas", + Stage: FeatureStageExperimental, + Owner: grafanaSearchAndStorageSquad, + RequiresRestart: true, + }, { Name: "kubernetesLogsDrilldown", Description: "Adds support for Kubernetes logs drilldown", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 15d15a986a5..fda591b9fbd 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -56,6 +56,7 @@ kubernetesShortURLs,experimental,@grafana/grafana-app-platform-squad,false,true, useKubernetesShortURLsAPI,experimental,@grafana/sharing-squad,false,false,true kubernetesAlertingRules,experimental,@grafana/alerting-squad,false,true,false kubernetesCorrelations,experimental,@grafana/datapro,false,true,false +kubernetesUnifiedStorageQuotas,experimental,@grafana/search-and-storage,false,true,false kubernetesLogsDrilldown,experimental,@grafana/observability-logs,false,true,false kubernetesQueryCaching,experimental,@grafana/grafana-operator-experience-squad,false,true,false dashboardDisableSchemaValidationV1,experimental,@grafana/grafana-app-platform-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index afc599d4eb8..6e106fd9950 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -183,6 +183,10 @@ const ( // Adds support for Kubernetes correlations FlagKubernetesCorrelations = "kubernetesCorrelations" + // FlagKubernetesUnifiedStorageQuotas + // Adds support for Kubernetes unified storage quotas + FlagKubernetesUnifiedStorageQuotas = "kubernetesUnifiedStorageQuotas" + // FlagKubernetesLogsDrilldown // Adds support for Kubernetes logs drilldown FlagKubernetesLogsDrilldown = "kubernetesLogsDrilldown" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 96ebe6dc5de..9ac920199e8 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2039,6 +2039,19 @@ "requiresRestart": true } }, + { + "metadata": { + "name": "kubernetesUnifiedStorageQuotas", + "resourceVersion": "1764965198011", + "creationTimestamp": "2025-12-05T20:06:38Z" + }, + "spec": { + "description": "Adds support for Kubernetes unified storage quotas", + "stage": "experimental", + "codeowner": "@grafana/search-and-storage", + "requiresRestart": true + } + }, { "metadata": { "name": "localeFormatPreference", diff --git a/pkg/storage/unified/apistore/store_test.go b/pkg/storage/unified/apistore/store_test.go index 495efc32175..5d7c5a372a0 100644 --- a/pkg/storage/unified/apistore/store_test.go +++ b/pkg/storage/unified/apistore/store_test.go @@ -166,6 +166,7 @@ type resourceClientMock struct { resourcepb.BulkStoreClient resourcepb.BlobStoreClient resourcepb.DiagnosticsClient + resourcepb.QuotasClient } // always return GRPC Unauthenticated code diff --git a/pkg/storage/unified/proto/resource.proto b/pkg/storage/unified/proto/resource.proto index 91a9288b194..ca91d9efd21 100644 --- a/pkg/storage/unified/proto/resource.proto +++ b/pkg/storage/unified/proto/resource.proto @@ -587,6 +587,22 @@ message ResourceTableRow { bytes object = 4; } +message QuotaUsageRequest { + // Namespace (tenant) + ResourceKey key = 1; +} + +message QuotaUsageResponse { + // Error details + ErrorResult error = 1; + + // Current usage + int64 usage = 2; + + // Current limit + int64 limit = 3; +} + // This provides the CRUD+List+Watch support needed for a k8s apiserver // The semantics and behaviors of this service are constrained by kubernetes // This does not understand the resource schemas, only deals with json bytes @@ -631,3 +647,8 @@ service Diagnostics { // Check if the service is healthy rpc IsHealthy(HealthCheckRequest) returns (HealthCheckResponse); } + +service Quotas { + // Get current quota usage and limits + rpc GetQuotaUsage(QuotaUsageRequest) returns (QuotaUsageResponse); +} diff --git a/pkg/storage/unified/resource/client.go b/pkg/storage/unified/resource/client.go index b2742f71dc9..e51b7ec4876 100644 --- a/pkg/storage/unified/resource/client.go +++ b/pkg/storage/unified/resource/client.go @@ -39,6 +39,7 @@ type ResourceClient interface { resourcepb.BulkStoreClient resourcepb.BlobStoreClient resourcepb.DiagnosticsClient + resourcepb.QuotasClient } // Internal implementation @@ -49,6 +50,7 @@ type resourceClient struct { resourcepb.BulkStoreClient resourcepb.BlobStoreClient resourcepb.DiagnosticsClient + resourcepb.QuotasClient } func NewResourceClient(conn, indexConn grpc.ClientConnInterface, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer trace.Tracer) (ResourceClient, error) { @@ -76,6 +78,7 @@ func newResourceClient(storageCc grpc.ClientConnInterface, indexCc grpc.ClientCo BulkStoreClient: resourcepb.NewBulkStoreClient(storageCc), BlobStoreClient: resourcepb.NewBlobStoreClient(storageCc), DiagnosticsClient: resourcepb.NewDiagnosticsClient(storageCc), + QuotasClient: resourcepb.NewQuotasClient(storageCc), } } @@ -102,6 +105,7 @@ func NewLocalResourceClient(server ResourceServer) ResourceClient { &resourcepb.BlobStore_ServiceDesc, &resourcepb.BulkStore_ServiceDesc, &resourcepb.Diagnostics_ServiceDesc, + &resourcepb.Quotas_ServiceDesc, } { channel.RegisterService( grpchan.InterceptServer( diff --git a/pkg/storage/unified/resource/client_mock.go b/pkg/storage/unified/resource/client_mock.go index fcc7392880e..059421b487a 100644 --- a/pkg/storage/unified/resource/client_mock.go +++ b/pkg/storage/unified/resource/client_mock.go @@ -394,6 +394,80 @@ func (_c *MockResourceClient_GetBlob_Call) RunAndReturn(run func(context.Context return _c } +// GetQuotaUsage provides a mock function with given fields: ctx, in, opts +func (_m *MockResourceClient) GetQuotaUsage(ctx context.Context, in *resourcepb.QuotaUsageRequest, opts ...grpc.CallOption) (*resourcepb.QuotaUsageResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for GetQuotaUsage") + } + + var r0 *resourcepb.QuotaUsageResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *resourcepb.QuotaUsageRequest, ...grpc.CallOption) (*resourcepb.QuotaUsageResponse, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *resourcepb.QuotaUsageRequest, ...grpc.CallOption) *resourcepb.QuotaUsageResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*resourcepb.QuotaUsageResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *resourcepb.QuotaUsageRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockResourceClient_GetQuotaUsage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetQuotaUsage' +type MockResourceClient_GetQuotaUsage_Call struct { + *mock.Call +} + +// GetQuotaUsage is a helper method to define mock.On call +// - ctx context.Context +// - in *resourcepb.QuotaUsageRequest +// - opts ...grpc.CallOption +func (_e *MockResourceClient_Expecter) GetQuotaUsage(ctx interface{}, in interface{}, opts ...interface{}) *MockResourceClient_GetQuotaUsage_Call { + return &MockResourceClient_GetQuotaUsage_Call{Call: _e.mock.On("GetQuotaUsage", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *MockResourceClient_GetQuotaUsage_Call) Run(run func(ctx context.Context, in *resourcepb.QuotaUsageRequest, opts ...grpc.CallOption)) *MockResourceClient_GetQuotaUsage_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*resourcepb.QuotaUsageRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockResourceClient_GetQuotaUsage_Call) Return(_a0 *resourcepb.QuotaUsageResponse, _a1 error) *MockResourceClient_GetQuotaUsage_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockResourceClient_GetQuotaUsage_Call) RunAndReturn(run func(context.Context, *resourcepb.QuotaUsageRequest, ...grpc.CallOption) (*resourcepb.QuotaUsageResponse, error)) *MockResourceClient_GetQuotaUsage_Call { + _c.Call.Return(run) + return _c +} + // GetStats provides a mock function with given fields: ctx, in, opts func (_m *MockResourceClient) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest, opts ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) { _va := make([]interface{}, len(opts)) diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index 951b1be5b9c..bdfb2e8c7ca 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -41,6 +41,7 @@ type ResourceServer interface { resourcepb.ManagedObjectIndexServer resourcepb.BlobStoreServer resourcepb.DiagnosticsServer + resourcepb.QuotasServer } type ListIterator interface { @@ -1466,6 +1467,38 @@ func (s *server) PutBlob(ctx context.Context, req *resourcepb.PutBlobRequest) (* return rsp, nil } +func (s *server) GetQuotaUsage(ctx context.Context, req *resourcepb.QuotaUsageRequest) (*resourcepb.QuotaUsageResponse, error) { + if s.overridesService == nil { + return &resourcepb.QuotaUsageResponse{Error: &resourcepb.ErrorResult{ + Message: "overrides service not configured on resource server", + Code: http.StatusNotImplemented, + }}, nil + } + nsr := NamespacedResource{ + Namespace: req.Key.Namespace, + Group: req.Key.Group, + Resource: req.Key.Resource, + } + usage, err := s.backend.GetResourceStats(ctx, nsr, 0) + if err != nil { + return &resourcepb.QuotaUsageResponse{Error: AsErrorResult(err)}, nil + } + limit, err := s.overridesService.GetQuota(ctx, nsr) + if err != nil { + return &resourcepb.QuotaUsageResponse{Error: AsErrorResult(err)}, nil + } + + // handle case where no resources exist yet - very unlikely but possible + rsp := &resourcepb.QuotaUsageResponse{Limit: int64(limit.Limit)} + if len(usage) <= 0 { + rsp.Usage = 0 + } else { + rsp.Usage = usage[0].Count + } + + return rsp, nil +} + func (s *server) getPartialObject(ctx context.Context, key *resourcepb.ResourceKey, rv int64) (utils.GrafanaMetaAccessor, *resourcepb.ErrorResult) { if r := verifyRequestKey(key); r != nil { return nil, r diff --git a/pkg/storage/unified/resource/server_test.go b/pkg/storage/unified/resource/server_test.go index 504c49616f9..b4ab0cdff2a 100644 --- a/pkg/storage/unified/resource/server_test.go +++ b/pkg/storage/unified/resource/server_test.go @@ -5,6 +5,8 @@ import ( "encoding/json" "errors" "net/http" + "os" + "path/filepath" "strings" "sync" "testing" @@ -22,6 +24,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/util/scheduler" ) @@ -614,3 +617,70 @@ func TestArtificialDelayAfterSuccessfulOperation(t *testing.T) { check(t, false, &resourcepb.UpdateResponse{Error: AsErrorResult(errors.New("some error"))}, nil) check(t, false, &resourcepb.DeleteResponse{Error: AsErrorResult(errors.New("some error"))}, nil) } + +func TestGetQuotaUsage(t *testing.T) { + ctx := context.Background() + + t.Run("returns error when overrides service is not configured", func(t *testing.T) { + s := &server{ + overridesService: nil, + log: log.NewNopLogger(), + } + + resp, err := s.GetQuotaUsage(ctx, &resourcepb.QuotaUsageRequest{ + Key: &resourcepb.ResourceKey{ + Namespace: "stacks-123", + Group: "dashboard.grafana.app", + Resource: "dashboards", + }, + }) + require.NoError(t, err) + require.NotNil(t, resp.Error) + assert.Equal(t, int32(http.StatusNotImplemented), resp.Error.Code) + assert.Equal(t, "overrides service not configured on resource server", resp.Error.Message) + }) + + t.Run("returns usage and limit successfully", func(t *testing.T) { + // Create a temporary overrides config file + tmpFile := filepath.Join(t.TempDir(), "overrides.yaml") + content := `"123": + quotas: + dashboard.grafana.app/dashboards: + limit: 500 +` + require.NoError(t, os.WriteFile(tmpFile, []byte(content), 0644)) + + // Create a real OverridesService with the temp file + overridesService, err := NewOverridesService(ctx, log.NewNopLogger(), prometheus.NewRegistry(), tracing.NewNoopTracerService(), ReloadOptions{ + FilePath: tmpFile, + }) + require.NoError(t, err) + require.NoError(t, overridesService.init(ctx)) + defer func() { + _ = overridesService.stop(ctx) + }() + + // Create a mock backend that returns resource stats (reusing mockStorageBackend from search_test.go) + mockBackend := &mockStorageBackend{ + resourceStats: []ResourceStats{{Count: 42}}, + } + + s := &server{ + backend: mockBackend, + overridesService: overridesService, + log: log.NewNopLogger(), + } + + resp, err := s.GetQuotaUsage(ctx, &resourcepb.QuotaUsageRequest{ + Key: &resourcepb.ResourceKey{ + Namespace: "stacks-123", + Group: "dashboard.grafana.app", + Resource: "dashboards", + }, + }) + require.NoError(t, err) + require.Nil(t, resp.Error) + assert.Equal(t, int64(42), resp.Usage) + assert.Equal(t, int64(500), resp.Limit) + }) +} diff --git a/pkg/storage/unified/resourcepb/resource.pb.go b/pkg/storage/unified/resourcepb/resource.pb.go index 8c6fb47b491..8046b354ccd 100644 --- a/pkg/storage/unified/resourcepb/resource.pb.go +++ b/pkg/storage/unified/resourcepb/resource.pb.go @@ -2484,6 +2484,114 @@ func (x *ResourceTableRow) GetObject() []byte { return nil } +type QuotaUsageRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Namespace (tenant) + Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *QuotaUsageRequest) Reset() { + *x = QuotaUsageRequest{} + mi := &file_resource_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *QuotaUsageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QuotaUsageRequest) ProtoMessage() {} + +func (x *QuotaUsageRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QuotaUsageRequest.ProtoReflect.Descriptor instead. +func (*QuotaUsageRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{30} +} + +func (x *QuotaUsageRequest) GetKey() *ResourceKey { + if x != nil { + return x.Key + } + return nil +} + +type QuotaUsageResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Error details + Error *ErrorResult `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + // Current usage + Usage int64 `protobuf:"varint,2,opt,name=usage,proto3" json:"usage,omitempty"` + // Current limit + Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *QuotaUsageResponse) Reset() { + *x = QuotaUsageResponse{} + mi := &file_resource_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *QuotaUsageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QuotaUsageResponse) ProtoMessage() {} + +func (x *QuotaUsageResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QuotaUsageResponse.ProtoReflect.Descriptor instead. +func (*QuotaUsageResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{31} +} + +func (x *QuotaUsageResponse) GetError() *ErrorResult { + if x != nil { + return x.Error + } + return nil +} + +func (x *QuotaUsageResponse) GetUsage() int64 { + if x != nil { + return x.Usage + } + return 0 +} + +func (x *QuotaUsageResponse) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + type WatchEvent_Resource struct { state protoimpl.MessageState `protogen:"open.v1"` Version int64 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` @@ -2494,7 +2602,7 @@ type WatchEvent_Resource struct { func (x *WatchEvent_Resource) Reset() { *x = WatchEvent_Resource{} - mi := &file_resource_proto_msgTypes[30] + mi := &file_resource_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2506,7 +2614,7 @@ func (x *WatchEvent_Resource) String() string { func (*WatchEvent_Resource) ProtoMessage() {} func (x *WatchEvent_Resource) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[30] + mi := &file_resource_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2553,7 +2661,7 @@ type BulkResponse_Summary struct { func (x *BulkResponse_Summary) Reset() { *x = BulkResponse_Summary{} - mi := &file_resource_proto_msgTypes[31] + mi := &file_resource_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2565,7 +2673,7 @@ func (x *BulkResponse_Summary) String() string { func (*BulkResponse_Summary) ProtoMessage() {} func (x *BulkResponse_Summary) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[31] + mi := &file_resource_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2649,7 +2757,7 @@ type BulkResponse_Rejected struct { func (x *BulkResponse_Rejected) Reset() { *x = BulkResponse_Rejected{} - mi := &file_resource_proto_msgTypes[32] + mi := &file_resource_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2661,7 +2769,7 @@ func (x *BulkResponse_Rejected) String() string { func (*BulkResponse_Rejected) ProtoMessage() {} func (x *BulkResponse_Rejected) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[32] + mi := &file_resource_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2718,7 +2826,7 @@ type ListManagedObjectsResponse_Item struct { func (x *ListManagedObjectsResponse_Item) Reset() { *x = ListManagedObjectsResponse_Item{} - mi := &file_resource_proto_msgTypes[33] + mi := &file_resource_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2730,7 +2838,7 @@ func (x *ListManagedObjectsResponse_Item) String() string { func (*ListManagedObjectsResponse_Item) ProtoMessage() {} func (x *ListManagedObjectsResponse_Item) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[33] + mi := &file_resource_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2801,7 +2909,7 @@ type CountManagedObjectsResponse_ResourceCount struct { func (x *CountManagedObjectsResponse_ResourceCount) Reset() { *x = CountManagedObjectsResponse_ResourceCount{} - mi := &file_resource_proto_msgTypes[34] + mi := &file_resource_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2813,7 +2921,7 @@ func (x *CountManagedObjectsResponse_ResourceCount) String() string { func (*CountManagedObjectsResponse_ResourceCount) ProtoMessage() {} func (x *CountManagedObjectsResponse_ResourceCount) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[34] + mi := &file_resource_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2886,7 +2994,7 @@ type ResourceTableColumnDefinition_Properties struct { func (x *ResourceTableColumnDefinition_Properties) Reset() { *x = ResourceTableColumnDefinition_Properties{} - mi := &file_resource_proto_msgTypes[35] + mi := &file_resource_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2898,7 +3006,7 @@ func (x *ResourceTableColumnDefinition_Properties) String() string { func (*ResourceTableColumnDefinition_Properties) ProtoMessage() {} func (x *ResourceTableColumnDefinition_Properties) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[35] + mi := &file_resource_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3320,68 +3428,85 @@ var file_resource_proto_rawDesc = string([]byte{ 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2a, 0x49, - 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x50, 0x52, 0x45, 0x43, - 0x41, 0x54, 0x45, 0x44, 0x5f, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, - 0x6e, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x44, 0x45, 0x50, 0x52, 0x45, 0x43, 0x41, 0x54, 0x45, - 0x44, 0x5f, 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x01, 0x2a, 0x4d, 0x0a, 0x16, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, - 0x68, 0x56, 0x32, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, - 0x12, 0x09, 0x0a, 0x05, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, - 0x78, 0x61, 0x63, 0x74, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, - 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x03, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, - 0x61, 0x64, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, - 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, - 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, - 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x37, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x14, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, - 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x32, 0x4b, 0x0a, 0x09, 0x42, 0x75, 0x6c, 0x6b, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x42, 0x75, 0x6c, 0x6b, 0x50, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x28, 0x01, 0x32, 0xd9, 0x01, 0x0a, 0x12, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x62, 0x0a, 0x13, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x12, 0x24, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x5f, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x32, 0x57, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, - 0x12, 0x48, 0x0a, 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, - 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3c, + 0x0a, 0x11, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x6d, 0x0a, 0x12, + 0x51, 0x75, 0x6f, 0x74, 0x61, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x14, 0x0a, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x75, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2a, 0x49, 0x0a, 0x14, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, + 0x74, 0x63, 0x68, 0x12, 0x1b, 0x0a, 0x17, 0x44, 0x45, 0x50, 0x52, 0x45, 0x43, 0x41, 0x54, 0x45, + 0x44, 0x5f, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, + 0x12, 0x14, 0x0a, 0x10, 0x44, 0x45, 0x50, 0x52, 0x45, 0x43, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x45, + 0x78, 0x61, 0x63, 0x74, 0x10, 0x01, 0x2a, 0x4d, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x56, 0x32, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, + 0x05, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, + 0x74, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, + 0x68, 0x61, 0x6e, 0x10, 0x03, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, + 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x30, 0x01, 0x32, 0x4b, 0x0a, 0x09, 0x42, 0x75, 0x6c, 0x6b, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x42, 0x75, 0x6c, 0x6b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x42, 0x75, 0x6c, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x28, 0x01, 0x32, 0xd9, 0x01, 0x0a, 0x12, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x62, 0x0a, 0x13, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x12, 0x24, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5f, 0x0a, + 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x57, + 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, + 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x54, 0x0a, 0x06, 0x51, 0x75, 0x6f, 0x74, 0x61, + 0x73, 0x12, 0x4a, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x55, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x51, 0x75, + 0x6f, 0x74, 0x61, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x51, 0x75, 0x6f, 0x74, 0x61, + 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3b, 0x5a, + 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, + 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, }) var ( @@ -3397,7 +3522,7 @@ func file_resource_proto_rawDescGZIP() []byte { } var file_resource_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 38) var file_resource_proto_goTypes = []any{ (ResourceVersionMatch)(0), // 0: resource.ResourceVersionMatch (ResourceVersionMatchV2)(0), // 1: resource.ResourceVersionMatchV2 @@ -3436,12 +3561,14 @@ var file_resource_proto_goTypes = []any{ (*ResourceTable)(nil), // 34: resource.ResourceTable (*ResourceTableColumnDefinition)(nil), // 35: resource.ResourceTableColumnDefinition (*ResourceTableRow)(nil), // 36: resource.ResourceTableRow - (*WatchEvent_Resource)(nil), // 37: resource.WatchEvent.Resource - (*BulkResponse_Summary)(nil), // 38: resource.BulkResponse.Summary - (*BulkResponse_Rejected)(nil), // 39: resource.BulkResponse.Rejected - (*ListManagedObjectsResponse_Item)(nil), // 40: resource.ListManagedObjectsResponse.Item - (*CountManagedObjectsResponse_ResourceCount)(nil), // 41: resource.CountManagedObjectsResponse.ResourceCount - (*ResourceTableColumnDefinition_Properties)(nil), // 42: resource.ResourceTableColumnDefinition.Properties + (*QuotaUsageRequest)(nil), // 37: resource.QuotaUsageRequest + (*QuotaUsageResponse)(nil), // 38: resource.QuotaUsageResponse + (*WatchEvent_Resource)(nil), // 39: resource.WatchEvent.Resource + (*BulkResponse_Summary)(nil), // 40: resource.BulkResponse.Summary + (*BulkResponse_Rejected)(nil), // 41: resource.BulkResponse.Rejected + (*ListManagedObjectsResponse_Item)(nil), // 42: resource.ListManagedObjectsResponse.Item + (*CountManagedObjectsResponse_ResourceCount)(nil), // 43: resource.CountManagedObjectsResponse.ResourceCount + (*ResourceTableColumnDefinition_Properties)(nil), // 44: resource.ResourceTableColumnDefinition.Properties } var file_resource_proto_depIdxs = []int32{ 10, // 0: resource.ErrorResult.details:type_name -> resource.ErrorDetails @@ -3465,51 +3592,55 @@ var file_resource_proto_depIdxs = []int32{ 9, // 18: resource.ListResponse.error:type_name -> resource.ErrorResult 21, // 19: resource.WatchRequest.options:type_name -> resource.ListOptions 3, // 20: resource.WatchEvent.type:type_name -> resource.WatchEvent.Type - 37, // 21: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource - 37, // 22: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource + 39, // 21: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource + 39, // 22: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource 7, // 23: resource.BulkRequest.key:type_name -> resource.ResourceKey 4, // 24: resource.BulkRequest.action:type_name -> resource.BulkRequest.Action 9, // 25: resource.BulkResponse.error:type_name -> resource.ErrorResult - 38, // 26: resource.BulkResponse.summary:type_name -> resource.BulkResponse.Summary - 39, // 27: resource.BulkResponse.rejected:type_name -> resource.BulkResponse.Rejected - 40, // 28: resource.ListManagedObjectsResponse.items:type_name -> resource.ListManagedObjectsResponse.Item + 40, // 26: resource.BulkResponse.summary:type_name -> resource.BulkResponse.Summary + 41, // 27: resource.BulkResponse.rejected:type_name -> resource.BulkResponse.Rejected + 42, // 28: resource.ListManagedObjectsResponse.items:type_name -> resource.ListManagedObjectsResponse.Item 9, // 29: resource.ListManagedObjectsResponse.error:type_name -> resource.ErrorResult - 41, // 30: resource.CountManagedObjectsResponse.items:type_name -> resource.CountManagedObjectsResponse.ResourceCount + 43, // 30: resource.CountManagedObjectsResponse.items:type_name -> resource.CountManagedObjectsResponse.ResourceCount 9, // 31: resource.CountManagedObjectsResponse.error:type_name -> resource.ErrorResult 5, // 32: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus 35, // 33: resource.ResourceTable.columns:type_name -> resource.ResourceTableColumnDefinition 36, // 34: resource.ResourceTable.rows:type_name -> resource.ResourceTableRow 6, // 35: resource.ResourceTableColumnDefinition.type:type_name -> resource.ResourceTableColumnDefinition.ColumnType - 42, // 36: resource.ResourceTableColumnDefinition.properties:type_name -> resource.ResourceTableColumnDefinition.Properties + 44, // 36: resource.ResourceTableColumnDefinition.properties:type_name -> resource.ResourceTableColumnDefinition.Properties 7, // 37: resource.ResourceTableRow.key:type_name -> resource.ResourceKey - 7, // 38: resource.BulkResponse.Rejected.key:type_name -> resource.ResourceKey - 4, // 39: resource.BulkResponse.Rejected.action:type_name -> resource.BulkRequest.Action - 7, // 40: resource.ListManagedObjectsResponse.Item.object:type_name -> resource.ResourceKey - 18, // 41: resource.ResourceStore.Read:input_type -> resource.ReadRequest - 12, // 42: resource.ResourceStore.Create:input_type -> resource.CreateRequest - 14, // 43: resource.ResourceStore.Update:input_type -> resource.UpdateRequest - 16, // 44: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest - 22, // 45: resource.ResourceStore.List:input_type -> resource.ListRequest - 24, // 46: resource.ResourceStore.Watch:input_type -> resource.WatchRequest - 26, // 47: resource.BulkStore.BulkProcess:input_type -> resource.BulkRequest - 30, // 48: resource.ManagedObjectIndex.CountManagedObjects:input_type -> resource.CountManagedObjectsRequest - 28, // 49: resource.ManagedObjectIndex.ListManagedObjects:input_type -> resource.ListManagedObjectsRequest - 32, // 50: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest - 19, // 51: resource.ResourceStore.Read:output_type -> resource.ReadResponse - 13, // 52: resource.ResourceStore.Create:output_type -> resource.CreateResponse - 15, // 53: resource.ResourceStore.Update:output_type -> resource.UpdateResponse - 17, // 54: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse - 23, // 55: resource.ResourceStore.List:output_type -> resource.ListResponse - 25, // 56: resource.ResourceStore.Watch:output_type -> resource.WatchEvent - 27, // 57: resource.BulkStore.BulkProcess:output_type -> resource.BulkResponse - 31, // 58: resource.ManagedObjectIndex.CountManagedObjects:output_type -> resource.CountManagedObjectsResponse - 29, // 59: resource.ManagedObjectIndex.ListManagedObjects:output_type -> resource.ListManagedObjectsResponse - 33, // 60: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse - 51, // [51:61] is the sub-list for method output_type - 41, // [41:51] is the sub-list for method input_type - 41, // [41:41] is the sub-list for extension type_name - 41, // [41:41] is the sub-list for extension extendee - 0, // [0:41] is the sub-list for field type_name + 7, // 38: resource.QuotaUsageRequest.key:type_name -> resource.ResourceKey + 9, // 39: resource.QuotaUsageResponse.error:type_name -> resource.ErrorResult + 7, // 40: resource.BulkResponse.Rejected.key:type_name -> resource.ResourceKey + 4, // 41: resource.BulkResponse.Rejected.action:type_name -> resource.BulkRequest.Action + 7, // 42: resource.ListManagedObjectsResponse.Item.object:type_name -> resource.ResourceKey + 18, // 43: resource.ResourceStore.Read:input_type -> resource.ReadRequest + 12, // 44: resource.ResourceStore.Create:input_type -> resource.CreateRequest + 14, // 45: resource.ResourceStore.Update:input_type -> resource.UpdateRequest + 16, // 46: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest + 22, // 47: resource.ResourceStore.List:input_type -> resource.ListRequest + 24, // 48: resource.ResourceStore.Watch:input_type -> resource.WatchRequest + 26, // 49: resource.BulkStore.BulkProcess:input_type -> resource.BulkRequest + 30, // 50: resource.ManagedObjectIndex.CountManagedObjects:input_type -> resource.CountManagedObjectsRequest + 28, // 51: resource.ManagedObjectIndex.ListManagedObjects:input_type -> resource.ListManagedObjectsRequest + 32, // 52: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest + 37, // 53: resource.Quotas.GetQuotaUsage:input_type -> resource.QuotaUsageRequest + 19, // 54: resource.ResourceStore.Read:output_type -> resource.ReadResponse + 13, // 55: resource.ResourceStore.Create:output_type -> resource.CreateResponse + 15, // 56: resource.ResourceStore.Update:output_type -> resource.UpdateResponse + 17, // 57: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse + 23, // 58: resource.ResourceStore.List:output_type -> resource.ListResponse + 25, // 59: resource.ResourceStore.Watch:output_type -> resource.WatchEvent + 27, // 60: resource.BulkStore.BulkProcess:output_type -> resource.BulkResponse + 31, // 61: resource.ManagedObjectIndex.CountManagedObjects:output_type -> resource.CountManagedObjectsResponse + 29, // 62: resource.ManagedObjectIndex.ListManagedObjects:output_type -> resource.ListManagedObjectsResponse + 33, // 63: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse + 38, // 64: resource.Quotas.GetQuotaUsage:output_type -> resource.QuotaUsageResponse + 54, // [54:65] is the sub-list for method output_type + 43, // [43:54] is the sub-list for method input_type + 43, // [43:43] is the sub-list for extension type_name + 43, // [43:43] is the sub-list for extension extendee + 0, // [0:43] is the sub-list for field type_name } func init() { file_resource_proto_init() } @@ -3524,9 +3655,9 @@ func file_resource_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_resource_proto_rawDesc), len(file_resource_proto_rawDesc)), NumEnums: 7, - NumMessages: 36, + NumMessages: 38, NumExtensions: 0, - NumServices: 4, + NumServices: 5, }, GoTypes: file_resource_proto_goTypes, DependencyIndexes: file_resource_proto_depIdxs, diff --git a/pkg/storage/unified/resourcepb/resource_grpc.pb.go b/pkg/storage/unified/resourcepb/resource_grpc.pb.go index c90aa0f26a6..a37ab264d76 100644 --- a/pkg/storage/unified/resourcepb/resource_grpc.pb.go +++ b/pkg/storage/unified/resourcepb/resource_grpc.pb.go @@ -709,3 +709,94 @@ var Diagnostics_ServiceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, Metadata: "resource.proto", } + +const ( + Quotas_GetQuotaUsage_FullMethodName = "/resource.Quotas/GetQuotaUsage" +) + +// QuotasClient is the client API for Quotas service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type QuotasClient interface { + // Get current quota usage and limits + GetQuotaUsage(ctx context.Context, in *QuotaUsageRequest, opts ...grpc.CallOption) (*QuotaUsageResponse, error) +} + +type quotasClient struct { + cc grpc.ClientConnInterface +} + +func NewQuotasClient(cc grpc.ClientConnInterface) QuotasClient { + return "asClient{cc} +} + +func (c *quotasClient) GetQuotaUsage(ctx context.Context, in *QuotaUsageRequest, opts ...grpc.CallOption) (*QuotaUsageResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(QuotaUsageResponse) + err := c.cc.Invoke(ctx, Quotas_GetQuotaUsage_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QuotasServer is the server API for Quotas service. +// All implementations should embed UnimplementedQuotasServer +// for forward compatibility +type QuotasServer interface { + // Get current quota usage and limits + GetQuotaUsage(context.Context, *QuotaUsageRequest) (*QuotaUsageResponse, error) +} + +// UnimplementedQuotasServer should be embedded to have forward compatible implementations. +type UnimplementedQuotasServer struct { +} + +func (UnimplementedQuotasServer) GetQuotaUsage(context.Context, *QuotaUsageRequest) (*QuotaUsageResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetQuotaUsage not implemented") +} + +// UnsafeQuotasServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to QuotasServer will +// result in compilation errors. +type UnsafeQuotasServer interface { + mustEmbedUnimplementedQuotasServer() +} + +func RegisterQuotasServer(s grpc.ServiceRegistrar, srv QuotasServer) { + s.RegisterService(&Quotas_ServiceDesc, srv) +} + +func _Quotas_GetQuotaUsage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QuotaUsageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QuotasServer).GetQuotaUsage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Quotas_GetQuotaUsage_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QuotasServer).GetQuotaUsage(ctx, req.(*QuotaUsageRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Quotas_ServiceDesc is the grpc.ServiceDesc for Quotas service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Quotas_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "resource.Quotas", + HandlerType: (*QuotasServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetQuotaUsage", + Handler: _Quotas_GetQuotaUsage_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "resource.proto", +} diff --git a/pkg/storage/unified/sql/service.go b/pkg/storage/unified/sql/service.go index 22ef4f5daf5..75b3e80fcb0 100644 --- a/pkg/storage/unified/sql/service.go +++ b/pkg/storage/unified/sql/service.go @@ -312,6 +312,7 @@ func (s *service) starting(ctx context.Context) error { resourcepb.RegisterManagedObjectIndexServer(srv, server) resourcepb.RegisterBlobStoreServer(srv, server) resourcepb.RegisterDiagnosticsServer(srv, server) + resourcepb.RegisterQuotasServer(srv, server) grpc_health_v1.RegisterHealthServer(srv, healthService) // register reflection service