Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ffc20df673 | |||
| 8f4fa9ed05 | |||
| 0aae7e01bc | |||
| 58e9e4a56d | |||
| dff9bea3e8 | |||
| 19cfab89f3 | |||
| 088bab8b38 | |||
| 9e8bdee283 | |||
| bb5bb00e4d | |||
| 5fcc67837a | |||
| 79f2016a66 | |||
| 7858dcb9c1 | |||
| 27eb488a96 | |||
| 97af86efb2 | |||
| f58ab2a6a1 | |||
| b96a1ae722 | |||
| a53875e621 | |||
| 9598ae6434 | |||
| ab0b05550f | |||
| 4518add556 | |||
| 00b89b0d29 | |||
| a3eedfeb73 | |||
| 1e8f1f74ea | |||
| 66b05914e2 | |||
| 0c60d356d1 | |||
| 41d7213d7e | |||
| efad6c7be0 | |||
| e116254f32 |
@@ -94,7 +94,6 @@
|
|||||||
/apps/shorturl/ @grafana/sharing-squad
|
/apps/shorturl/ @grafana/sharing-squad
|
||||||
/apps/secret/ @grafana/grafana-operator-experience-squad
|
/apps/secret/ @grafana/grafana-operator-experience-squad
|
||||||
/apps/scope/ @grafana/grafana-operator-experience-squad
|
/apps/scope/ @grafana/grafana-operator-experience-squad
|
||||||
/apps/investigations/ @fcjack @matryer @svennergr
|
|
||||||
/apps/advisor/ @grafana/plugins-platform-backend
|
/apps/advisor/ @grafana/plugins-platform-backend
|
||||||
/apps/iam/ @grafana/access-squad
|
/apps/iam/ @grafana/access-squad
|
||||||
/apps/sdk.mk @grafana/grafana-app-platform-squad
|
/apps/sdk.mk @grafana/grafana-app-platform-squad
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ updates:
|
|||||||
- "/apps/dashboard"
|
- "/apps/dashboard"
|
||||||
- "/apps/folder"
|
- "/apps/folder"
|
||||||
- "/apps/iam"
|
- "/apps/iam"
|
||||||
- "/apps/investigations"
|
|
||||||
- "/apps/playlist"
|
- "/apps/playlist"
|
||||||
- "/apps/plugins"
|
- "/apps/plugins"
|
||||||
- "/apps/preferences"
|
- "/apps/preferences"
|
||||||
|
|||||||
@@ -67,14 +67,6 @@ linters:
|
|||||||
deny:
|
deny:
|
||||||
- pkg: github.com/grafana/grafana/pkg
|
- pkg: github.com/grafana/grafana/pkg
|
||||||
desc: apiserver is not allowed to import grafana core
|
desc: apiserver is not allowed to import grafana core
|
||||||
apps-investigation:
|
|
||||||
list-mode: lax
|
|
||||||
files:
|
|
||||||
- ./apps/investigations/*
|
|
||||||
- ./apps/investigations/**/*
|
|
||||||
deny:
|
|
||||||
- pkg: github.com/grafana/grafana/pkg
|
|
||||||
desc: apps/investigations is not allowed to import grafana core
|
|
||||||
apps-playlist:
|
apps-playlist:
|
||||||
list-mode: lax
|
list-mode: lax
|
||||||
files:
|
files:
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ COPY apps/collections apps/collections
|
|||||||
COPY apps/provisioning apps/provisioning
|
COPY apps/provisioning apps/provisioning
|
||||||
COPY apps/secret apps/secret
|
COPY apps/secret apps/secret
|
||||||
COPY apps/scope apps/scope
|
COPY apps/scope apps/scope
|
||||||
COPY apps/investigations apps/investigations
|
|
||||||
COPY apps/logsdrilldown apps/logsdrilldown
|
COPY apps/logsdrilldown apps/logsdrilldown
|
||||||
COPY apps/advisor apps/advisor
|
COPY apps/advisor apps/advisor
|
||||||
COPY apps/dashboard apps/dashboard
|
COPY apps/dashboard apps/dashboard
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ replace github.com/grafana/grafana/apps/alerting/historian => ../alerting/histor
|
|||||||
|
|
||||||
replace github.com/grafana/grafana/apps/correlations => ../correlations
|
replace github.com/grafana/grafana/apps/correlations => ../correlations
|
||||||
|
|
||||||
replace github.com/grafana/grafana/apps/investigations => ../investigations
|
|
||||||
|
|
||||||
replace github.com/grafana/grafana/apps/logsdrilldown => ../logsdrilldown
|
replace github.com/grafana/grafana/apps/logsdrilldown => ../logsdrilldown
|
||||||
|
|
||||||
replace github.com/grafana/grafana/apps/playlist => ../playlist
|
replace github.com/grafana/grafana/apps/playlist => ../playlist
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
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 \
|
|
||||||
--genoperatorstate=false \
|
|
||||||
--defencoding=none
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "896312ce-65b0-4b50-ade1-e7f04fa22c66",
|
|
||||||
"title": "Thursday morning investigation",
|
|
||||||
"hasCustomName": false,
|
|
||||||
"isFavorite": false,
|
|
||||||
"collectables": [
|
|
||||||
{
|
|
||||||
"origin": "Explore Logs",
|
|
||||||
"type": "timeseries",
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"refId": "LABEL_BREAKDOWN_VALUES",
|
|
||||||
"queryType": "range",
|
|
||||||
"editorMode": "code",
|
|
||||||
"supportingQueryType": "grafana-lokiexplore-app",
|
|
||||||
"legendFormat": "{{detected_level}}",
|
|
||||||
"expr": "sum(count_over_time({service_name=\"web_app_1\"} | detected_level != \"\"[$__auto])) by (detected_level)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeRange": {
|
|
||||||
"to": "2025-02-13T11:31:20.536Z",
|
|
||||||
"from": "2025-02-13T11:16:20.536Z",
|
|
||||||
"raw": {
|
|
||||||
"from": "now-15m",
|
|
||||||
"to": "now"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"uid": "fe9k7u07b1a0wc"
|
|
||||||
},
|
|
||||||
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
|
|
||||||
"id": "LABEL_BREAKDOWN_VALUES_detected_level",
|
|
||||||
"title": "detected_level",
|
|
||||||
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
|
|
||||||
"createdAt": "2025-02-13T11:31:23.637Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"createdAt": "2025-02-13T11:31:23.636Z",
|
|
||||||
"updatedAt": "2025-02-13T11:31:23.637Z",
|
|
||||||
"viewMode": {
|
|
||||||
"mode": "compact",
|
|
||||||
"showComments": true,
|
|
||||||
"showTooltips": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e9cf1958-d0ed-46b7-b597-9052c7648656",
|
|
||||||
"title": "Thursday morning investigation",
|
|
||||||
"hasCustomName": false,
|
|
||||||
"isFavorite": false,
|
|
||||||
"collectables": [
|
|
||||||
{
|
|
||||||
"origin": "Explore Logs",
|
|
||||||
"type": "timeseries",
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"refId": "LABEL_BREAKDOWN_VALUES",
|
|
||||||
"queryType": "range",
|
|
||||||
"editorMode": "code",
|
|
||||||
"supportingQueryType": "grafana-lokiexplore-app",
|
|
||||||
"legendFormat": "{{detected_level}}",
|
|
||||||
"expr": "sum(count_over_time({service_name=\"web_app_1\"} | detected_level != \"\"[$__auto])) by (detected_level)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeRange": {
|
|
||||||
"to": "2025-02-13T11:31:20.536Z",
|
|
||||||
"from": "2025-02-13T11:16:20.536Z",
|
|
||||||
"raw": {
|
|
||||||
"from": "now-15m",
|
|
||||||
"to": "now"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"uid": "fe9k7u07b1a0wc"
|
|
||||||
},
|
|
||||||
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
|
|
||||||
"id": "LABEL_BREAKDOWN_VALUES_detected_level",
|
|
||||||
"title": "detected_level",
|
|
||||||
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
|
|
||||||
"createdAt": "2025-02-13T11:31:23.638Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "Explore Logs",
|
|
||||||
"type": "timeseries",
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"refId": "LABEL_BREAKDOWN_VALUES",
|
|
||||||
"queryType": "range",
|
|
||||||
"editorMode": "code",
|
|
||||||
"supportingQueryType": "grafana-lokiexplore-app",
|
|
||||||
"legendFormat": "{{service_name}}",
|
|
||||||
"expr": "sum(count_over_time({service_name=\"web_app_1\",service_name != \"\"} [$__auto])) by (service_name)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeRange": {
|
|
||||||
"to": "2025-02-13T11:31:20.536Z",
|
|
||||||
"from": "2025-02-13T11:16:20.536Z",
|
|
||||||
"raw": {
|
|
||||||
"from": "now-15m",
|
|
||||||
"to": "now"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"uid": "fe9k7u07b1a0wc"
|
|
||||||
},
|
|
||||||
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
|
|
||||||
"id": "LABEL_BREAKDOWN_VALUES_service_name",
|
|
||||||
"title": "service_name",
|
|
||||||
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
|
|
||||||
"createdAt": "2025-02-13T11:31:41.507Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "Explore Logs",
|
|
||||||
"type": "timeseries",
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"refId": "LABEL_BREAKDOWN_VALUES",
|
|
||||||
"queryType": "range",
|
|
||||||
"editorMode": "code",
|
|
||||||
"supportingQueryType": "grafana-lokiexplore-app",
|
|
||||||
"legendFormat": "{{service}}",
|
|
||||||
"expr": "sum(count_over_time({service_name=\"web_app_1\",service != \"\"} [$__auto])) by (service)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeRange": {
|
|
||||||
"to": "2025-02-13T11:31:20.536Z",
|
|
||||||
"from": "2025-02-13T11:16:20.536Z",
|
|
||||||
"raw": {
|
|
||||||
"from": "now-15m",
|
|
||||||
"to": "now"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"uid": "fe9k7u07b1a0wc"
|
|
||||||
},
|
|
||||||
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
|
|
||||||
"id": "LABEL_BREAKDOWN_VALUES_service",
|
|
||||||
"title": "service",
|
|
||||||
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
|
|
||||||
"createdAt": "2025-02-13T11:31:43.698Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"createdAt": "2025-02-13T11:31:23.637Z",
|
|
||||||
"updatedAt": "2025-02-13T11:31:43.698Z",
|
|
||||||
"viewMode": {
|
|
||||||
"mode": "compact",
|
|
||||||
"showComments": true,
|
|
||||||
"showTooltips": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
module github.com/grafana/grafana/apps/investigations
|
|
||||||
|
|
||||||
go 1.25.5
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/grafana/grafana-app-sdk v0.48.7
|
|
||||||
k8s.io/apimachinery v0.34.3
|
|
||||||
k8s.io/klog/v2 v2.130.1
|
|
||||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // 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.4 // indirect
|
|
||||||
github.com/go-openapi/jsonreference v0.21.4 // indirect
|
|
||||||
github.com/go-openapi/swag v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/conv v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/loading v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/mangling v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/netutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
|
||||||
github.com/go-test/deep v1.1.1 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/google/gnostic-models v0.7.1 // indirect
|
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
|
||||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/mailru/easyjson v0.9.1 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
|
||||||
github.com/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.4 // indirect
|
|
||||||
github.com/prometheus/procfs v0.19.2 // indirect
|
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
|
||||||
github.com/woodsbury/decimal128 v1.4.0 // indirect
|
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
|
||||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
|
||||||
golang.org/x/net v0.48.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.34.0 // indirect
|
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
|
||||||
golang.org/x/term v0.38.0 // indirect
|
|
||||||
golang.org/x/text v0.32.0 // indirect
|
|
||||||
golang.org/x/time v0.14.0 // indirect
|
|
||||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
k8s.io/api v0.34.3 // indirect
|
|
||||||
k8s.io/apiextensions-apiserver v0.34.3 // indirect
|
|
||||||
k8s.io/client-go v0.34.3 // indirect
|
|
||||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
|
||||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
|
||||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
|
||||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
|
|
||||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
|
||||||
)
|
|
||||||
@@ -1,264 +0,0 @@
|
|||||||
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-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/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.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
|
||||||
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
|
|
||||||
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
|
|
||||||
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
|
|
||||||
github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
|
|
||||||
github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
|
|
||||||
github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
|
|
||||||
github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
|
|
||||||
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
|
|
||||||
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
|
|
||||||
github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
|
|
||||||
github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
|
|
||||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
|
||||||
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
|
||||||
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
|
|
||||||
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
|
|
||||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
|
|
||||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
|
|
||||||
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
|
|
||||||
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
|
|
||||||
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
|
|
||||||
github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
|
|
||||||
github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
|
|
||||||
github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
|
|
||||||
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
|
|
||||||
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
|
|
||||||
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
|
|
||||||
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
|
|
||||||
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
|
|
||||||
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
|
|
||||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
|
|
||||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
|
||||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
|
||||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
|
||||||
github.com/go-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.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
|
|
||||||
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/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.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo=
|
|
||||||
github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k=
|
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw=
|
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
|
||||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
|
||||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
|
||||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
|
||||||
github.com/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
|
||||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
|
||||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
|
||||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
|
||||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
|
||||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
|
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
|
||||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
|
||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
|
|
||||||
github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU=
|
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
|
||||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
|
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
|
||||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
|
||||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
|
||||||
golang.org/x/sync v0.19.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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
|
||||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
|
||||||
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-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4=
|
|
||||||
k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk=
|
|
||||||
k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g=
|
|
||||||
k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0=
|
|
||||||
k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE=
|
|
||||||
k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
|
||||||
k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A=
|
|
||||||
k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM=
|
|
||||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
|
||||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
|
||||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ=
|
|
||||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
|
||||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
|
||||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
|
||||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
|
||||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
|
||||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
|
||||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
|
||||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
|
||||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
// Collectable represents an item collected during investigation
|
|
||||||
#Collectable: {
|
|
||||||
id: string
|
|
||||||
createdAt: string
|
|
||||||
|
|
||||||
title: string
|
|
||||||
origin: string
|
|
||||||
type: string
|
|
||||||
queries: [...string] // +listType=atomic
|
|
||||||
timeRange: #TimeRange
|
|
||||||
datasource: #DatasourceRef
|
|
||||||
url: string
|
|
||||||
logoPath?: string
|
|
||||||
|
|
||||||
note: string
|
|
||||||
noteUpdatedAt: string
|
|
||||||
|
|
||||||
fieldConfig: string
|
|
||||||
}
|
|
||||||
|
|
||||||
#CollectableSummary: {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
logoPath: string
|
|
||||||
origin: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeRange represents a time range with both absolute and relative values
|
|
||||||
#TimeRange: {
|
|
||||||
from: string
|
|
||||||
to: string
|
|
||||||
raw: {
|
|
||||||
from: string
|
|
||||||
to: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatasourceRef is a reference to a datasource
|
|
||||||
#DatasourceRef: {
|
|
||||||
uid: string
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module: "github.com/grafana/grafana/apps/investigations"
|
|
||||||
language: {
|
|
||||||
version: "v0.11.0"
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
investigationV0alpha1: {
|
|
||||||
kind: "Investigation"
|
|
||||||
pluralName: "Investigations"
|
|
||||||
schema: {
|
|
||||||
spec: {
|
|
||||||
title: string
|
|
||||||
createdByProfile: #Person
|
|
||||||
hasCustomName: bool
|
|
||||||
isFavorite: bool
|
|
||||||
overviewNote: string
|
|
||||||
overviewNoteUpdatedAt: string
|
|
||||||
collectables: [...#Collectable] // +listType=atomic
|
|
||||||
viewMode: #ViewMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type definition for investigation summaries
|
|
||||||
#InvestigationSummary: {
|
|
||||||
title: string
|
|
||||||
createdByProfile: #Person
|
|
||||||
hasCustomName: bool
|
|
||||||
isFavorite: bool
|
|
||||||
overviewNote: string
|
|
||||||
overviewNoteUpdatedAt: string
|
|
||||||
viewMode: #ViewMode
|
|
||||||
collectableSummaries: [...#CollectableSummary] // +listType=atomic
|
|
||||||
}
|
|
||||||
|
|
||||||
// Person represents a user profile with basic information
|
|
||||||
#Person: {
|
|
||||||
uid: string // Unique identifier for the user
|
|
||||||
name: string // Display name of the user
|
|
||||||
gravatarUrl: string // URL to user's Gravatar image
|
|
||||||
}
|
|
||||||
|
|
||||||
#ViewMode: {
|
|
||||||
mode: "compact" | "full"
|
|
||||||
showComments: bool
|
|
||||||
showTooltips: bool
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
investigationIndexV0alpha1:{
|
|
||||||
kind: "InvestigationIndex"
|
|
||||||
pluralName: "InvestigationIndexes"
|
|
||||||
schema: {
|
|
||||||
spec: {
|
|
||||||
// Title of the index, e.g. 'Favorites' or 'My Investigations'
|
|
||||||
title: string
|
|
||||||
|
|
||||||
// The Person who owns this investigation index
|
|
||||||
owner: #Person
|
|
||||||
|
|
||||||
// Array of investigation summaries
|
|
||||||
investigationSummaries: [...#InvestigationSummary] // +listType=atomic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
manifest: {
|
|
||||||
appName: "investigations"
|
|
||||||
groupOverride: "investigations.grafana.app"
|
|
||||||
versions: {
|
|
||||||
"v0alpha1": {
|
|
||||||
codegen: {
|
|
||||||
ts: {enabled: false}
|
|
||||||
go: {enabled: true}
|
|
||||||
}
|
|
||||||
kinds: [
|
|
||||||
investigationV0alpha1,
|
|
||||||
investigationIndexV0alpha1,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package v0alpha1
|
|
||||||
|
|
||||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// APIGroup is the API group used by all kinds in this package
|
|
||||||
APIGroup = "investigations.grafana.app"
|
|
||||||
// APIVersion is the API version used by all kinds in this package
|
|
||||||
APIVersion = "v0alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
|
|
||||||
GroupVersion = schema.GroupVersion{
|
|
||||||
Group: APIGroup,
|
|
||||||
Version: APIVersion,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
-80
@@ -1,80 +0,0 @@
|
|||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InvestigationClient struct {
|
|
||||||
client *resource.TypedClient[*Investigation, *InvestigationList]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInvestigationClient(client resource.Client) *InvestigationClient {
|
|
||||||
return &InvestigationClient{
|
|
||||||
client: resource.NewTypedClient[*Investigation, *InvestigationList](client, InvestigationKind()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInvestigationClientFromGenerator(generator resource.ClientGenerator) (*InvestigationClient, error) {
|
|
||||||
c, err := generator.ClientFor(InvestigationKind())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewInvestigationClient(c), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Get(ctx context.Context, identifier resource.Identifier) (*Investigation, error) {
|
|
||||||
return c.client.Get(ctx, identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationList, error) {
|
|
||||||
return c.client.List(ctx, namespace, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationList, error) {
|
|
||||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
|
||||||
ResourceVersion: opts.ResourceVersion,
|
|
||||||
Limit: opts.Limit,
|
|
||||||
LabelFilters: opts.LabelFilters,
|
|
||||||
FieldSelectors: opts.FieldSelectors,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for resp.GetContinue() != "" {
|
|
||||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
|
||||||
Continue: resp.GetContinue(),
|
|
||||||
ResourceVersion: opts.ResourceVersion,
|
|
||||||
Limit: opts.Limit,
|
|
||||||
LabelFilters: opts.LabelFilters,
|
|
||||||
FieldSelectors: opts.FieldSelectors,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.SetContinue(page.GetContinue())
|
|
||||||
resp.SetResourceVersion(page.GetResourceVersion())
|
|
||||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Create(ctx context.Context, obj *Investigation, opts resource.CreateOptions) (*Investigation, error) {
|
|
||||||
// Make sure apiVersion and kind are set
|
|
||||||
obj.APIVersion = GroupVersion.Identifier()
|
|
||||||
obj.Kind = InvestigationKind().Kind()
|
|
||||||
return c.client.Create(ctx, obj, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Update(ctx context.Context, obj *Investigation, opts resource.UpdateOptions) (*Investigation, error) {
|
|
||||||
return c.client.Update(ctx, obj, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Investigation, error) {
|
|
||||||
return c.client.Patch(ctx, identifier, req, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
|
||||||
return c.client.Delete(ctx, identifier, opts)
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InvestigationJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
|
||||||
type InvestigationJSONCodec struct{}
|
|
||||||
|
|
||||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
|
||||||
func (*InvestigationJSONCodec) Read(reader io.Reader, into resource.Object) error {
|
|
||||||
return json.NewDecoder(reader).Decode(into)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
|
||||||
func (*InvestigationJSONCodec) Write(writer io.Writer, from resource.Object) error {
|
|
||||||
return json.NewEncoder(writer).Encode(from)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance checks
|
|
||||||
var _ resource.Codec = &InvestigationJSONCodec{}
|
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
time "time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
|
||||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
|
||||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
|
||||||
type InvestigationMetadata struct {
|
|
||||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
|
||||||
CreatedBy string `json:"createdBy"`
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
|
||||||
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
|
|
||||||
Finalizers []string `json:"finalizers"`
|
|
||||||
ResourceVersion string `json:"resourceVersion"`
|
|
||||||
Generation int64 `json:"generation"`
|
|
||||||
UpdatedBy string `json:"updatedBy"`
|
|
||||||
Labels map[string]string `json:"labels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationMetadata creates a new InvestigationMetadata object.
|
|
||||||
func NewInvestigationMetadata() *InvestigationMetadata {
|
|
||||||
return &InvestigationMetadata{
|
|
||||||
Finalizers: []string{},
|
|
||||||
Labels: map[string]string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-293
@@ -1,293 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type Investigation struct {
|
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
|
||||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
|
||||||
|
|
||||||
// Spec is the spec of the Investigation
|
|
||||||
Spec InvestigationSpec `json:"spec" yaml:"spec"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetSpec() any {
|
|
||||||
return o.Spec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetSpec(spec any) error {
|
|
||||||
cast, ok := spec.(InvestigationSpec)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
|
||||||
}
|
|
||||||
o.Spec = cast
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetSubresources() map[string]any {
|
|
||||||
return map[string]any{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetSubresource(name string) (any, bool) {
|
|
||||||
switch name {
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetSubresource(name string, value any) error {
|
|
||||||
switch name {
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetStaticMetadata() resource.StaticMetadata {
|
|
||||||
gvk := o.GroupVersionKind()
|
|
||||||
return resource.StaticMetadata{
|
|
||||||
Name: o.ObjectMeta.Name,
|
|
||||||
Namespace: o.ObjectMeta.Namespace,
|
|
||||||
Group: gvk.Group,
|
|
||||||
Version: gvk.Version,
|
|
||||||
Kind: gvk.Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetStaticMetadata(metadata resource.StaticMetadata) {
|
|
||||||
o.Name = metadata.Name
|
|
||||||
o.Namespace = metadata.Namespace
|
|
||||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
|
||||||
Group: metadata.Group,
|
|
||||||
Version: metadata.Version,
|
|
||||||
Kind: metadata.Kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetCommonMetadata() resource.CommonMetadata {
|
|
||||||
dt := o.DeletionTimestamp
|
|
||||||
var deletionTimestamp *time.Time
|
|
||||||
if dt != nil {
|
|
||||||
deletionTimestamp = &dt.Time
|
|
||||||
}
|
|
||||||
// Legacy ExtraFields support
|
|
||||||
extraFields := make(map[string]any)
|
|
||||||
if o.Annotations != nil {
|
|
||||||
extraFields["annotations"] = o.Annotations
|
|
||||||
}
|
|
||||||
if o.ManagedFields != nil {
|
|
||||||
extraFields["managedFields"] = o.ManagedFields
|
|
||||||
}
|
|
||||||
if o.OwnerReferences != nil {
|
|
||||||
extraFields["ownerReferences"] = o.OwnerReferences
|
|
||||||
}
|
|
||||||
return resource.CommonMetadata{
|
|
||||||
UID: string(o.UID),
|
|
||||||
ResourceVersion: o.ResourceVersion,
|
|
||||||
Generation: o.Generation,
|
|
||||||
Labels: o.Labels,
|
|
||||||
CreationTimestamp: o.CreationTimestamp.Time,
|
|
||||||
DeletionTimestamp: deletionTimestamp,
|
|
||||||
Finalizers: o.Finalizers,
|
|
||||||
UpdateTimestamp: o.GetUpdateTimestamp(),
|
|
||||||
CreatedBy: o.GetCreatedBy(),
|
|
||||||
UpdatedBy: o.GetUpdatedBy(),
|
|
||||||
ExtraFields: extraFields,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetCommonMetadata(metadata resource.CommonMetadata) {
|
|
||||||
o.UID = types.UID(metadata.UID)
|
|
||||||
o.ResourceVersion = metadata.ResourceVersion
|
|
||||||
o.Generation = metadata.Generation
|
|
||||||
o.Labels = metadata.Labels
|
|
||||||
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
|
|
||||||
if metadata.DeletionTimestamp != nil {
|
|
||||||
dt := metav1.NewTime(*metadata.DeletionTimestamp)
|
|
||||||
o.DeletionTimestamp = &dt
|
|
||||||
} else {
|
|
||||||
o.DeletionTimestamp = nil
|
|
||||||
}
|
|
||||||
o.Finalizers = metadata.Finalizers
|
|
||||||
if o.Annotations == nil {
|
|
||||||
o.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
if !metadata.UpdateTimestamp.IsZero() {
|
|
||||||
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
|
|
||||||
}
|
|
||||||
if metadata.CreatedBy != "" {
|
|
||||||
o.SetCreatedBy(metadata.CreatedBy)
|
|
||||||
}
|
|
||||||
if metadata.UpdatedBy != "" {
|
|
||||||
o.SetUpdatedBy(metadata.UpdatedBy)
|
|
||||||
}
|
|
||||||
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
|
|
||||||
if metadata.ExtraFields != nil {
|
|
||||||
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
|
|
||||||
if cast, ok := annotations.(map[string]string); ok {
|
|
||||||
o.Annotations = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
|
|
||||||
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
|
|
||||||
o.ManagedFields = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
|
|
||||||
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
|
|
||||||
o.OwnerReferences = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetCreatedBy() string {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetCreatedBy(createdBy string) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetUpdateTimestamp() time.Time {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetUpdateTimestamp(updateTimestamp time.Time) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetUpdatedBy() string {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetUpdatedBy(updatedBy string) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) Copy() resource.Object {
|
|
||||||
return resource.CopyObject(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) DeepCopyObject() runtime.Object {
|
|
||||||
return o.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) DeepCopy() *Investigation {
|
|
||||||
cpy := &Investigation{}
|
|
||||||
o.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) DeepCopyInto(dst *Investigation) {
|
|
||||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
|
||||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
|
||||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
|
||||||
o.Spec.DeepCopyInto(&dst.Spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
|
||||||
var _ resource.Object = &Investigation{}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationList struct {
|
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
|
||||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
|
||||||
Items []Investigation `json:"items" yaml:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) DeepCopyObject() runtime.Object {
|
|
||||||
return o.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) Copy() resource.ListObject {
|
|
||||||
cpy := &InvestigationList{
|
|
||||||
TypeMeta: o.TypeMeta,
|
|
||||||
Items: make([]Investigation, len(o.Items)),
|
|
||||||
}
|
|
||||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
|
||||||
for i := 0; i < len(o.Items); i++ {
|
|
||||||
if item, ok := o.Items[i].Copy().(*Investigation); ok {
|
|
||||||
cpy.Items[i] = *item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) GetItems() []resource.Object {
|
|
||||||
items := make([]resource.Object, len(o.Items))
|
|
||||||
for i := 0; i < len(o.Items); i++ {
|
|
||||||
items[i] = &o.Items[i]
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) SetItems(items []resource.Object) {
|
|
||||||
o.Items = make([]Investigation, len(items))
|
|
||||||
for i := 0; i < len(items); i++ {
|
|
||||||
o.Items[i] = *items[i].(*Investigation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) DeepCopy() *InvestigationList {
|
|
||||||
cpy := &InvestigationList{}
|
|
||||||
o.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) DeepCopyInto(dst *InvestigationList) {
|
|
||||||
resource.CopyObjectInto(dst, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
|
||||||
var _ resource.ListObject = &InvestigationList{}
|
|
||||||
|
|
||||||
// Copy methods for all subresource types
|
|
||||||
|
|
||||||
// DeepCopy creates a full deep copy of Spec
|
|
||||||
func (s *InvestigationSpec) DeepCopy() *InvestigationSpec {
|
|
||||||
cpy := &InvestigationSpec{}
|
|
||||||
s.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto deep copies Spec into another Spec object
|
|
||||||
func (s *InvestigationSpec) DeepCopyInto(dst *InvestigationSpec) {
|
|
||||||
resource.CopyObjectInto(dst, s)
|
|
||||||
}
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// schema is unexported to prevent accidental overwrites
|
|
||||||
var (
|
|
||||||
schemaInvestigation = resource.NewSimpleSchema("investigations.grafana.app", "v0alpha1", &Investigation{}, &InvestigationList{}, resource.WithKind("Investigation"),
|
|
||||||
resource.WithPlural("investigations"), resource.WithScope(resource.NamespacedScope))
|
|
||||||
kindInvestigation = resource.Kind{
|
|
||||||
Schema: schemaInvestigation,
|
|
||||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
|
||||||
resource.KindEncodingJSON: &InvestigationJSONCodec{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
|
||||||
func InvestigationKind() resource.Kind {
|
|
||||||
return kindInvestigation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schema returns a resource.SimpleSchema representation of Investigation
|
|
||||||
func InvestigationSchema() *resource.SimpleSchema {
|
|
||||||
return schemaInvestigation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance checks
|
|
||||||
var _ resource.Schema = kindInvestigation
|
|
||||||
-126
@@ -1,126 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
// Person represents a user profile with basic information
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationPerson struct {
|
|
||||||
// Unique identifier for the user
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
// Display name of the user
|
|
||||||
Name string `json:"name"`
|
|
||||||
// URL to user's Gravatar image
|
|
||||||
GravatarUrl string `json:"gravatarUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationPerson creates a new InvestigationPerson object.
|
|
||||||
func NewInvestigationPerson() *InvestigationPerson {
|
|
||||||
return &InvestigationPerson{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collectable represents an item collected during investigation
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationCollectable struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
CreatedAt string `json:"createdAt"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Origin string `json:"origin"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
// +listType=atomic
|
|
||||||
Queries []string `json:"queries"`
|
|
||||||
TimeRange InvestigationTimeRange `json:"timeRange"`
|
|
||||||
Datasource InvestigationDatasourceRef `json:"datasource"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
LogoPath *string `json:"logoPath,omitempty"`
|
|
||||||
Note string `json:"note"`
|
|
||||||
NoteUpdatedAt string `json:"noteUpdatedAt"`
|
|
||||||
FieldConfig string `json:"fieldConfig"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationCollectable creates a new InvestigationCollectable object.
|
|
||||||
func NewInvestigationCollectable() *InvestigationCollectable {
|
|
||||||
return &InvestigationCollectable{
|
|
||||||
Queries: []string{},
|
|
||||||
TimeRange: *NewInvestigationTimeRange(),
|
|
||||||
Datasource: *NewInvestigationDatasourceRef(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeRange represents a time range with both absolute and relative values
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationTimeRange struct {
|
|
||||||
From string `json:"from"`
|
|
||||||
To string `json:"to"`
|
|
||||||
Raw InvestigationV0alpha1TimeRangeRaw `json:"raw"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationTimeRange creates a new InvestigationTimeRange object.
|
|
||||||
func NewInvestigationTimeRange() *InvestigationTimeRange {
|
|
||||||
return &InvestigationTimeRange{
|
|
||||||
Raw: *NewInvestigationV0alpha1TimeRangeRaw(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatasourceRef is a reference to a datasource
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationDatasourceRef struct {
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationDatasourceRef creates a new InvestigationDatasourceRef object.
|
|
||||||
func NewInvestigationDatasourceRef() *InvestigationDatasourceRef {
|
|
||||||
return &InvestigationDatasourceRef{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationViewMode struct {
|
|
||||||
Mode InvestigationViewModeMode `json:"mode"`
|
|
||||||
ShowComments bool `json:"showComments"`
|
|
||||||
ShowTooltips bool `json:"showTooltips"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationViewMode creates a new InvestigationViewMode object.
|
|
||||||
func NewInvestigationViewMode() *InvestigationViewMode {
|
|
||||||
return &InvestigationViewMode{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationSpec struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
CreatedByProfile InvestigationPerson `json:"createdByProfile"`
|
|
||||||
HasCustomName bool `json:"hasCustomName"`
|
|
||||||
IsFavorite bool `json:"isFavorite"`
|
|
||||||
OverviewNote string `json:"overviewNote"`
|
|
||||||
OverviewNoteUpdatedAt string `json:"overviewNoteUpdatedAt"`
|
|
||||||
// +listType=atomic
|
|
||||||
Collectables []InvestigationCollectable `json:"collectables"`
|
|
||||||
ViewMode InvestigationViewMode `json:"viewMode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationSpec creates a new InvestigationSpec object.
|
|
||||||
func NewInvestigationSpec() *InvestigationSpec {
|
|
||||||
return &InvestigationSpec{
|
|
||||||
CreatedByProfile: *NewInvestigationPerson(),
|
|
||||||
Collectables: []InvestigationCollectable{},
|
|
||||||
ViewMode: *NewInvestigationViewMode(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationV0alpha1TimeRangeRaw struct {
|
|
||||||
From string `json:"from"`
|
|
||||||
To string `json:"to"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationV0alpha1TimeRangeRaw creates a new InvestigationV0alpha1TimeRangeRaw object.
|
|
||||||
func NewInvestigationV0alpha1TimeRangeRaw() *InvestigationV0alpha1TimeRangeRaw {
|
|
||||||
return &InvestigationV0alpha1TimeRangeRaw{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationViewModeMode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
InvestigationViewModeModeCompact InvestigationViewModeMode = "compact"
|
|
||||||
InvestigationViewModeModeFull InvestigationViewModeMode = "full"
|
|
||||||
)
|
|
||||||
-80
@@ -1,80 +0,0 @@
|
|||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InvestigationIndexClient struct {
|
|
||||||
client *resource.TypedClient[*InvestigationIndex, *InvestigationIndexList]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInvestigationIndexClient(client resource.Client) *InvestigationIndexClient {
|
|
||||||
return &InvestigationIndexClient{
|
|
||||||
client: resource.NewTypedClient[*InvestigationIndex, *InvestigationIndexList](client, InvestigationIndexKind()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInvestigationIndexClientFromGenerator(generator resource.ClientGenerator) (*InvestigationIndexClient, error) {
|
|
||||||
c, err := generator.ClientFor(InvestigationIndexKind())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewInvestigationIndexClient(c), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Get(ctx context.Context, identifier resource.Identifier) (*InvestigationIndex, error) {
|
|
||||||
return c.client.Get(ctx, identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationIndexList, error) {
|
|
||||||
return c.client.List(ctx, namespace, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationIndexList, error) {
|
|
||||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
|
||||||
ResourceVersion: opts.ResourceVersion,
|
|
||||||
Limit: opts.Limit,
|
|
||||||
LabelFilters: opts.LabelFilters,
|
|
||||||
FieldSelectors: opts.FieldSelectors,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for resp.GetContinue() != "" {
|
|
||||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
|
||||||
Continue: resp.GetContinue(),
|
|
||||||
ResourceVersion: opts.ResourceVersion,
|
|
||||||
Limit: opts.Limit,
|
|
||||||
LabelFilters: opts.LabelFilters,
|
|
||||||
FieldSelectors: opts.FieldSelectors,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.SetContinue(page.GetContinue())
|
|
||||||
resp.SetResourceVersion(page.GetResourceVersion())
|
|
||||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Create(ctx context.Context, obj *InvestigationIndex, opts resource.CreateOptions) (*InvestigationIndex, error) {
|
|
||||||
// Make sure apiVersion and kind are set
|
|
||||||
obj.APIVersion = GroupVersion.Identifier()
|
|
||||||
obj.Kind = InvestigationIndexKind().Kind()
|
|
||||||
return c.client.Create(ctx, obj, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Update(ctx context.Context, obj *InvestigationIndex, opts resource.UpdateOptions) (*InvestigationIndex, error) {
|
|
||||||
return c.client.Update(ctx, obj, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*InvestigationIndex, error) {
|
|
||||||
return c.client.Patch(ctx, identifier, req, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
|
||||||
return c.client.Delete(ctx, identifier, opts)
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InvestigationIndexJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
|
||||||
type InvestigationIndexJSONCodec struct{}
|
|
||||||
|
|
||||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
|
||||||
func (*InvestigationIndexJSONCodec) Read(reader io.Reader, into resource.Object) error {
|
|
||||||
return json.NewDecoder(reader).Decode(into)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
|
||||||
func (*InvestigationIndexJSONCodec) Write(writer io.Writer, from resource.Object) error {
|
|
||||||
return json.NewEncoder(writer).Encode(from)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance checks
|
|
||||||
var _ resource.Codec = &InvestigationIndexJSONCodec{}
|
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
time "time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
|
||||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
|
||||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
|
||||||
type InvestigationIndexMetadata struct {
|
|
||||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
|
||||||
CreatedBy string `json:"createdBy"`
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
|
||||||
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
|
|
||||||
Finalizers []string `json:"finalizers"`
|
|
||||||
ResourceVersion string `json:"resourceVersion"`
|
|
||||||
Generation int64 `json:"generation"`
|
|
||||||
UpdatedBy string `json:"updatedBy"`
|
|
||||||
Labels map[string]string `json:"labels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexMetadata creates a new InvestigationIndexMetadata object.
|
|
||||||
func NewInvestigationIndexMetadata() *InvestigationIndexMetadata {
|
|
||||||
return &InvestigationIndexMetadata{
|
|
||||||
Finalizers: []string{},
|
|
||||||
Labels: map[string]string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-293
@@ -1,293 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndex struct {
|
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
|
||||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
|
||||||
|
|
||||||
// Spec is the spec of the InvestigationIndex
|
|
||||||
Spec InvestigationIndexSpec `json:"spec" yaml:"spec"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetSpec() any {
|
|
||||||
return o.Spec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetSpec(spec any) error {
|
|
||||||
cast, ok := spec.(InvestigationIndexSpec)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
|
||||||
}
|
|
||||||
o.Spec = cast
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetSubresources() map[string]any {
|
|
||||||
return map[string]any{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetSubresource(name string) (any, bool) {
|
|
||||||
switch name {
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetSubresource(name string, value any) error {
|
|
||||||
switch name {
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetStaticMetadata() resource.StaticMetadata {
|
|
||||||
gvk := o.GroupVersionKind()
|
|
||||||
return resource.StaticMetadata{
|
|
||||||
Name: o.ObjectMeta.Name,
|
|
||||||
Namespace: o.ObjectMeta.Namespace,
|
|
||||||
Group: gvk.Group,
|
|
||||||
Version: gvk.Version,
|
|
||||||
Kind: gvk.Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetStaticMetadata(metadata resource.StaticMetadata) {
|
|
||||||
o.Name = metadata.Name
|
|
||||||
o.Namespace = metadata.Namespace
|
|
||||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
|
||||||
Group: metadata.Group,
|
|
||||||
Version: metadata.Version,
|
|
||||||
Kind: metadata.Kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetCommonMetadata() resource.CommonMetadata {
|
|
||||||
dt := o.DeletionTimestamp
|
|
||||||
var deletionTimestamp *time.Time
|
|
||||||
if dt != nil {
|
|
||||||
deletionTimestamp = &dt.Time
|
|
||||||
}
|
|
||||||
// Legacy ExtraFields support
|
|
||||||
extraFields := make(map[string]any)
|
|
||||||
if o.Annotations != nil {
|
|
||||||
extraFields["annotations"] = o.Annotations
|
|
||||||
}
|
|
||||||
if o.ManagedFields != nil {
|
|
||||||
extraFields["managedFields"] = o.ManagedFields
|
|
||||||
}
|
|
||||||
if o.OwnerReferences != nil {
|
|
||||||
extraFields["ownerReferences"] = o.OwnerReferences
|
|
||||||
}
|
|
||||||
return resource.CommonMetadata{
|
|
||||||
UID: string(o.UID),
|
|
||||||
ResourceVersion: o.ResourceVersion,
|
|
||||||
Generation: o.Generation,
|
|
||||||
Labels: o.Labels,
|
|
||||||
CreationTimestamp: o.CreationTimestamp.Time,
|
|
||||||
DeletionTimestamp: deletionTimestamp,
|
|
||||||
Finalizers: o.Finalizers,
|
|
||||||
UpdateTimestamp: o.GetUpdateTimestamp(),
|
|
||||||
CreatedBy: o.GetCreatedBy(),
|
|
||||||
UpdatedBy: o.GetUpdatedBy(),
|
|
||||||
ExtraFields: extraFields,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetCommonMetadata(metadata resource.CommonMetadata) {
|
|
||||||
o.UID = types.UID(metadata.UID)
|
|
||||||
o.ResourceVersion = metadata.ResourceVersion
|
|
||||||
o.Generation = metadata.Generation
|
|
||||||
o.Labels = metadata.Labels
|
|
||||||
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
|
|
||||||
if metadata.DeletionTimestamp != nil {
|
|
||||||
dt := metav1.NewTime(*metadata.DeletionTimestamp)
|
|
||||||
o.DeletionTimestamp = &dt
|
|
||||||
} else {
|
|
||||||
o.DeletionTimestamp = nil
|
|
||||||
}
|
|
||||||
o.Finalizers = metadata.Finalizers
|
|
||||||
if o.Annotations == nil {
|
|
||||||
o.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
if !metadata.UpdateTimestamp.IsZero() {
|
|
||||||
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
|
|
||||||
}
|
|
||||||
if metadata.CreatedBy != "" {
|
|
||||||
o.SetCreatedBy(metadata.CreatedBy)
|
|
||||||
}
|
|
||||||
if metadata.UpdatedBy != "" {
|
|
||||||
o.SetUpdatedBy(metadata.UpdatedBy)
|
|
||||||
}
|
|
||||||
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
|
|
||||||
if metadata.ExtraFields != nil {
|
|
||||||
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
|
|
||||||
if cast, ok := annotations.(map[string]string); ok {
|
|
||||||
o.Annotations = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
|
|
||||||
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
|
|
||||||
o.ManagedFields = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
|
|
||||||
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
|
|
||||||
o.OwnerReferences = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetCreatedBy() string {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetCreatedBy(createdBy string) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetUpdateTimestamp() time.Time {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetUpdateTimestamp(updateTimestamp time.Time) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetUpdatedBy() string {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetUpdatedBy(updatedBy string) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) Copy() resource.Object {
|
|
||||||
return resource.CopyObject(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) DeepCopyObject() runtime.Object {
|
|
||||||
return o.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) DeepCopy() *InvestigationIndex {
|
|
||||||
cpy := &InvestigationIndex{}
|
|
||||||
o.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) DeepCopyInto(dst *InvestigationIndex) {
|
|
||||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
|
||||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
|
||||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
|
||||||
o.Spec.DeepCopyInto(&dst.Spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
|
||||||
var _ resource.Object = &InvestigationIndex{}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexList struct {
|
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
|
||||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
|
||||||
Items []InvestigationIndex `json:"items" yaml:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) DeepCopyObject() runtime.Object {
|
|
||||||
return o.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) Copy() resource.ListObject {
|
|
||||||
cpy := &InvestigationIndexList{
|
|
||||||
TypeMeta: o.TypeMeta,
|
|
||||||
Items: make([]InvestigationIndex, len(o.Items)),
|
|
||||||
}
|
|
||||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
|
||||||
for i := 0; i < len(o.Items); i++ {
|
|
||||||
if item, ok := o.Items[i].Copy().(*InvestigationIndex); ok {
|
|
||||||
cpy.Items[i] = *item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) GetItems() []resource.Object {
|
|
||||||
items := make([]resource.Object, len(o.Items))
|
|
||||||
for i := 0; i < len(o.Items); i++ {
|
|
||||||
items[i] = &o.Items[i]
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) SetItems(items []resource.Object) {
|
|
||||||
o.Items = make([]InvestigationIndex, len(items))
|
|
||||||
for i := 0; i < len(items); i++ {
|
|
||||||
o.Items[i] = *items[i].(*InvestigationIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) DeepCopy() *InvestigationIndexList {
|
|
||||||
cpy := &InvestigationIndexList{}
|
|
||||||
o.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) DeepCopyInto(dst *InvestigationIndexList) {
|
|
||||||
resource.CopyObjectInto(dst, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
|
||||||
var _ resource.ListObject = &InvestigationIndexList{}
|
|
||||||
|
|
||||||
// Copy methods for all subresource types
|
|
||||||
|
|
||||||
// DeepCopy creates a full deep copy of Spec
|
|
||||||
func (s *InvestigationIndexSpec) DeepCopy() *InvestigationIndexSpec {
|
|
||||||
cpy := &InvestigationIndexSpec{}
|
|
||||||
s.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto deep copies Spec into another Spec object
|
|
||||||
func (s *InvestigationIndexSpec) DeepCopyInto(dst *InvestigationIndexSpec) {
|
|
||||||
resource.CopyObjectInto(dst, s)
|
|
||||||
}
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// schema is unexported to prevent accidental overwrites
|
|
||||||
var (
|
|
||||||
schemaInvestigationIndex = resource.NewSimpleSchema("investigations.grafana.app", "v0alpha1", &InvestigationIndex{}, &InvestigationIndexList{}, resource.WithKind("InvestigationIndex"),
|
|
||||||
resource.WithPlural("investigationindexes"), resource.WithScope(resource.NamespacedScope))
|
|
||||||
kindInvestigationIndex = resource.Kind{
|
|
||||||
Schema: schemaInvestigationIndex,
|
|
||||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
|
||||||
resource.KindEncodingJSON: &InvestigationIndexJSONCodec{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
|
||||||
func InvestigationIndexKind() resource.Kind {
|
|
||||||
return kindInvestigationIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schema returns a resource.SimpleSchema representation of InvestigationIndex
|
|
||||||
func InvestigationIndexSchema() *resource.SimpleSchema {
|
|
||||||
return schemaInvestigationIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance checks
|
|
||||||
var _ resource.Schema = kindInvestigationIndex
|
|
||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
// Person represents a user profile with basic information
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexPerson struct {
|
|
||||||
// Unique identifier for the user
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
// Display name of the user
|
|
||||||
Name string `json:"name"`
|
|
||||||
// URL to user's Gravatar image
|
|
||||||
GravatarUrl string `json:"gravatarUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexPerson creates a new InvestigationIndexPerson object.
|
|
||||||
func NewInvestigationIndexPerson() *InvestigationIndexPerson {
|
|
||||||
return &InvestigationIndexPerson{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type definition for investigation summaries
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexInvestigationSummary struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
CreatedByProfile InvestigationIndexPerson `json:"createdByProfile"`
|
|
||||||
HasCustomName bool `json:"hasCustomName"`
|
|
||||||
IsFavorite bool `json:"isFavorite"`
|
|
||||||
OverviewNote string `json:"overviewNote"`
|
|
||||||
OverviewNoteUpdatedAt string `json:"overviewNoteUpdatedAt"`
|
|
||||||
ViewMode InvestigationIndexViewMode `json:"viewMode"`
|
|
||||||
// +listType=atomic
|
|
||||||
CollectableSummaries []InvestigationIndexCollectableSummary `json:"collectableSummaries"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexInvestigationSummary creates a new InvestigationIndexInvestigationSummary object.
|
|
||||||
func NewInvestigationIndexInvestigationSummary() *InvestigationIndexInvestigationSummary {
|
|
||||||
return &InvestigationIndexInvestigationSummary{
|
|
||||||
CreatedByProfile: *NewInvestigationIndexPerson(),
|
|
||||||
ViewMode: *NewInvestigationIndexViewMode(),
|
|
||||||
CollectableSummaries: []InvestigationIndexCollectableSummary{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexViewMode struct {
|
|
||||||
Mode InvestigationIndexViewModeMode `json:"mode"`
|
|
||||||
ShowComments bool `json:"showComments"`
|
|
||||||
ShowTooltips bool `json:"showTooltips"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexViewMode creates a new InvestigationIndexViewMode object.
|
|
||||||
func NewInvestigationIndexViewMode() *InvestigationIndexViewMode {
|
|
||||||
return &InvestigationIndexViewMode{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexCollectableSummary struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
LogoPath string `json:"logoPath"`
|
|
||||||
Origin string `json:"origin"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexCollectableSummary creates a new InvestigationIndexCollectableSummary object.
|
|
||||||
func NewInvestigationIndexCollectableSummary() *InvestigationIndexCollectableSummary {
|
|
||||||
return &InvestigationIndexCollectableSummary{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexSpec struct {
|
|
||||||
// Title of the index, e.g. 'Favorites' or 'My Investigations'
|
|
||||||
Title string `json:"title"`
|
|
||||||
// The Person who owns this investigation index
|
|
||||||
Owner InvestigationIndexPerson `json:"owner"`
|
|
||||||
// Array of investigation summaries
|
|
||||||
// +listType=atomic
|
|
||||||
InvestigationSummaries []InvestigationIndexInvestigationSummary `json:"investigationSummaries"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexSpec creates a new InvestigationIndexSpec object.
|
|
||||||
func NewInvestigationIndexSpec() *InvestigationIndexSpec {
|
|
||||||
return &InvestigationIndexSpec{
|
|
||||||
Owner: *NewInvestigationIndexPerson(),
|
|
||||||
InvestigationSummaries: []InvestigationIndexInvestigationSummary{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexViewModeMode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
InvestigationIndexViewModeModeCompact InvestigationIndexViewModeMode = "compact"
|
|
||||||
InvestigationIndexViewModeModeFull InvestigationIndexViewModeMode = "full"
|
|
||||||
)
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexstatusOperatorState struct {
|
|
||||||
// lastEvaluation is the ResourceVersion last evaluated
|
|
||||||
LastEvaluation string `json:"lastEvaluation"`
|
|
||||||
// state describes the state of the lastEvaluation.
|
|
||||||
// It is limited to three possible states for machine evaluation.
|
|
||||||
State InvestigationIndexStatusOperatorStateState `json:"state"`
|
|
||||||
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
|
||||||
DescriptiveState *string `json:"descriptiveState,omitempty"`
|
|
||||||
// details contains any extra information that is operator-specific
|
|
||||||
Details map[string]interface{} `json:"details,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexstatusOperatorState creates a new InvestigationIndexstatusOperatorState object.
|
|
||||||
func NewInvestigationIndexstatusOperatorState() *InvestigationIndexstatusOperatorState {
|
|
||||||
return &InvestigationIndexstatusOperatorState{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexStatus struct {
|
|
||||||
// operatorStates is a map of operator ID to operator state evaluations.
|
|
||||||
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
|
||||||
OperatorStates map[string]InvestigationIndexstatusOperatorState `json:"operatorStates,omitempty"`
|
|
||||||
// additionalFields is reserved for future use
|
|
||||||
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexStatus creates a new InvestigationIndexStatus object.
|
|
||||||
func NewInvestigationIndexStatus() *InvestigationIndexStatus {
|
|
||||||
return &InvestigationIndexStatus{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexStatusOperatorStateState string
|
|
||||||
|
|
||||||
const (
|
|
||||||
InvestigationIndexStatusOperatorStateStateSuccess InvestigationIndexStatusOperatorStateState = "success"
|
|
||||||
InvestigationIndexStatusOperatorStateStateInProgress InvestigationIndexStatusOperatorStateState = "in_progress"
|
|
||||||
InvestigationIndexStatusOperatorStateStateFailed InvestigationIndexStatusOperatorStateState = "failed"
|
|
||||||
)
|
|
||||||
File diff suppressed because it is too large
Load Diff
-136
@@ -1,136 +0,0 @@
|
|||||||
//
|
|
||||||
// This file is generated by grafana-app-sdk
|
|
||||||
// DO NOT EDIT
|
|
||||||
//
|
|
||||||
|
|
||||||
package apis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/app"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/kube-openapi/pkg/spec3"
|
|
||||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
|
||||||
|
|
||||||
v0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
rawSchemaInvestigationv0alpha1 = []byte(`{"Collectable":{"additionalProperties":false,"description":"Collectable represents an item collected during investigation","properties":{"createdAt":{"type":"string"},"datasource":{"$ref":"#/components/schemas/DatasourceRef"},"fieldConfig":{"type":"string"},"id":{"type":"string"},"logoPath":{"type":"string"},"note":{"type":"string"},"noteUpdatedAt":{"type":"string"},"origin":{"type":"string"},"queries":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"timeRange":{"$ref":"#/components/schemas/TimeRange"},"title":{"type":"string"},"type":{"type":"string"},"url":{"type":"string"}},"required":["id","createdAt","title","origin","type","queries","timeRange","datasource","url","note","noteUpdatedAt","fieldConfig"],"type":"object"},"DatasourceRef":{"additionalProperties":false,"description":"DatasourceRef is a reference to a datasource","properties":{"uid":{"type":"string"}},"required":["uid"],"type":"object"},"Investigation":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"Person":{"additionalProperties":false,"description":"Person represents a user profile with basic information","properties":{"gravatarUrl":{"description":"URL to user's Gravatar image","type":"string"},"name":{"description":"Display name of the user","type":"string"},"uid":{"description":"Unique identifier for the user","type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"TimeRange":{"additionalProperties":false,"description":"TimeRange represents a time range with both absolute and relative values","properties":{"from":{"type":"string"},"raw":{"additionalProperties":false,"properties":{"from":{"type":"string"},"to":{"type":"string"}},"required":["from","to"],"type":"object"},"to":{"type":"string"}},"required":["from","to","raw"],"type":"object"},"ViewMode":{"additionalProperties":false,"properties":{"mode":{"enum":["compact","full"],"type":"string"},"showComments":{"type":"boolean"},"showTooltips":{"type":"boolean"}},"required":["mode","showComments","showTooltips"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"collectables":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Collectable"},"type":"array"},"createdByProfile":{"$ref":"#/components/schemas/Person"},"hasCustomName":{"type":"boolean"},"isFavorite":{"type":"boolean"},"overviewNote":{"type":"string"},"overviewNoteUpdatedAt":{"type":"string"},"title":{"type":"string"},"viewMode":{"$ref":"#/components/schemas/ViewMode"}},"required":["title","createdByProfile","hasCustomName","isFavorite","overviewNote","overviewNoteUpdatedAt","collectables","viewMode"],"type":"object"}}`)
|
|
||||||
versionSchemaInvestigationv0alpha1 app.VersionSchema
|
|
||||||
_ = json.Unmarshal(rawSchemaInvestigationv0alpha1, &versionSchemaInvestigationv0alpha1)
|
|
||||||
rawSchemaInvestigationIndexv0alpha1 = []byte(`{"CollectableSummary":{"additionalProperties":false,"properties":{"id":{"type":"string"},"logoPath":{"type":"string"},"origin":{"type":"string"},"title":{"type":"string"}},"required":["id","title","logoPath","origin"],"type":"object"},"InvestigationIndex":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"InvestigationSummary":{"additionalProperties":false,"description":"Type definition for investigation summaries","properties":{"collectableSummaries":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/CollectableSummary"},"type":"array"},"createdByProfile":{"$ref":"#/components/schemas/Person"},"hasCustomName":{"type":"boolean"},"isFavorite":{"type":"boolean"},"overviewNote":{"type":"string"},"overviewNoteUpdatedAt":{"type":"string"},"title":{"type":"string"},"viewMode":{"$ref":"#/components/schemas/ViewMode"}},"required":["title","createdByProfile","hasCustomName","isFavorite","overviewNote","overviewNoteUpdatedAt","viewMode","collectableSummaries"],"type":"object"},"Person":{"additionalProperties":false,"description":"Person represents a user profile with basic information","properties":{"gravatarUrl":{"description":"URL to user's Gravatar image","type":"string"},"name":{"description":"Display name of the user","type":"string"},"uid":{"description":"Unique identifier for the user","type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"ViewMode":{"additionalProperties":false,"properties":{"mode":{"enum":["compact","full"],"type":"string"},"showComments":{"type":"boolean"},"showTooltips":{"type":"boolean"}},"required":["mode","showComments","showTooltips"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"investigationSummaries":{"description":"Array of investigation summaries\n+listType=atomic","items":{"$ref":"#/components/schemas/InvestigationSummary"},"type":"array"},"owner":{"$ref":"#/components/schemas/Person","description":"The Person who owns this investigation index"},"title":{"description":"Title of the index, e.g. 'Favorites' or 'My Investigations'","type":"string"}},"required":["title","owner","investigationSummaries"],"type":"object"}}`)
|
|
||||||
versionSchemaInvestigationIndexv0alpha1 app.VersionSchema
|
|
||||||
_ = json.Unmarshal(rawSchemaInvestigationIndexv0alpha1, &versionSchemaInvestigationIndexv0alpha1)
|
|
||||||
)
|
|
||||||
|
|
||||||
var appManifestData = app.ManifestData{
|
|
||||||
AppName: "investigations",
|
|
||||||
Group: "investigations.grafana.app",
|
|
||||||
PreferredVersion: "v0alpha1",
|
|
||||||
Versions: []app.ManifestVersion{
|
|
||||||
{
|
|
||||||
Name: "v0alpha1",
|
|
||||||
Served: true,
|
|
||||||
Kinds: []app.ManifestVersionKind{
|
|
||||||
{
|
|
||||||
Kind: "Investigation",
|
|
||||||
Plural: "Investigations",
|
|
||||||
Scope: "Namespaced",
|
|
||||||
Conversion: false,
|
|
||||||
Schema: &versionSchemaInvestigationv0alpha1,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Kind: "InvestigationIndex",
|
|
||||||
Plural: "InvestigationIndexes",
|
|
||||||
Scope: "Namespaced",
|
|
||||||
Conversion: false,
|
|
||||||
Schema: &versionSchemaInvestigationIndexv0alpha1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Routes: app.ManifestVersionRoutes{
|
|
||||||
Namespaced: map[string]spec3.PathProps{},
|
|
||||||
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("investigations")
|
|
||||||
}
|
|
||||||
|
|
||||||
var kindVersionToGoType = map[string]resource.Kind{
|
|
||||||
"Investigation/v0alpha1": v0alpha1.InvestigationKind(),
|
|
||||||
"InvestigationIndex/v0alpha1": v0alpha1.InvestigationIndexKind(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.
|
|
||||||
// If there is no association for the provided Kind and Version, exists will return false.
|
|
||||||
func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) {
|
|
||||||
goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)]
|
|
||||||
return goType, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
var customRouteToGoResponseType = map[string]any{}
|
|
||||||
|
|
||||||
// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists.
|
|
||||||
// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths.
|
|
||||||
// If there is no association for the provided kind, version, custom route path, and method, exists will return false.
|
|
||||||
// Resource routes (those without a kind) should prefix their route with "<namespace>/" 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)
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/app"
|
|
||||||
"github.com/grafana/grafana-app-sdk/operator"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
"github.com/grafana/grafana-app-sdk/simple"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
investigationsv0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(cfg app.Config) (app.App, error) {
|
|
||||||
var err error
|
|
||||||
simpleConfig := simple.AppConfig{
|
|
||||||
Name: "investigation",
|
|
||||||
KubeConfig: cfg.KubeConfig,
|
|
||||||
InformerConfig: simple.AppInformerConfig{
|
|
||||||
InformerOptions: operator.InformerOptions{
|
|
||||||
ErrorHandler: func(_ context.Context, err error) {
|
|
||||||
klog.ErrorS(err, "Informer processing error")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ManagedKinds: []simple.AppManagedKind{
|
|
||||||
{
|
|
||||||
Kind: investigationsv0alpha1.InvestigationKind(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Kind: investigationsv0alpha1.InvestigationIndexKind(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
gv := schema.GroupVersion{
|
|
||||||
Group: investigationsv0alpha1.InvestigationKind().Group(),
|
|
||||||
Version: investigationsv0alpha1.InvestigationKind().Version(),
|
|
||||||
}
|
|
||||||
return map[schema.GroupVersion][]resource.Kind{
|
|
||||||
gv: {
|
|
||||||
investigationsv0alpha1.InvestigationKind(),
|
|
||||||
investigationsv0alpha1.InvestigationIndexKind(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
-49
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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<string, string>;
|
|
||||||
annotations?: Record<string, string>;
|
|
||||||
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 Investigation {
|
|
||||||
kind: string;
|
|
||||||
apiVersion: string;
|
|
||||||
metadata: Metadata;
|
|
||||||
spec: Spec;
|
|
||||||
status: Status;
|
|
||||||
}
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
// 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<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultMetadata = (): Metadata => ({
|
|
||||||
updateTimestamp: "",
|
|
||||||
createdBy: "",
|
|
||||||
uid: "",
|
|
||||||
creationTimestamp: "",
|
|
||||||
finalizers: [],
|
|
||||||
resourceVersion: "",
|
|
||||||
generation: 0,
|
|
||||||
updatedBy: "",
|
|
||||||
labels: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
-115
@@ -1,115 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
// Person represents a user profile with basic information
|
|
||||||
export interface Person {
|
|
||||||
// Unique identifier for the user
|
|
||||||
uid: string;
|
|
||||||
// Display name of the user
|
|
||||||
name: string;
|
|
||||||
// URL to user's Gravatar image
|
|
||||||
gravatarUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultPerson = (): Person => ({
|
|
||||||
uid: "",
|
|
||||||
name: "",
|
|
||||||
gravatarUrl: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Collectable represents an item collected during investigation
|
|
||||||
export interface Collectable {
|
|
||||||
id: string;
|
|
||||||
createdAt: string;
|
|
||||||
title: string;
|
|
||||||
origin: string;
|
|
||||||
type: string;
|
|
||||||
// +listType=atomic
|
|
||||||
queries: string[];
|
|
||||||
timeRange: TimeRange;
|
|
||||||
datasource: DatasourceRef;
|
|
||||||
url: string;
|
|
||||||
logoPath?: string;
|
|
||||||
note: string;
|
|
||||||
noteUpdatedAt: string;
|
|
||||||
fieldConfig: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultCollectable = (): Collectable => ({
|
|
||||||
id: "",
|
|
||||||
createdAt: "",
|
|
||||||
title: "",
|
|
||||||
origin: "",
|
|
||||||
type: "",
|
|
||||||
queries: [],
|
|
||||||
timeRange: defaultTimeRange(),
|
|
||||||
datasource: defaultDatasourceRef(),
|
|
||||||
url: "",
|
|
||||||
note: "",
|
|
||||||
noteUpdatedAt: "",
|
|
||||||
fieldConfig: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
// TimeRange represents a time range with both absolute and relative values
|
|
||||||
export interface TimeRange {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
raw: {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultTimeRange = (): TimeRange => ({
|
|
||||||
from: "",
|
|
||||||
to: "",
|
|
||||||
raw: {
|
|
||||||
from: "",
|
|
||||||
to: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// DatasourceRef is a reference to a datasource
|
|
||||||
export interface DatasourceRef {
|
|
||||||
uid: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultDatasourceRef = (): DatasourceRef => ({
|
|
||||||
uid: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface ViewMode {
|
|
||||||
mode: "compact" | "full";
|
|
||||||
showComments: boolean;
|
|
||||||
showTooltips: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultViewMode = (): ViewMode => ({
|
|
||||||
mode: "compact",
|
|
||||||
showComments: false,
|
|
||||||
showTooltips: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// spec is the schema of our resource
|
|
||||||
export interface Spec {
|
|
||||||
title: string;
|
|
||||||
createdByProfile: Person;
|
|
||||||
hasCustomName: boolean;
|
|
||||||
isFavorite: boolean;
|
|
||||||
overviewNote: string;
|
|
||||||
overviewNoteUpdatedAt: string;
|
|
||||||
// +listType=atomic
|
|
||||||
collectables: Collectable[];
|
|
||||||
viewMode: ViewMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultSpec = (): Spec => ({
|
|
||||||
title: "",
|
|
||||||
createdByProfile: defaultPerson(),
|
|
||||||
hasCustomName: false,
|
|
||||||
isFavorite: false,
|
|
||||||
overviewNote: "",
|
|
||||||
overviewNoteUpdatedAt: "",
|
|
||||||
collectables: [],
|
|
||||||
viewMode: defaultViewMode(),
|
|
||||||
});
|
|
||||||
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
// 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<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<string, OperatorState>;
|
|
||||||
// additionalFields is reserved for future use
|
|
||||||
additionalFields?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultStatus = (): Status => ({
|
|
||||||
});
|
|
||||||
|
|
||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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<string, string>;
|
|
||||||
annotations?: Record<string, string>;
|
|
||||||
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 InvestigationIndex {
|
|
||||||
kind: string;
|
|
||||||
apiVersion: string;
|
|
||||||
metadata: Metadata;
|
|
||||||
spec: Spec;
|
|
||||||
status: Status;
|
|
||||||
}
|
|
||||||
Generated
-30
@@ -1,30 +0,0 @@
|
|||||||
// 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<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultMetadata = (): Metadata => ({
|
|
||||||
updateTimestamp: "",
|
|
||||||
createdBy: "",
|
|
||||||
uid: "",
|
|
||||||
creationTimestamp: "",
|
|
||||||
finalizers: [],
|
|
||||||
resourceVersion: "",
|
|
||||||
generation: 0,
|
|
||||||
updatedBy: "",
|
|
||||||
labels: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
-84
@@ -1,84 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
// Person represents a user profile with basic information
|
|
||||||
export interface Person {
|
|
||||||
// Unique identifier for the user
|
|
||||||
uid: string;
|
|
||||||
// Display name of the user
|
|
||||||
name: string;
|
|
||||||
// URL to user's Gravatar image
|
|
||||||
gravatarUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultPerson = (): Person => ({
|
|
||||||
uid: "",
|
|
||||||
name: "",
|
|
||||||
gravatarUrl: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Type definition for investigation summaries
|
|
||||||
export interface InvestigationSummary {
|
|
||||||
title: string;
|
|
||||||
createdByProfile: Person;
|
|
||||||
hasCustomName: boolean;
|
|
||||||
isFavorite: boolean;
|
|
||||||
overviewNote: string;
|
|
||||||
overviewNoteUpdatedAt: string;
|
|
||||||
viewMode: ViewMode;
|
|
||||||
// +listType=atomic
|
|
||||||
collectableSummaries: CollectableSummary[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultInvestigationSummary = (): InvestigationSummary => ({
|
|
||||||
title: "",
|
|
||||||
createdByProfile: defaultPerson(),
|
|
||||||
hasCustomName: false,
|
|
||||||
isFavorite: false,
|
|
||||||
overviewNote: "",
|
|
||||||
overviewNoteUpdatedAt: "",
|
|
||||||
viewMode: defaultViewMode(),
|
|
||||||
collectableSummaries: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface ViewMode {
|
|
||||||
mode: "compact" | "full";
|
|
||||||
showComments: boolean;
|
|
||||||
showTooltips: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultViewMode = (): ViewMode => ({
|
|
||||||
mode: "compact",
|
|
||||||
showComments: false,
|
|
||||||
showTooltips: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface CollectableSummary {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
logoPath: string;
|
|
||||||
origin: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultCollectableSummary = (): CollectableSummary => ({
|
|
||||||
id: "",
|
|
||||||
title: "",
|
|
||||||
logoPath: "",
|
|
||||||
origin: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface Spec {
|
|
||||||
// Title of the index, e.g. 'Favorites' or 'My Investigations'
|
|
||||||
title: string;
|
|
||||||
// The Person who owns this investigation index
|
|
||||||
owner: Person;
|
|
||||||
// Array of investigation summaries
|
|
||||||
// +listType=atomic
|
|
||||||
investigationSummaries: InvestigationSummary[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultSpec = (): Spec => ({
|
|
||||||
title: "",
|
|
||||||
owner: defaultPerson(),
|
|
||||||
investigationSummaries: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
// 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<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<string, OperatorState>;
|
|
||||||
// additionalFields is reserved for future use
|
|
||||||
additionalFields?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultStatus = (): Status => ({
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -135,9 +135,12 @@ You can use the **Span Limit** field in **Options** section of the TraceQL query
|
|||||||
This field sets the maximum number of spans to return for each span set.
|
This field sets the maximum number of spans to return for each span set.
|
||||||
By default, the maximum value that you can set for the **Span Limit** value (or the spss query) is 100.
|
By default, the maximum value that you can set for the **Span Limit** value (or the spss query) is 100.
|
||||||
In Tempo configuration, this value is controlled by the `max_spans_per_span_set` parameter and can be modified by your Tempo administrator.
|
In Tempo configuration, this value is controlled by the `max_spans_per_span_set` parameter and can be modified by your Tempo administrator.
|
||||||
Grafana Cloud users can contact Grafana Support to request a change.
|
|
||||||
Entering a value higher than the default results in an error.
|
Entering a value higher than the default results in an error.
|
||||||
|
|
||||||
|
{{< admonition type="note" >}}
|
||||||
|
Changing the value of `max_spans_per_span_set` isn't supported in Grafana Cloud.
|
||||||
|
{{< /admonition >}}
|
||||||
|
|
||||||
### Focus on traces or spans
|
### Focus on traces or spans
|
||||||
|
|
||||||
Under **Options**, you can choose to display the table as **Traces** or **Spans** focused.
|
Under **Options**, you can choose to display the table as **Traces** or **Spans** focused.
|
||||||
|
|||||||
@@ -33,12 +33,14 @@ require (
|
|||||||
github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad
|
github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad
|
||||||
github.com/aws/aws-sdk-go v1.55.7 // @grafana/aws-datasources
|
github.com/aws/aws-sdk-go v1.55.7 // @grafana/aws-datasources
|
||||||
github.com/aws/aws-sdk-go-v2 v1.40.0 // @grafana/aws-datasources
|
github.com/aws/aws-sdk-go-v2 v1.40.0 // @grafana/aws-datasources
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // @grafana/grafana-operator-experience-squad
|
||||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3 // @grafana/aws-datasources
|
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3 // @grafana/aws-datasources
|
||||||
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 // @grafana/aws-datasources
|
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 // @grafana/aws-datasources
|
||||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 // @grafana/aws-datasources
|
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 // @grafana/aws-datasources
|
||||||
github.com/aws/aws-sdk-go-v2/service/oam v1.18.3 // @grafana/aws-datasources
|
github.com/aws/aws-sdk-go-v2/service/oam v1.18.3 // @grafana/aws-datasources
|
||||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 // @grafana/aws-datasources
|
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 // @grafana/aws-datasources
|
||||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 // @grafana/grafana-operator-experience-squad
|
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 // @grafana/grafana-operator-experience-squad
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // @grafana/grafana-operator-experience-squad
|
||||||
github.com/aws/smithy-go v1.23.2 // @grafana/aws-datasources
|
github.com/aws/smithy-go v1.23.2 // @grafana/aws-datasources
|
||||||
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
|
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
|
||||||
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
|
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
|
||||||
@@ -248,7 +250,6 @@ require (
|
|||||||
github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b // @grafana/grafana-app-platform-squad
|
github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b // @grafana/grafana-app-platform-squad
|
||||||
github.com/grafana/grafana/apps/folder v0.0.0 // @grafana/grafana-search-and-storage
|
github.com/grafana/grafana/apps/folder v0.0.0 // @grafana/grafana-search-and-storage
|
||||||
github.com/grafana/grafana/apps/iam v0.0.0 // @grafana/identity-access-team
|
github.com/grafana/grafana/apps/iam v0.0.0 // @grafana/identity-access-team
|
||||||
github.com/grafana/grafana/apps/investigations v0.0.0 // @fcjack @matryer
|
|
||||||
github.com/grafana/grafana/apps/logsdrilldown v0.0.0 // @grafana/observability-logs
|
github.com/grafana/grafana/apps/logsdrilldown v0.0.0 // @grafana/observability-logs
|
||||||
github.com/grafana/grafana/apps/playlist v0.0.0 // @grafana/grafana-app-platform-squad
|
github.com/grafana/grafana/apps/playlist v0.0.0 // @grafana/grafana-app-platform-squad
|
||||||
github.com/grafana/grafana/apps/plugins v0.0.0 // @grafana/plugins-platform-backend
|
github.com/grafana/grafana/apps/plugins v0.0.0 // @grafana/plugins-platform-backend
|
||||||
@@ -282,7 +283,6 @@ replace (
|
|||||||
github.com/grafana/grafana/apps/dashboard => ./apps/dashboard
|
github.com/grafana/grafana/apps/dashboard => ./apps/dashboard
|
||||||
github.com/grafana/grafana/apps/folder => ./apps/folder
|
github.com/grafana/grafana/apps/folder => ./apps/folder
|
||||||
github.com/grafana/grafana/apps/iam => ./apps/iam
|
github.com/grafana/grafana/apps/iam => ./apps/iam
|
||||||
github.com/grafana/grafana/apps/investigations => ./apps/investigations
|
|
||||||
github.com/grafana/grafana/apps/logsdrilldown => ./apps/logsdrilldown
|
github.com/grafana/grafana/apps/logsdrilldown => ./apps/logsdrilldown
|
||||||
github.com/grafana/grafana/apps/playlist => ./apps/playlist
|
github.com/grafana/grafana/apps/playlist => ./apps/playlist
|
||||||
github.com/grafana/grafana/apps/plugins => ./apps/plugins
|
github.com/grafana/grafana/apps/plugins => ./apps/plugins
|
||||||
@@ -343,7 +343,6 @@ require (
|
|||||||
github.com/at-wat/mqtt-go v0.19.6 // indirect
|
github.com/at-wat/mqtt-go v0.19.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.31.17 // indirect
|
github.com/aws/aws-sdk-go-v2/config v1.31.17 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect
|
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect
|
||||||
@@ -358,7 +357,6 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 // indirect
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect
|
|
||||||
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect
|
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
|
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use (
|
|||||||
./apps/example
|
./apps/example
|
||||||
./apps/folder
|
./apps/folder
|
||||||
./apps/iam
|
./apps/iam
|
||||||
./apps/investigations
|
|
||||||
./apps/logsdrilldown
|
./apps/logsdrilldown
|
||||||
./apps/playlist
|
./apps/playlist
|
||||||
./apps/plugins
|
./apps/plugins
|
||||||
|
|||||||
@@ -5554,6 +5554,7 @@ export type ReportDashboard = {
|
|||||||
};
|
};
|
||||||
export type Type = string;
|
export type Type = string;
|
||||||
export type ReportOptions = {
|
export type ReportOptions = {
|
||||||
|
csvEncoding?: string;
|
||||||
layout?: string;
|
layout?: string;
|
||||||
orientation?: string;
|
orientation?: string;
|
||||||
pdfCombineOneFile?: boolean;
|
pdfCombineOneFile?: boolean;
|
||||||
|
|||||||
@@ -165,9 +165,17 @@ describe('DateMath', () => {
|
|||||||
expect(date!.valueOf()).toEqual(dateTime([2014, 1, 3]).valueOf());
|
expect(date!.valueOf()).toEqual(dateTime([2014, 1, 3]).valueOf());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle multiple math expressions', () => {
|
it.each([
|
||||||
const date = dateMath.parseDateMath('-2d-6h', dateTime([2014, 1, 5]));
|
['-2d-6h', [2014, 1, 5], [2014, 1, 2, 18]],
|
||||||
expect(date!.valueOf()).toEqual(dateTime([2014, 1, 2, 18]).valueOf());
|
['-30m-2d', [2014, 1, 5], [2014, 1, 2, 23, 30]],
|
||||||
|
['-2d-1d', [2014, 1, 5], [2014, 1, 2]],
|
||||||
|
['-1h-30m', [2014, 1, 5, 12, 0], [2014, 1, 5, 10, 30]],
|
||||||
|
['-1d-1h-30m', [2014, 1, 5, 12, 0], [2014, 1, 4, 10, 30]],
|
||||||
|
['+1d-6h', [2014, 1, 5], [2014, 1, 5, 18]],
|
||||||
|
['-1w-1d', [2014, 1, 14], [2014, 1, 6]],
|
||||||
|
])('should handle multiple math expressions: %s', (expression, inputDate, expectedDate) => {
|
||||||
|
const date = dateMath.parseDateMath(expression, dateTime(inputDate));
|
||||||
|
expect(date!.valueOf()).toEqual(dateTime(expectedDate).valueOf());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when invalid expression', () => {
|
it('should return false when invalid expression', () => {
|
||||||
|
|||||||
+4
-5
@@ -207,6 +207,10 @@ export interface FeatureToggles {
|
|||||||
*/
|
*/
|
||||||
reportingRetries?: boolean;
|
reportingRetries?: boolean;
|
||||||
/**
|
/**
|
||||||
|
* Enables CSV encoding options in the reporting feature
|
||||||
|
*/
|
||||||
|
reportingCsvEncodingOptions?: boolean;
|
||||||
|
/**
|
||||||
* Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.
|
* Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.
|
||||||
*/
|
*/
|
||||||
sseGroupByDatasource?: boolean;
|
sseGroupByDatasource?: boolean;
|
||||||
@@ -787,11 +791,6 @@ export interface FeatureToggles {
|
|||||||
*/
|
*/
|
||||||
lokiLabelNamesQueryApi?: boolean;
|
lokiLabelNamesQueryApi?: boolean;
|
||||||
/**
|
/**
|
||||||
* Enable the investigations backend API
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
investigationsBackend?: boolean;
|
|
||||||
/**
|
|
||||||
* Enable folder's api server counts
|
* Enable folder's api server counts
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useId, memo, HTMLAttributes, ReactNode, SVGProps } from 'react';
|
import { useId, memo, HTMLAttributes, SVGProps } from 'react';
|
||||||
|
|
||||||
import { FieldDisplay } from '@grafana/data';
|
import { FieldDisplay } from '@grafana/data';
|
||||||
|
|
||||||
import { getBarEndcapColors, getGradientCss, getEndpointMarkerColors } from './colors';
|
import { RadialArcPathEndpointMarks } from './RadialArcPathEndpointMarks';
|
||||||
|
import { getBarEndcapColors, getGradientCss } from './colors';
|
||||||
import { RadialShape, RadialGaugeDimensions, GradientStop } from './types';
|
import { RadialShape, RadialGaugeDimensions, GradientStop } from './types';
|
||||||
import { drawRadialArcPath, toRad } from './utils';
|
import { drawRadialArcPath, toRad } from './utils';
|
||||||
|
|
||||||
@@ -29,11 +30,6 @@ interface RadialArcPathPropsWithGradient extends RadialArcPathPropsBase {
|
|||||||
|
|
||||||
type RadialArcPathProps = RadialArcPathPropsWithColor | RadialArcPathPropsWithGradient;
|
type RadialArcPathProps = RadialArcPathPropsWithColor | RadialArcPathPropsWithGradient;
|
||||||
|
|
||||||
const ENDPOINT_MARKER_MIN_ANGLE = 10;
|
|
||||||
const DOT_OPACITY = 0.5;
|
|
||||||
const DOT_RADIUS_FACTOR = 0.4;
|
|
||||||
const MAX_DOT_RADIUS = 8;
|
|
||||||
|
|
||||||
export const RadialArcPath = memo(
|
export const RadialArcPath = memo(
|
||||||
({
|
({
|
||||||
arcLengthDeg,
|
arcLengthDeg,
|
||||||
@@ -68,67 +64,25 @@ export const RadialArcPath = memo(
|
|||||||
const xEnd = centerX + radius * Math.cos(endRadians);
|
const xEnd = centerX + radius * Math.cos(endRadians);
|
||||||
const yEnd = centerY + radius * Math.sin(endRadians);
|
const yEnd = centerY + radius * Math.sin(endRadians);
|
||||||
|
|
||||||
const dotRadius =
|
|
||||||
endpointMarker === 'point' ? Math.min((barWidth / 2) * DOT_RADIUS_FACTOR, MAX_DOT_RADIUS) : barWidth / 2;
|
|
||||||
|
|
||||||
const bgDivStyle: HTMLAttributes<HTMLDivElement>['style'] = { width: boxSize, height: vizHeight, marginLeft: boxX };
|
const bgDivStyle: HTMLAttributes<HTMLDivElement>['style'] = { width: boxSize, height: vizHeight, marginLeft: boxX };
|
||||||
|
|
||||||
const pathProps: SVGProps<SVGPathElement> = {};
|
const pathProps: SVGProps<SVGPathElement> = {};
|
||||||
let barEndcapColors: [string, string] | undefined;
|
|
||||||
let endpointMarks: ReactNode = null;
|
|
||||||
if (isGradient) {
|
if (isGradient) {
|
||||||
bgDivStyle.backgroundImage = getGradientCss(rest.gradient, shape);
|
bgDivStyle.backgroundImage = getGradientCss(rest.gradient, shape);
|
||||||
|
|
||||||
if (endpointMarker && (rest.gradient?.length ?? 0) > 0) {
|
|
||||||
switch (endpointMarker) {
|
|
||||||
case 'point':
|
|
||||||
const [pointColorStart, pointColorEnd] = getEndpointMarkerColors(
|
|
||||||
rest.gradient!,
|
|
||||||
fieldDisplay.display.percent
|
|
||||||
);
|
|
||||||
endpointMarks = (
|
|
||||||
<>
|
|
||||||
{arcLengthDeg > ENDPOINT_MARKER_MIN_ANGLE && (
|
|
||||||
<circle cx={xStart} cy={yStart} r={dotRadius} fill={pointColorStart} opacity={DOT_OPACITY} />
|
|
||||||
)}
|
|
||||||
<circle cx={xEnd} cy={yEnd} r={dotRadius} fill={pointColorEnd} opacity={DOT_OPACITY} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'glow':
|
|
||||||
const offsetAngle = toRad(ENDPOINT_MARKER_MIN_ANGLE);
|
|
||||||
const xStartMark = centerX + radius * Math.cos(endRadians + offsetAngle);
|
|
||||||
const yStartMark = centerY + radius * Math.sin(endRadians + offsetAngle);
|
|
||||||
endpointMarks =
|
|
||||||
arcLengthDeg > ENDPOINT_MARKER_MIN_ANGLE ? (
|
|
||||||
<path
|
|
||||||
d={['M', xStartMark, yStartMark, 'A', radius, radius, 0, 0, 1, xEnd, yEnd].join(' ')}
|
|
||||||
fill="none"
|
|
||||||
strokeWidth={barWidth}
|
|
||||||
stroke={endpointMarkerGlowFilter}
|
|
||||||
strokeLinecap={roundedBars ? 'round' : 'butt'}
|
|
||||||
filter={glowFilter}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (barEndcaps) {
|
|
||||||
barEndcapColors = getBarEndcapColors(rest.gradient, fieldDisplay.display.percent);
|
|
||||||
}
|
|
||||||
|
|
||||||
pathProps.fill = 'none';
|
pathProps.fill = 'none';
|
||||||
pathProps.stroke = 'white';
|
pathProps.stroke = 'white';
|
||||||
} else {
|
} else {
|
||||||
bgDivStyle.backgroundColor = rest.color;
|
bgDivStyle.backgroundColor = rest.color;
|
||||||
|
|
||||||
pathProps.fill = 'none';
|
pathProps.fill = 'none';
|
||||||
pathProps.stroke = rest.color;
|
pathProps.stroke = rest.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let barEndcapColors: [string, string] | undefined;
|
||||||
|
if (barEndcaps) {
|
||||||
|
barEndcapColors = isGradient
|
||||||
|
? getBarEndcapColors(rest.gradient, fieldDisplay.display.percent)
|
||||||
|
: [rest.color, rest.color];
|
||||||
|
}
|
||||||
|
|
||||||
const pathEl = (
|
const pathEl = (
|
||||||
<path d={path} strokeWidth={barWidth} strokeLinecap={roundedBars ? 'round' : 'butt'} {...pathProps} />
|
<path d={path} strokeWidth={barWidth} strokeLinecap={roundedBars ? 'round' : 'butt'} {...pathProps} />
|
||||||
);
|
);
|
||||||
@@ -158,7 +112,23 @@ export const RadialArcPath = memo(
|
|||||||
)}
|
)}
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{endpointMarks}
|
{endpointMarker && (
|
||||||
|
<RadialArcPathEndpointMarks
|
||||||
|
startAngle={angle}
|
||||||
|
arcLengthDeg={arcLengthDeg}
|
||||||
|
dimensions={dimensions}
|
||||||
|
endpointMarker={endpointMarker}
|
||||||
|
fieldDisplay={fieldDisplay}
|
||||||
|
xStart={xStart}
|
||||||
|
xEnd={xEnd}
|
||||||
|
yStart={yStart}
|
||||||
|
yEnd={yEnd}
|
||||||
|
roundedBars={roundedBars}
|
||||||
|
endpointMarkerGlowFilter={endpointMarkerGlowFilter}
|
||||||
|
glowFilter={glowFilter}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { render, RenderResult } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { FieldDisplay } from '@grafana/data';
|
||||||
|
|
||||||
|
import { RadialArcPathEndpointMarks, RadialArcPathEndpointMarksProps } from './RadialArcPathEndpointMarks';
|
||||||
|
import { RadialGaugeDimensions } from './types';
|
||||||
|
|
||||||
|
const ser = new XMLSerializer();
|
||||||
|
|
||||||
|
const expectHTML = (result: RenderResult, expected: string) => {
|
||||||
|
let actual = ser.serializeToString(result.asFragment()).replace(/xmlns=".*?" /g, '');
|
||||||
|
expect(actual).toEqual(expected.replace(/^\s*|\n/gm, ''));
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('RadialArcPathEndpointMarks', () => {
|
||||||
|
const defaultDimensions = Object.freeze({
|
||||||
|
centerX: 100,
|
||||||
|
centerY: 100,
|
||||||
|
radius: 80,
|
||||||
|
barWidth: 20,
|
||||||
|
vizWidth: 200,
|
||||||
|
vizHeight: 200,
|
||||||
|
margin: 10,
|
||||||
|
barIndex: 0,
|
||||||
|
thresholdsBarRadius: 0,
|
||||||
|
thresholdsBarWidth: 0,
|
||||||
|
thresholdsBarSpacing: 0,
|
||||||
|
scaleLabelsFontSize: 0,
|
||||||
|
scaleLabelsSpacing: 0,
|
||||||
|
scaleLabelsRadius: 0,
|
||||||
|
gaugeBottomY: 0,
|
||||||
|
}) satisfies RadialGaugeDimensions;
|
||||||
|
|
||||||
|
const defaultFieldDisplay = Object.freeze({
|
||||||
|
name: 'Test',
|
||||||
|
field: {},
|
||||||
|
display: { text: '50', numeric: 50, color: '#FF0000' },
|
||||||
|
hasLinks: false,
|
||||||
|
}) satisfies FieldDisplay;
|
||||||
|
|
||||||
|
const defaultProps = Object.freeze({
|
||||||
|
arcLengthDeg: 90,
|
||||||
|
dimensions: defaultDimensions,
|
||||||
|
fieldDisplay: defaultFieldDisplay,
|
||||||
|
startAngle: 0,
|
||||||
|
xStart: 100,
|
||||||
|
xEnd: 150,
|
||||||
|
yStart: 100,
|
||||||
|
yEnd: 50,
|
||||||
|
}) satisfies Omit<RadialArcPathEndpointMarksProps, 'color' | 'gradient' | 'endpointMarker'>;
|
||||||
|
|
||||||
|
it('renders the expected marks when endpointMarker is "point" w/ a static color', () => {
|
||||||
|
expectHTML(
|
||||||
|
render(
|
||||||
|
<svg role="img">
|
||||||
|
<RadialArcPathEndpointMarks {...defaultProps} endpointMarker="point" color="#FF0000" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
'<svg role=\"img\"><circle cx=\"100\" cy=\"100\" r=\"4\" fill=\"#111217\" opacity=\"0.5\"/><circle cx=\"150\" cy=\"50\" r=\"4\" fill=\"#111217\" opacity=\"0.5\"/></svg>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the expected marks when endpointMarker is "point" w/ a gradient color', () => {
|
||||||
|
expectHTML(
|
||||||
|
render(
|
||||||
|
<svg role="img">
|
||||||
|
<RadialArcPathEndpointMarks
|
||||||
|
{...defaultProps}
|
||||||
|
endpointMarker="point"
|
||||||
|
gradient={[
|
||||||
|
{ color: '#00FF00', percent: 0 },
|
||||||
|
{ color: '#0000FF', percent: 1 },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
'<svg role=\"img\"><circle cx=\"100\" cy=\"100\" r=\"4\" fill=\"#111217\" opacity=\"0.5\"/><circle cx=\"150\" cy=\"50\" r=\"4\" fill=\"#fbfbfb\" opacity=\"0.5\"/></svg>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the expected marks when endpointMarker is "glow" w/ a static color', () => {
|
||||||
|
expectHTML(
|
||||||
|
render(
|
||||||
|
<svg role="img">
|
||||||
|
<RadialArcPathEndpointMarks {...defaultProps} endpointMarker="glow" color="#FF0000" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
'<svg role=\"img\"><path d=\"M 113.89185421335443 21.215379759023364 A 80 80 0 0 1 150 50\" fill=\"none\" stroke-width=\"20\" stroke-linecap=\"butt\"/></svg>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the expected marks when endpointMarker is "glow" w/ a gradient color', () => {
|
||||||
|
expectHTML(
|
||||||
|
render(
|
||||||
|
<svg role="img">
|
||||||
|
<RadialArcPathEndpointMarks
|
||||||
|
{...defaultProps}
|
||||||
|
endpointMarker="glow"
|
||||||
|
gradient={[
|
||||||
|
{ color: '#00FF00', percent: 0 },
|
||||||
|
{ color: '#0000FF', percent: 1 },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
'<svg role=\"img\"><path d=\"M 113.89185421335443 21.215379759023364 A 80 80 0 0 1 150 50\" fill=\"none\" stroke-width=\"20\" stroke-linecap=\"butt\"/></svg>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render the start mark when arcLengthDeg is less than the minimum angle for "point" endpointMarker', () => {
|
||||||
|
expectHTML(
|
||||||
|
render(
|
||||||
|
<svg role="img">
|
||||||
|
<RadialArcPathEndpointMarks {...defaultProps} arcLengthDeg={5} endpointMarker="point" color="#FF0000" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
'<svg role=\"img\"><circle cx=\"150\" cy=\"50\" r=\"4\" fill=\"#111217\" opacity=\"0.5\"/></svg>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render anything when arcLengthDeg is less than the minimum angle for "glow" endpointMarker', () => {
|
||||||
|
expectHTML(
|
||||||
|
render(
|
||||||
|
<svg role="img">
|
||||||
|
<RadialArcPathEndpointMarks {...defaultProps} arcLengthDeg={5} endpointMarker="glow" color="#FF0000" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
'<svg role=\"img\"/>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render anything if endpointMarker is some other value', () => {
|
||||||
|
expectHTML(
|
||||||
|
render(
|
||||||
|
<svg role="img">
|
||||||
|
{/* @ts-ignore: confirming the component doesn't throw */}
|
||||||
|
<RadialArcPathEndpointMarks {...defaultProps} endpointMarker="foo" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
'<svg role=\"img\"/>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { FieldDisplay } from '@grafana/data';
|
||||||
|
|
||||||
|
import { getEndpointMarkerColors, getGuideDotColor } from './colors';
|
||||||
|
import { GradientStop, RadialGaugeDimensions } from './types';
|
||||||
|
import { toRad } from './utils';
|
||||||
|
|
||||||
|
interface RadialArcPathEndpointMarksPropsBase {
|
||||||
|
arcLengthDeg: number;
|
||||||
|
dimensions: RadialGaugeDimensions;
|
||||||
|
fieldDisplay: FieldDisplay;
|
||||||
|
endpointMarker: 'point' | 'glow';
|
||||||
|
roundedBars?: boolean;
|
||||||
|
startAngle: number;
|
||||||
|
glowFilter?: string;
|
||||||
|
endpointMarkerGlowFilter?: string;
|
||||||
|
xStart: number;
|
||||||
|
xEnd: number;
|
||||||
|
yStart: number;
|
||||||
|
yEnd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RadialArcPathEndpointMarksPropsWithColor extends RadialArcPathEndpointMarksPropsBase {
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RadialArcPathEndpointMarksPropsWithGradient extends RadialArcPathEndpointMarksPropsBase {
|
||||||
|
gradient: GradientStop[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RadialArcPathEndpointMarksProps =
|
||||||
|
| RadialArcPathEndpointMarksPropsWithColor
|
||||||
|
| RadialArcPathEndpointMarksPropsWithGradient;
|
||||||
|
|
||||||
|
const ENDPOINT_MARKER_MIN_ANGLE = 10;
|
||||||
|
const DOT_OPACITY = 0.5;
|
||||||
|
const DOT_RADIUS_FACTOR = 0.4;
|
||||||
|
const MAX_DOT_RADIUS = 8;
|
||||||
|
|
||||||
|
export function RadialArcPathEndpointMarks({
|
||||||
|
startAngle: angle,
|
||||||
|
arcLengthDeg,
|
||||||
|
dimensions,
|
||||||
|
endpointMarker,
|
||||||
|
fieldDisplay,
|
||||||
|
xStart,
|
||||||
|
xEnd,
|
||||||
|
yStart,
|
||||||
|
yEnd,
|
||||||
|
roundedBars,
|
||||||
|
endpointMarkerGlowFilter,
|
||||||
|
glowFilter,
|
||||||
|
...rest
|
||||||
|
}: RadialArcPathEndpointMarksProps) {
|
||||||
|
const isGradient = 'gradient' in rest;
|
||||||
|
const { radius, centerX, centerY, barWidth } = dimensions;
|
||||||
|
const endRadians = toRad(angle + arcLengthDeg);
|
||||||
|
|
||||||
|
switch (endpointMarker) {
|
||||||
|
case 'point': {
|
||||||
|
const [pointColorStart, pointColorEnd] = isGradient
|
||||||
|
? getEndpointMarkerColors(rest.gradient, fieldDisplay.display.percent)
|
||||||
|
: [getGuideDotColor(rest.color), getGuideDotColor(rest.color)];
|
||||||
|
|
||||||
|
const dotRadius =
|
||||||
|
endpointMarker === 'point' ? Math.min((barWidth / 2) * DOT_RADIUS_FACTOR, MAX_DOT_RADIUS) : barWidth / 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{arcLengthDeg > ENDPOINT_MARKER_MIN_ANGLE && (
|
||||||
|
<circle cx={xStart} cy={yStart} r={dotRadius} fill={pointColorStart} opacity={DOT_OPACITY} />
|
||||||
|
)}
|
||||||
|
<circle cx={xEnd} cy={yEnd} r={dotRadius} fill={pointColorEnd} opacity={DOT_OPACITY} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 'glow':
|
||||||
|
const offsetAngle = toRad(ENDPOINT_MARKER_MIN_ANGLE);
|
||||||
|
const xStartMark = centerX + radius * Math.cos(endRadians + offsetAngle);
|
||||||
|
const yStartMark = centerY + radius * Math.sin(endRadians + offsetAngle);
|
||||||
|
if (arcLengthDeg <= ENDPOINT_MARKER_MIN_ANGLE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<path
|
||||||
|
d={['M', xStartMark, yStartMark, 'A', radius, radius, 0, 0, 1, xEnd, yEnd].join(' ')}
|
||||||
|
fill="none"
|
||||||
|
strokeWidth={barWidth}
|
||||||
|
stroke={endpointMarkerGlowFilter}
|
||||||
|
strokeLinecap={roundedBars ? 'round' : 'butt'}
|
||||||
|
filter={glowFilter}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -175,7 +175,7 @@ export function getGradientCss(gradientStops: GradientStop[], shape: RadialShape
|
|||||||
const GRAY_05 = '#111217';
|
const GRAY_05 = '#111217';
|
||||||
const GRAY_90 = '#fbfbfb';
|
const GRAY_90 = '#fbfbfb';
|
||||||
const CONTRAST_THRESHOLD_MAX = 4.5;
|
const CONTRAST_THRESHOLD_MAX = 4.5;
|
||||||
const getGuideDotColor = (color: string): string => {
|
export const getGuideDotColor = (color: string): string => {
|
||||||
const darkColor = GRAY_05;
|
const darkColor = GRAY_05;
|
||||||
const lightColor = GRAY_90;
|
const lightColor = GRAY_90;
|
||||||
return colorManipulator.getContrastRatio(darkColor, color) >= CONTRAST_THRESHOLD_MAX ? darkColor : lightColor;
|
return colorManipulator.getContrastRatio(darkColor, color) >= CONTRAST_THRESHOLD_MAX ? darkColor : lightColor;
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ func (hs *HTTPServer) DeleteDataSourceById(c *contextmodel.ReqContext) response.
|
|||||||
func (hs *HTTPServer) GetDataSourceByUID(c *contextmodel.ReqContext) response.Response {
|
func (hs *HTTPServer) GetDataSourceByUID(c *contextmodel.ReqContext) response.Response {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
metricutil.ObserveWithExemplar(c.Req.Context(), hs.dsConfigHandlerRequestsDuration.WithLabelValues("legacy", "GetDataSourceByUID"), time.Since(start).Seconds())
|
metricutil.ObserveWithExemplar(c.Req.Context(), hs.dsConfigHandlerRequestsDuration.WithLabelValues("GetDataSourceByUID"), time.Since(start).Seconds())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ds, err := hs.getRawDataSourceByUID(c.Req.Context(), web.Params(c.Req)[":uid"], c.GetOrgID())
|
ds, err := hs.getRawDataSourceByUID(c.Req.Context(), web.Params(c.Req)[":uid"], c.GetOrgID())
|
||||||
@@ -240,7 +240,7 @@ func (hs *HTTPServer) GetDataSourceByUID(c *contextmodel.ReqContext) response.Re
|
|||||||
func (hs *HTTPServer) DeleteDataSourceByUID(c *contextmodel.ReqContext) response.Response {
|
func (hs *HTTPServer) DeleteDataSourceByUID(c *contextmodel.ReqContext) response.Response {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
metricutil.ObserveWithExemplar(c.Req.Context(), hs.dsConfigHandlerRequestsDuration.WithLabelValues("legacy", "DeleteDataSourceByUID"), time.Since(start).Seconds())
|
metricutil.ObserveWithExemplar(c.Req.Context(), hs.dsConfigHandlerRequestsDuration.WithLabelValues("DeleteDataSourceByUID"), time.Since(start).Seconds())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
uid := web.Params(c.Req)[":uid"]
|
uid := web.Params(c.Req)[":uid"]
|
||||||
@@ -375,7 +375,7 @@ func validateJSONData(jsonData *simplejson.Json, cfg *setting.Cfg) error {
|
|||||||
func (hs *HTTPServer) AddDataSource(c *contextmodel.ReqContext) response.Response {
|
func (hs *HTTPServer) AddDataSource(c *contextmodel.ReqContext) response.Response {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
metricutil.ObserveWithExemplar(c.Req.Context(), hs.dsConfigHandlerRequestsDuration.WithLabelValues("legacy", "AddDataSource"), time.Since(start).Seconds())
|
metricutil.ObserveWithExemplar(c.Req.Context(), hs.dsConfigHandlerRequestsDuration.WithLabelValues("AddDataSource"), time.Since(start).Seconds())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cmd := datasources.AddDataSourceCommand{}
|
cmd := datasources.AddDataSourceCommand{}
|
||||||
@@ -497,7 +497,7 @@ func (hs *HTTPServer) UpdateDataSourceByID(c *contextmodel.ReqContext) response.
|
|||||||
func (hs *HTTPServer) UpdateDataSourceByUID(c *contextmodel.ReqContext) response.Response {
|
func (hs *HTTPServer) UpdateDataSourceByUID(c *contextmodel.ReqContext) response.Response {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
metricutil.ObserveWithExemplar(c.Req.Context(), hs.dsConfigHandlerRequestsDuration.WithLabelValues("legacy", "UpdateDataSourceByUID"), time.Since(start).Seconds())
|
metricutil.ObserveWithExemplar(c.Req.Context(), hs.dsConfigHandlerRequestsDuration.WithLabelValues("UpdateDataSourceByUID"), time.Since(start).Seconds())
|
||||||
}()
|
}()
|
||||||
cmd := datasources.UpdateDataSourceCommand{}
|
cmd := datasources.UpdateDataSourceCommand{}
|
||||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func setupDsConfigHandlerMetrics() (prometheus.Registerer, *prometheus.Histogram
|
|||||||
Namespace: "grafana",
|
Namespace: "grafana",
|
||||||
Name: "ds_config_handler_requests_duration_seconds",
|
Name: "ds_config_handler_requests_duration_seconds",
|
||||||
Help: "Duration of requests handled by datasource configuration handlers",
|
Help: "Duration of requests handled by datasource configuration handlers",
|
||||||
}, []string{"code_path", "handler"})
|
}, []string{"handler"})
|
||||||
promRegister.MustRegister(dsConfigHandlerRequestsDuration)
|
promRegister.MustRegister(dsConfigHandlerRequestsDuration)
|
||||||
return promRegister, dsConfigHandlerRequestsDuration
|
return promRegister, dsConfigHandlerRequestsDuration
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
|||||||
Namespace: "grafana",
|
Namespace: "grafana",
|
||||||
Name: "ds_config_handler_requests_duration_seconds",
|
Name: "ds_config_handler_requests_duration_seconds",
|
||||||
Help: "Duration of requests handled by datasource configuration handlers",
|
Help: "Duration of requests handled by datasource configuration handlers",
|
||||||
}, []string{"code_path", "handler"}),
|
}, []string{"handler"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
promRegister.MustRegister(hs.htmlHandlerRequestsDuration)
|
promRegister.MustRegister(hs.htmlHandlerRequestsDuration)
|
||||||
|
|||||||
@@ -928,9 +928,10 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *contextmodel.ReqContext, cfg
|
|||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
features := featuremgmt.WithFeatures()
|
features := featuremgmt.WithFeatures()
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
|
dsRetriever := datasourceservice.ProvideDataSourceRetriever(sqlStore, features)
|
||||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
||||||
&actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
&actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
||||||
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
|
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()), dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, features)
|
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, features)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -1050,9 +1051,11 @@ func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, secrets
|
|||||||
var routes []*plugins.Route
|
var routes []*plugins.Route
|
||||||
features := featuremgmt.WithFeatures()
|
features := featuremgmt.WithFeatures()
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
var sqlStore db.DB = nil
|
||||||
|
dsRetriever := datasourceservice.ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := datasourceservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
||||||
&actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
&actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
||||||
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
|
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()), dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
proxy, err := NewDataSourceProxy(test.datasource, routes, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, features)
|
proxy, err := NewDataSourceProxy(test.datasource, routes, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, features)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -1106,9 +1109,11 @@ func setupDSProxyTest(t *testing.T, ctx *contextmodel.ReqContext, ds *datasource
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(dbtest.NewFakeDB(), secretsService, log.NewNopLogger())
|
secretsStore := secretskvs.NewSQLSecretsKVStore(dbtest.NewFakeDB(), secretsService, log.NewNopLogger())
|
||||||
features := featuremgmt.WithFeatures()
|
features := featuremgmt.WithFeatures()
|
||||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
var sqlStore db.DB = nil
|
||||||
|
dsRetriever := datasourceservice.ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := datasourceservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
||||||
&actest.FakePermissionsService{}, quotatest.New(false, nil), &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
&actest.FakePermissionsService{}, quotatest.New(false, nil), &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
||||||
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
|
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()), dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tracer := tracing.InitializeTracerForTest()
|
tracer := tracing.InitializeTracerForTest()
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import (
|
|||||||
_ "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
_ "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||||
_ "github.com/Azure/go-autorest/autorest"
|
_ "github.com/Azure/go-autorest/autorest"
|
||||||
_ "github.com/Azure/go-autorest/autorest/adal"
|
_ "github.com/Azure/go-autorest/autorest/adal"
|
||||||
|
_ "github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
_ "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
|
||||||
|
_ "github.com/aws/aws-sdk-go-v2/service/sts"
|
||||||
_ "github.com/beevik/etree"
|
_ "github.com/beevik/etree"
|
||||||
_ "github.com/blugelabs/bluge"
|
_ "github.com/blugelabs/bluge"
|
||||||
_ "github.com/blugelabs/bluge_segment_api"
|
_ "github.com/blugelabs/bluge_segment_api"
|
||||||
@@ -46,7 +49,6 @@ import (
|
|||||||
_ "sigs.k8s.io/randfill"
|
_ "sigs.k8s.io/randfill"
|
||||||
_ "xorm.io/builder"
|
_ "xorm.io/builder"
|
||||||
|
|
||||||
_ "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
|
|
||||||
_ "github.com/grafana/authlib/authn"
|
_ "github.com/grafana/authlib/authn"
|
||||||
_ "github.com/grafana/authlib/authz"
|
_ "github.com/grafana/authlib/authz"
|
||||||
_ "github.com/grafana/authlib/cache"
|
_ "github.com/grafana/authlib/cache"
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ func (ots *TracingService) initSampler() (tracesdk.Sampler, error) {
|
|||||||
case "rateLimiting":
|
case "rateLimiting":
|
||||||
return newRateLimiter(ots.cfg.SamplerParam), nil
|
return newRateLimiter(ots.cfg.SamplerParam), nil
|
||||||
case "remote":
|
case "remote":
|
||||||
return jaegerremote.New("grafana",
|
return jaegerremote.New(ots.cfg.ServiceName,
|
||||||
jaegerremote.WithSamplingServerURL(ots.cfg.SamplerRemoteURL),
|
jaegerremote.WithSamplingServerURL(ots.cfg.SamplerRemoteURL),
|
||||||
jaegerremote.WithInitialSampler(tracesdk.TraceIDRatioBased(ots.cfg.SamplerParam)),
|
jaegerremote.WithInitialSampler(tracesdk.TraceIDRatioBased(ots.cfg.SamplerParam)),
|
||||||
), nil
|
), nil
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ func (a *dashboardSqlAccess) CountResources(ctx context.Context, opts MigrateOpt
|
|||||||
case "folder.grafana.app/folders":
|
case "folder.grafana.app/folders":
|
||||||
summary := &resourcepb.BulkResponse_Summary{}
|
summary := &resourcepb.BulkResponse_Summary{}
|
||||||
summary.Group = folders.GROUP
|
summary.Group = folders.GROUP
|
||||||
summary.Group = folders.RESOURCE
|
summary.Resource = folders.RESOURCE
|
||||||
_, err = sess.SQL("SELECT COUNT(*) FROM "+sql.Table("dashboard")+
|
_, err = sess.SQL("SELECT COUNT(*) FROM "+sql.Table("dashboard")+
|
||||||
" WHERE is_folder=TRUE AND org_id=?", orgId).Get(&summary.Count)
|
" WHERE is_folder=TRUE AND org_id=?", orgId).Get(&summary.Count)
|
||||||
rsp.Summary = append(rsp.Summary, summary)
|
rsp.Summary = append(rsp.Summary, summary)
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ func (s *legacyStorage) ConvertToTable(ctx context.Context, object runtime.Objec
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||||
|
if s.dsConfigHandlerRequestsDuration != nil {
|
||||||
|
start := time.Now()
|
||||||
|
defer func() {
|
||||||
|
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("legacyStorage.List"), time.Since(start).Seconds())
|
||||||
|
}()
|
||||||
|
}
|
||||||
return s.datasources.ListDataSources(ctx)
|
return s.datasources.ListDataSources(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +70,7 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge
|
|||||||
if s.dsConfigHandlerRequestsDuration != nil {
|
if s.dsConfigHandlerRequestsDuration != nil {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("new", "Get"), time.Since(start).Seconds())
|
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("legacyStorage.Get"), time.Since(start).Seconds())
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +82,7 @@ func (s *legacyStorage) Create(ctx context.Context, obj runtime.Object, createVa
|
|||||||
if s.dsConfigHandlerRequestsDuration != nil {
|
if s.dsConfigHandlerRequestsDuration != nil {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("new", "Create"), time.Since(start).Seconds())
|
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("legacyStorage.Create"), time.Since(start).Seconds())
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +98,7 @@ func (s *legacyStorage) Update(ctx context.Context, name string, objInfo rest.Up
|
|||||||
if s.dsConfigHandlerRequestsDuration != nil {
|
if s.dsConfigHandlerRequestsDuration != nil {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("new", "Create"), time.Since(start).Seconds())
|
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("legacyStorage.Update"), time.Since(start).Seconds())
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +141,7 @@ func (s *legacyStorage) Delete(ctx context.Context, name string, deleteValidatio
|
|||||||
if s.dsConfigHandlerRequestsDuration != nil {
|
if s.dsConfigHandlerRequestsDuration != nil {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("new", "Create"), time.Since(start).Seconds())
|
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("legacyStorage.Delete"), time.Since(start).Seconds())
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +151,13 @@ func (s *legacyStorage) Delete(ctx context.Context, name string, deleteValidatio
|
|||||||
|
|
||||||
// DeleteCollection implements rest.CollectionDeleter.
|
// DeleteCollection implements rest.CollectionDeleter.
|
||||||
func (s *legacyStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
|
func (s *legacyStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
|
||||||
|
if s.dsConfigHandlerRequestsDuration != nil {
|
||||||
|
start := time.Now()
|
||||||
|
defer func() {
|
||||||
|
metricutil.ObserveWithExemplar(ctx, s.dsConfigHandlerRequestsDuration.WithLabelValues("legacyStorage.DeleteCollection"), time.Since(start).Seconds())
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
dss, err := s.datasources.ListDataSources(ctx)
|
dss, err := s.datasources.ListDataSources(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||||
queryV0 "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
queryV0 "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
||||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics/metricutil"
|
"github.com/grafana/grafana/pkg/infra/metrics/metricutil"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
||||||
@@ -69,10 +70,10 @@ func RegisterAPIService(
|
|||||||
|
|
||||||
dataSourceCRUDMetric := metricutil.NewHistogramVec(prometheus.HistogramOpts{
|
dataSourceCRUDMetric := metricutil.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
Namespace: "grafana",
|
Namespace: "grafana",
|
||||||
Name: "ds_config_handler_requests_duration_seconds",
|
Name: "ds_config_handler_apis_requests_duration_seconds",
|
||||||
Help: "Duration of requests handled by datasource configuration handlers",
|
Help: "Duration of requests handled by new k8s style APIs datasource configuration handlers",
|
||||||
}, []string{"code_path", "handler"})
|
}, []string{"handler"})
|
||||||
regErr := reg.Register(dataSourceCRUDMetric)
|
regErr := metrics.ProvideRegisterer().Register(dataSourceCRUDMetric)
|
||||||
if regErr != nil && !errors.As(regErr, &prometheus.AlreadyRegisteredError{}) {
|
if regErr != nil && !errors.As(regErr, &prometheus.AlreadyRegisteredError{}) {
|
||||||
return nil, regErr
|
return nil, regErr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func newIAMAuthorizer(
|
|||||||
resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer
|
resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer
|
||||||
resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer
|
resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer
|
||||||
resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer
|
resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer
|
||||||
resourceAuthorizer[iamv0.ExternalGroupMappingResourceInfo.GetName()] = authorizer
|
resourceAuthorizer[iamv0.ExternalGroupMappingResourceInfo.GetName()] = allowAuthorizer
|
||||||
resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = authorizer
|
resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = authorizer
|
||||||
resourceAuthorizer["searchUsers"] = serviceAuthorizer
|
resourceAuthorizer["searchUsers"] = serviceAuthorizer
|
||||||
resourceAuthorizer["searchTeams"] = serviceAuthorizer
|
resourceAuthorizer["searchTeams"] = serviceAuthorizer
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package authorizer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/authlib/types"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
|
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
"github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer/storewrapper"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalGroupMappingAuthorizer struct {
|
||||||
|
accessClient types.AccessClient
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ storewrapper.ResourceStorageAuthorizer = (*ExternalGroupMappingAuthorizer)(nil)
|
||||||
|
|
||||||
|
func NewExternalGroupMappingAuthorizer(
|
||||||
|
accessClient types.AccessClient,
|
||||||
|
) *ExternalGroupMappingAuthorizer {
|
||||||
|
return &ExternalGroupMappingAuthorizer{
|
||||||
|
accessClient: accessClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterGet implements ResourceStorageAuthorizer.
|
||||||
|
func (r *ExternalGroupMappingAuthorizer) AfterGet(ctx context.Context, obj runtime.Object) error {
|
||||||
|
authInfo, ok := types.AuthInfoFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
return storewrapper.ErrUnauthenticated
|
||||||
|
}
|
||||||
|
|
||||||
|
concreteObj, ok := obj.(*iamv0.ExternalGroupMapping)
|
||||||
|
if !ok {
|
||||||
|
return apierrors.NewInternalError(fmt.Errorf("expected ExternalGroupMapping, got %T: %w", obj, storewrapper.ErrUnexpectedType))
|
||||||
|
}
|
||||||
|
|
||||||
|
teamName := concreteObj.Spec.TeamRef.Name
|
||||||
|
checkReq := types.CheckRequest{
|
||||||
|
Namespace: authInfo.GetNamespace(),
|
||||||
|
Group: iamv0.GROUP,
|
||||||
|
Resource: iamv0.TeamResourceInfo.GetName(),
|
||||||
|
Verb: utils.VerbGetPermissions,
|
||||||
|
Name: teamName,
|
||||||
|
}
|
||||||
|
res, err := r.accessClient.Check(ctx, authInfo, checkReq, "")
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !res.Allowed {
|
||||||
|
return apierrors.NewForbidden(
|
||||||
|
iamv0.ExternalGroupMappingResourceInfo.GroupResource(),
|
||||||
|
concreteObj.Name,
|
||||||
|
fmt.Errorf("user cannot access team %s", teamName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeCreate implements ResourceStorageAuthorizer.
|
||||||
|
func (r *ExternalGroupMappingAuthorizer) BeforeCreate(ctx context.Context, obj runtime.Object) error {
|
||||||
|
return r.beforeWrite(ctx, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeDelete implements ResourceStorageAuthorizer.
|
||||||
|
func (r *ExternalGroupMappingAuthorizer) BeforeDelete(ctx context.Context, obj runtime.Object) error {
|
||||||
|
return r.beforeWrite(ctx, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeUpdate implements ResourceStorageAuthorizer.
|
||||||
|
func (r *ExternalGroupMappingAuthorizer) BeforeUpdate(ctx context.Context, obj runtime.Object) error {
|
||||||
|
// Update is not supported for ExternalGroupMapping resources and update attempts are blocked at a lower level,
|
||||||
|
// so this is just a safeguard.
|
||||||
|
return apierrors.NewMethodNotSupported(iamv0.ExternalGroupMappingResourceInfo.GroupResource(), "PUT/PATCH")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExternalGroupMappingAuthorizer) beforeWrite(ctx context.Context, obj runtime.Object) error {
|
||||||
|
authInfo, ok := types.AuthInfoFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
return storewrapper.ErrUnauthenticated
|
||||||
|
}
|
||||||
|
|
||||||
|
concreteObj, ok := obj.(*iamv0.ExternalGroupMapping)
|
||||||
|
if !ok {
|
||||||
|
return apierrors.NewInternalError(fmt.Errorf("expected ExternalGroupMapping, got %T: %w", obj, storewrapper.ErrUnexpectedType))
|
||||||
|
}
|
||||||
|
|
||||||
|
teamName := concreteObj.Spec.TeamRef.Name
|
||||||
|
checkReq := types.CheckRequest{
|
||||||
|
Namespace: authInfo.GetNamespace(),
|
||||||
|
Group: iamv0.GROUP,
|
||||||
|
Resource: iamv0.TeamResourceInfo.GetName(),
|
||||||
|
Verb: utils.VerbSetPermissions,
|
||||||
|
Name: teamName,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := r.accessClient.Check(ctx, authInfo, checkReq, "")
|
||||||
|
if err != nil {
|
||||||
|
return apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !res.Allowed {
|
||||||
|
return apierrors.NewForbidden(
|
||||||
|
iamv0.ExternalGroupMappingResourceInfo.GroupResource(),
|
||||||
|
concreteObj.Name,
|
||||||
|
fmt.Errorf("user cannot write team %s", teamName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterList implements ResourceStorageAuthorizer.
|
||||||
|
func (r *ExternalGroupMappingAuthorizer) FilterList(ctx context.Context, list runtime.Object) (runtime.Object, error) {
|
||||||
|
authInfo, ok := types.AuthInfoFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, storewrapper.ErrUnauthenticated
|
||||||
|
}
|
||||||
|
|
||||||
|
l, ok := list.(*iamv0.ExternalGroupMappingList)
|
||||||
|
if !ok {
|
||||||
|
return nil, apierrors.NewInternalError(fmt.Errorf("expected ExternalGroupMappingList, got %T: %w", list, storewrapper.ErrUnexpectedType))
|
||||||
|
}
|
||||||
|
|
||||||
|
var filteredItems []iamv0.ExternalGroupMapping
|
||||||
|
|
||||||
|
listReq := types.ListRequest{
|
||||||
|
Namespace: authInfo.GetNamespace(),
|
||||||
|
Group: iamv0.GROUP,
|
||||||
|
Resource: iamv0.TeamResourceInfo.GetName(),
|
||||||
|
Verb: utils.VerbGetPermissions,
|
||||||
|
}
|
||||||
|
canView, _, err := r.accessClient.Compile(ctx, authInfo, listReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, apierrors.NewInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range l.Items {
|
||||||
|
if canView(item.Spec.TeamRef.Name, "") {
|
||||||
|
filteredItems = append(filteredItems, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Items = filteredItems
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package authorizer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"github.com/grafana/authlib/types"
|
||||||
|
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newExternalGroupMapping(teamName, name string) *iamv0.ExternalGroupMapping {
|
||||||
|
return &iamv0.ExternalGroupMapping{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: "org-2", Name: name},
|
||||||
|
Spec: iamv0.ExternalGroupMappingSpec{
|
||||||
|
TeamRef: iamv0.ExternalGroupMappingTeamRef{
|
||||||
|
Name: teamName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExternalGroupMapping_AfterGet(t *testing.T) {
|
||||||
|
mapping := newExternalGroupMapping("team-1", "mapping-1")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
shouldAllow bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "allow access",
|
||||||
|
shouldAllow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deny access",
|
||||||
|
shouldAllow: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||||
|
require.NotNil(t, id)
|
||||||
|
require.Equal(t, "user:u001", id.GetUID())
|
||||||
|
require.Equal(t, "org-2", id.GetNamespace())
|
||||||
|
|
||||||
|
require.Equal(t, "org-2", req.Namespace)
|
||||||
|
require.Equal(t, iamv0.GROUP, req.Group)
|
||||||
|
require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource)
|
||||||
|
require.Equal(t, "team-1", req.Name)
|
||||||
|
require.Equal(t, utils.VerbGetPermissions, req.Verb)
|
||||||
|
require.Equal(t, "", folder)
|
||||||
|
|
||||||
|
return types.CheckResponse{Allowed: tt.shouldAllow}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
accessClient := &fakeAccessClient{checkFunc: checkFunc}
|
||||||
|
authz := NewExternalGroupMappingAuthorizer(accessClient)
|
||||||
|
ctx := types.WithAuthInfo(context.Background(), user)
|
||||||
|
|
||||||
|
err := authz.AfterGet(ctx, mapping)
|
||||||
|
if tt.shouldAllow {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
require.True(t, accessClient.checkCalled)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExternalGroupMapping_FilterList(t *testing.T) {
|
||||||
|
list := &iamv0.ExternalGroupMappingList{
|
||||||
|
Items: []iamv0.ExternalGroupMapping{
|
||||||
|
*newExternalGroupMapping("team-1", "mapping-1"),
|
||||||
|
*newExternalGroupMapping("team-2", "mapping-2"),
|
||||||
|
},
|
||||||
|
ListMeta: metav1.ListMeta{
|
||||||
|
SelfLink: "/apis/iam.grafana.app/v0alpha1/namespaces/org-2/externalgroupmappings",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
compileFunc := func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) {
|
||||||
|
require.NotNil(t, id)
|
||||||
|
require.Equal(t, "user:u001", id.GetUID())
|
||||||
|
require.Equal(t, "org-2", id.GetNamespace())
|
||||||
|
|
||||||
|
require.Equal(t, "org-2", req.Namespace)
|
||||||
|
require.Equal(t, iamv0.GROUP, req.Group)
|
||||||
|
require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource)
|
||||||
|
require.Equal(t, utils.VerbGetPermissions, req.Verb)
|
||||||
|
|
||||||
|
return func(name, folder string) bool {
|
||||||
|
return name == "team-1"
|
||||||
|
}, &types.NoopZookie{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
accessClient := &fakeAccessClient{compileFunc: compileFunc}
|
||||||
|
authz := NewExternalGroupMappingAuthorizer(accessClient)
|
||||||
|
ctx := types.WithAuthInfo(context.Background(), user)
|
||||||
|
|
||||||
|
obj, err := authz.FilterList(ctx, list)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, list)
|
||||||
|
require.True(t, accessClient.compileCalled)
|
||||||
|
|
||||||
|
filtered, ok := obj.(*iamv0.ExternalGroupMappingList)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Len(t, filtered.Items, 1)
|
||||||
|
require.Equal(t, "mapping-1", filtered.Items[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExternalGroupMapping_BeforeCreate(t *testing.T) {
|
||||||
|
mapping := newExternalGroupMapping("team-1", "mapping-1")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
shouldAllow bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "allow create",
|
||||||
|
shouldAllow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deny create",
|
||||||
|
shouldAllow: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||||
|
require.NotNil(t, id)
|
||||||
|
require.Equal(t, "user:u001", id.GetUID())
|
||||||
|
require.Equal(t, "org-2", id.GetNamespace())
|
||||||
|
|
||||||
|
require.Equal(t, "org-2", req.Namespace)
|
||||||
|
require.Equal(t, iamv0.GROUP, req.Group)
|
||||||
|
require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource)
|
||||||
|
require.Equal(t, "team-1", req.Name)
|
||||||
|
require.Equal(t, utils.VerbSetPermissions, req.Verb)
|
||||||
|
require.Equal(t, "", folder)
|
||||||
|
|
||||||
|
return types.CheckResponse{Allowed: tt.shouldAllow}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
accessClient := &fakeAccessClient{checkFunc: checkFunc}
|
||||||
|
authz := NewExternalGroupMappingAuthorizer(accessClient)
|
||||||
|
ctx := types.WithAuthInfo(context.Background(), user)
|
||||||
|
|
||||||
|
err := authz.BeforeCreate(ctx, mapping)
|
||||||
|
if tt.shouldAllow {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
require.True(t, accessClient.checkCalled)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExternalGroupMapping_BeforeUpdate(t *testing.T) {
|
||||||
|
mapping := newExternalGroupMapping("team-1", "mapping-1")
|
||||||
|
|
||||||
|
accessClient := &fakeAccessClient{
|
||||||
|
checkFunc: func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||||
|
require.Fail(t, "check should not be called")
|
||||||
|
return types.CheckResponse{}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
authz := NewExternalGroupMappingAuthorizer(accessClient)
|
||||||
|
ctx := types.WithAuthInfo(context.Background(), user)
|
||||||
|
|
||||||
|
err := authz.BeforeUpdate(ctx, mapping)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.True(t, apierrors.IsMethodNotSupported(err))
|
||||||
|
require.Contains(t, err.Error(), "PUT/PATCH")
|
||||||
|
require.False(t, accessClient.checkCalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExternalGroupMapping_BeforeDelete(t *testing.T) {
|
||||||
|
mapping := newExternalGroupMapping("team-1", "mapping-1")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
shouldAllow bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "allow delete",
|
||||||
|
shouldAllow: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deny delete",
|
||||||
|
shouldAllow: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||||
|
require.NotNil(t, id)
|
||||||
|
require.Equal(t, "user:u001", id.GetUID())
|
||||||
|
require.Equal(t, "org-2", id.GetNamespace())
|
||||||
|
|
||||||
|
require.Equal(t, "org-2", req.Namespace)
|
||||||
|
require.Equal(t, iamv0.GROUP, req.Group)
|
||||||
|
require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource)
|
||||||
|
require.Equal(t, "team-1", req.Name)
|
||||||
|
require.Equal(t, utils.VerbSetPermissions, req.Verb)
|
||||||
|
require.Equal(t, "", folder)
|
||||||
|
|
||||||
|
return types.CheckResponse{Allowed: tt.shouldAllow}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
accessClient := &fakeAccessClient{checkFunc: checkFunc}
|
||||||
|
authz := NewExternalGroupMappingAuthorizer(accessClient)
|
||||||
|
ctx := types.WithAuthInfo(context.Background(), user)
|
||||||
|
|
||||||
|
err := authz.BeforeDelete(ctx, mapping)
|
||||||
|
if tt.shouldAllow {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
require.True(t, accessClient.checkCalled)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,35 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v4/jwt"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
"github.com/grafana/authlib/authn"
|
|
||||||
"github.com/grafana/authlib/types"
|
"github.com/grafana/authlib/types"
|
||||||
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
user = authn.NewIDTokenAuthInfo(
|
|
||||||
authn.Claims[authn.AccessTokenClaims]{
|
|
||||||
Claims: jwt.Claims{Issuer: "grafana",
|
|
||||||
Subject: types.NewTypeID(types.TypeAccessPolicy, "grafana"), Audience: []string{"iam.grafana.app"}},
|
|
||||||
Rest: authn.AccessTokenClaims{
|
|
||||||
Namespace: "*",
|
|
||||||
Permissions: identity.ServiceIdentityClaims.Rest.Permissions,
|
|
||||||
DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions,
|
|
||||||
},
|
|
||||||
}, &authn.Claims[authn.IDTokenClaims]{
|
|
||||||
Claims: jwt.Claims{Subject: types.NewTypeID(types.TypeUser, "u001")},
|
|
||||||
Rest: authn.IDTokenClaims{Namespace: "org-2", Identifier: "u001", Type: types.TypeUser},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
func newResourcePermission(apiGroup, resource, name string) *iamv0.ResourcePermission {
|
func newResourcePermission(apiGroup, resource, name string) *iamv0.ResourcePermission {
|
||||||
return &iamv0.ResourcePermission{
|
return &iamv0.ResourcePermission{
|
||||||
ObjectMeta: metav1.ObjectMeta{Namespace: "org-2"},
|
ObjectMeta: metav1.ObjectMeta{Namespace: "org-2"},
|
||||||
@@ -222,26 +202,6 @@ func TestResourcePermissions_beforeWrite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fakeAccessClient is a mock implementation of claims.AccessClient
|
|
||||||
type fakeAccessClient struct {
|
|
||||||
checkCalled bool
|
|
||||||
checkFunc func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error)
|
|
||||||
compileCalled bool
|
|
||||||
compileFunc func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *fakeAccessClient) Check(ctx context.Context, id types.AuthInfo, req types.CheckRequest, folder string) (types.CheckResponse, error) {
|
|
||||||
m.checkCalled = true
|
|
||||||
return m.checkFunc(id, &req, folder)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *fakeAccessClient) Compile(ctx context.Context, id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) {
|
|
||||||
m.compileCalled = true
|
|
||||||
return m.compileFunc(id, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ types.AccessClient = (*fakeAccessClient)(nil)
|
|
||||||
|
|
||||||
type fakeParentProvider struct {
|
type fakeParentProvider struct {
|
||||||
hasParent bool
|
hasParent bool
|
||||||
getParentCalled bool
|
getParentCalled bool
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package authorizer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v4/jwt"
|
||||||
|
"github.com/grafana/authlib/authn"
|
||||||
|
"github.com/grafana/authlib/types"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Shared test user identity
|
||||||
|
user = authn.NewIDTokenAuthInfo(
|
||||||
|
authn.Claims[authn.AccessTokenClaims]{
|
||||||
|
Claims: jwt.Claims{Issuer: "grafana",
|
||||||
|
Subject: types.NewTypeID(types.TypeAccessPolicy, "grafana"), Audience: []string{"iam.grafana.app"}},
|
||||||
|
Rest: authn.AccessTokenClaims{
|
||||||
|
Namespace: "*",
|
||||||
|
Permissions: identity.ServiceIdentityClaims.Rest.Permissions,
|
||||||
|
DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions,
|
||||||
|
},
|
||||||
|
}, &authn.Claims[authn.IDTokenClaims]{
|
||||||
|
Claims: jwt.Claims{Subject: types.NewTypeID(types.TypeUser, "u001")},
|
||||||
|
Rest: authn.IDTokenClaims{Namespace: "org-2", Identifier: "u001", Type: types.TypeUser},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ types.AccessClient = (*fakeAccessClient)(nil)
|
||||||
|
|
||||||
|
// fakeAccessClient is a mock implementation of claims.AccessClient
|
||||||
|
type fakeAccessClient struct {
|
||||||
|
checkCalled bool
|
||||||
|
checkFunc func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error)
|
||||||
|
compileCalled bool
|
||||||
|
compileFunc func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *fakeAccessClient) Check(ctx context.Context, id types.AuthInfo, req types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||||
|
m.checkCalled = true
|
||||||
|
return m.checkFunc(id, &req, folder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *fakeAccessClient) Compile(ctx context.Context, id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) {
|
||||||
|
m.compileCalled = true
|
||||||
|
return m.compileFunc(id, req)
|
||||||
|
}
|
||||||
@@ -353,7 +353,8 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
storage[extGroupMappingResource.StoragePath()] = extGroupMappingUniStore
|
|
||||||
|
var extGroupMappingStore storewrapper.K8sStorage = extGroupMappingUniStore
|
||||||
|
|
||||||
if b.externalGroupMappingStorage != nil {
|
if b.externalGroupMappingStorage != nil {
|
||||||
extGroupMappingLegacyStore, err := NewLocalStore(extGroupMappingResource, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.externalGroupMappingStorage)
|
extGroupMappingLegacyStore, err := NewLocalStore(extGroupMappingResource, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.externalGroupMappingStorage)
|
||||||
@@ -365,9 +366,17 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
storage[extGroupMappingResource.StoragePath()] = dw
|
|
||||||
|
var ok bool
|
||||||
|
extGroupMappingStore, ok = dw.(storewrapper.K8sStorage)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expected storewrapper.K8sStorage, got %T", dw)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authzWrapper := storewrapper.New(extGroupMappingStore, iamauthorizer.NewExternalGroupMappingAuthorizer(b.accessClient))
|
||||||
|
storage[extGroupMappingResource.StoragePath()] = authzWrapper
|
||||||
|
|
||||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||||
if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis) {
|
if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis) {
|
||||||
// v0alpha1
|
// v0alpha1
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/registry/apps/annotation"
|
"github.com/grafana/grafana/pkg/registry/apps/annotation"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/correlations"
|
"github.com/grafana/grafana/pkg/registry/apps/correlations"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/example"
|
"github.com/grafana/grafana/pkg/registry/apps/example"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/investigations"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/logsdrilldown"
|
"github.com/grafana/grafana/pkg/registry/apps/logsdrilldown"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/playlist"
|
"github.com/grafana/grafana/pkg/registry/apps/playlist"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/plugins"
|
"github.com/grafana/grafana/pkg/registry/apps/plugins"
|
||||||
@@ -107,7 +106,6 @@ func ProvideBuilderRunners(
|
|||||||
registrar builder.APIRegistrar,
|
registrar builder.APIRegistrar,
|
||||||
restConfigProvider apiserver.RestConfigProvider,
|
restConfigProvider apiserver.RestConfigProvider,
|
||||||
features featuremgmt.FeatureToggles,
|
features featuremgmt.FeatureToggles,
|
||||||
investigationAppProvider *investigations.InvestigationsAppProvider,
|
|
||||||
grafanaCfg *setting.Cfg,
|
grafanaCfg *setting.Cfg,
|
||||||
) (*Service, error) {
|
) (*Service, error) {
|
||||||
cfgWrapper := func(ctx context.Context) (*rest.Config, error) {
|
cfgWrapper := func(ctx context.Context) (*rest.Config, error) {
|
||||||
@@ -127,11 +125,6 @@ func ProvideBuilderRunners(
|
|||||||
var apiGroupRunner *runner.APIGroupRunner
|
var apiGroupRunner *runner.APIGroupRunner
|
||||||
var err error
|
var err error
|
||||||
providers := []app.Provider{}
|
providers := []app.Provider{}
|
||||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
|
||||||
if features.IsEnabledGlobally(featuremgmt.FlagInvestigationsBackend) {
|
|
||||||
logger.Debug("Investigations backend is enabled")
|
|
||||||
providers = append(providers, investigationAppProvider)
|
|
||||||
}
|
|
||||||
apiGroupRunner, err = runner.NewAPIGroupRunner(cfg, providers...)
|
apiGroupRunner, err = runner.NewAPIGroupRunner(cfg, providers...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetAuthorizer() authorizer.Authorizer {
|
|
||||||
return authorizer.AuthorizerFunc(func(
|
|
||||||
ctx context.Context, attr authorizer.Attributes,
|
|
||||||
) (authorized authorizer.Decision, reason string, err error) {
|
|
||||||
if !attr.IsResourceRequest() {
|
|
||||||
return authorizer.DecisionNoOpinion, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := identity.GetRequester(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return authorizer.DecisionDeny, "valid user is required", err
|
|
||||||
}
|
|
||||||
|
|
||||||
p := u.GetPermissions()
|
|
||||||
if len(p) == 0 {
|
|
||||||
return authorizer.DecisionDeny, "no permissions", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok := p[accesscontrol.ActionDatasourcesExplore]
|
|
||||||
if !ok {
|
|
||||||
// defer to the default authorizer if datasources:explore is not present
|
|
||||||
return authorizer.DecisionNoOpinion, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorizer.DecisionAllow, "", nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetAuthorizer(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
ctx context.Context
|
|
||||||
attr authorizer.Attributes
|
|
||||||
expectedDecision authorizer.Decision
|
|
||||||
expectedReason string
|
|
||||||
expectedErr error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "non-resource request",
|
|
||||||
ctx: context.TODO(),
|
|
||||||
attr: &mockAttributes{resourceRequest: false},
|
|
||||||
expectedDecision: authorizer.DecisionNoOpinion,
|
|
||||||
expectedReason: "",
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "user has datasources:explore permission",
|
|
||||||
ctx: identity.WithRequester(context.TODO(), &mockUser{permissions: map[string][]string{accesscontrol.ActionDatasourcesExplore: {}}}),
|
|
||||||
attr: &mockAttributes{resourceRequest: true},
|
|
||||||
expectedDecision: authorizer.DecisionAllow,
|
|
||||||
expectedReason: "",
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "user does not have datasources:explore permission",
|
|
||||||
ctx: identity.WithRequester(context.TODO(), &mockUser{}),
|
|
||||||
attr: &mockAttributes{resourceRequest: true},
|
|
||||||
expectedDecision: authorizer.DecisionDeny,
|
|
||||||
expectedReason: "no permissions",
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "user does not have datasources:explore permission",
|
|
||||||
ctx: identity.WithRequester(context.TODO(), &mockUser{permissions: map[string][]string{"foo": {}}}),
|
|
||||||
attr: &mockAttributes{resourceRequest: true},
|
|
||||||
expectedDecision: authorizer.DecisionNoOpinion,
|
|
||||||
expectedReason: "",
|
|
||||||
expectedErr: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
auth := GetAuthorizer()
|
|
||||||
decision, reason, err := auth.Authorize(tt.ctx, tt.attr)
|
|
||||||
assert.Equal(t, tt.expectedDecision, decision)
|
|
||||||
assert.Equal(t, tt.expectedReason, reason)
|
|
||||||
assert.Equal(t, tt.expectedErr, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockAttributes struct {
|
|
||||||
authorizer.Attributes
|
|
||||||
resourceRequest bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockAttributes) IsResourceRequest() bool {
|
|
||||||
return m.resourceRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implement other methods of authorizer.Attributes as needed
|
|
||||||
|
|
||||||
type mockUser struct {
|
|
||||||
identity.Requester
|
|
||||||
permissions map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockUser) GetPermissions() map[string][]string {
|
|
||||||
return m.permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implement other methods of identity.Requester as needed
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana-app-sdk/app"
|
|
||||||
"github.com/grafana/grafana-app-sdk/simple"
|
|
||||||
"github.com/grafana/grafana/apps/investigations/pkg/apis"
|
|
||||||
investigationv0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1"
|
|
||||||
investigationapp "github.com/grafana/grafana/apps/investigations/pkg/app"
|
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/builder/runner"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InvestigationsAppProvider struct {
|
|
||||||
app.Provider
|
|
||||||
cfg *setting.Cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterApp(
|
|
||||||
cfg *setting.Cfg,
|
|
||||||
) *InvestigationsAppProvider {
|
|
||||||
provider := &InvestigationsAppProvider{
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
appCfg := &runner.AppBuilderConfig{
|
|
||||||
OpenAPIDefGetter: investigationv0alpha1.GetOpenAPIDefinitions,
|
|
||||||
ManagedKinds: investigationapp.GetKinds(),
|
|
||||||
Authorizer: GetAuthorizer(),
|
|
||||||
AllowedV0Alpha1Resources: []string{builder.AllResourcesAllowed},
|
|
||||||
}
|
|
||||||
provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, investigationapp.New)
|
|
||||||
return provider
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/apiserver"
|
"github.com/grafana/grafana/pkg/services/apiserver"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/appinstaller"
|
"github.com/grafana/grafana/pkg/services/apiserver/appinstaller"
|
||||||
grafanaauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
|
grafanaauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
)
|
)
|
||||||
@@ -36,9 +37,13 @@ func ProvideAppInstaller(
|
|||||||
pluginStore pluginstore.Store,
|
pluginStore pluginstore.Store,
|
||||||
pluginAssetsService *pluginassets.Service,
|
pluginAssetsService *pluginassets.Service,
|
||||||
accessControlService accesscontrol.Service, accessClient authlib.AccessClient,
|
accessControlService accesscontrol.Service, accessClient authlib.AccessClient,
|
||||||
|
features featuremgmt.FeatureToggles,
|
||||||
) (*AppInstaller, error) {
|
) (*AppInstaller, error) {
|
||||||
if err := registerAccessControlRoles(accessControlService); err != nil {
|
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||||
return nil, fmt.Errorf("registering access control roles: %w", err)
|
if features.IsEnabledGlobally(featuremgmt.FlagPluginStoreServiceLoading) {
|
||||||
|
if err := registerAccessControlRoles(accessControlService); err != nil {
|
||||||
|
return nil, fmt.Errorf("registering access control roles: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
localProvider := meta.NewLocalProvider(pluginStore, pluginAssetsService)
|
localProvider := meta.NewLocalProvider(pluginStore, pluginAssetsService)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/registry/apps/annotation"
|
"github.com/grafana/grafana/pkg/registry/apps/annotation"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/correlations"
|
"github.com/grafana/grafana/pkg/registry/apps/correlations"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/example"
|
"github.com/grafana/grafana/pkg/registry/apps/example"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/investigations"
|
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/logsdrilldown"
|
"github.com/grafana/grafana/pkg/registry/apps/logsdrilldown"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/playlist"
|
"github.com/grafana/grafana/pkg/registry/apps/playlist"
|
||||||
"github.com/grafana/grafana/pkg/registry/apps/plugins"
|
"github.com/grafana/grafana/pkg/registry/apps/plugins"
|
||||||
@@ -21,7 +20,6 @@ var WireSet = wire.NewSet(
|
|||||||
ProvideAppInstallers,
|
ProvideAppInstallers,
|
||||||
ProvideBuilderRunners,
|
ProvideBuilderRunners,
|
||||||
playlist.RegisterAppInstaller,
|
playlist.RegisterAppInstaller,
|
||||||
investigations.RegisterApp,
|
|
||||||
plugins.ProvideAppInstaller,
|
plugins.ProvideAppInstaller,
|
||||||
shorturl.RegisterAppInstaller,
|
shorturl.RegisterAppInstaller,
|
||||||
correlations.RegisterAppInstaller,
|
correlations.RegisterAppInstaller,
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ var wireBasicSet = wire.NewSet(
|
|||||||
dashsnapstore.ProvideStore,
|
dashsnapstore.ProvideStore,
|
||||||
wire.Bind(new(dashboardsnapshots.Service), new(*dashsnapsvc.ServiceImpl)),
|
wire.Bind(new(dashboardsnapshots.Service), new(*dashsnapsvc.ServiceImpl)),
|
||||||
dashsnapsvc.ProvideService,
|
dashsnapsvc.ProvideService,
|
||||||
|
datasourceservice.ProvideDataSourceRetriever,
|
||||||
datasourceservice.ProvideService,
|
datasourceservice.ProvideService,
|
||||||
wire.Bind(new(datasources.DataSourceService), new(*datasourceservice.Service)),
|
wire.Bind(new(datasources.DataSourceService), new(*datasourceservice.Service)),
|
||||||
datasourceservice.ProvideLegacyDataSourceLookup,
|
datasourceservice.ProvideLegacyDataSourceLookup,
|
||||||
|
|||||||
Generated
+9
-10
File diff suppressed because one or more lines are too long
@@ -3,7 +3,6 @@ package authorizer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
k8suser "k8s.io/apiserver/pkg/authentication/user"
|
k8suser "k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
@@ -29,9 +28,9 @@ type GrafanaAuthorizer struct {
|
|||||||
// 4. We check authorizer that is configured speficially for an api.
|
// 4. We check authorizer that is configured speficially for an api.
|
||||||
// 5. As a last fallback we check Role, this will only happen if an api have not configured
|
// 5. As a last fallback we check Role, this will only happen if an api have not configured
|
||||||
// an authorizer or return authorizer.DecisionNoOpinion
|
// an authorizer or return authorizer.DecisionNoOpinion
|
||||||
func NewGrafanaBuiltInSTAuthorizer(cfg *setting.Cfg) *GrafanaAuthorizer {
|
func NewGrafanaBuiltInSTAuthorizer() *GrafanaAuthorizer {
|
||||||
authorizers := []authorizer.Authorizer{
|
authorizers := []authorizer.Authorizer{
|
||||||
newImpersonationAuthorizer(),
|
NewImpersonationAuthorizer(),
|
||||||
authorizerfactory.NewPrivilegedGroups(k8suser.SystemPrivilegedGroup),
|
authorizerfactory.NewPrivilegedGroups(k8suser.SystemPrivilegedGroup),
|
||||||
newNamespaceAuthorizer(),
|
newNamespaceAuthorizer(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
var _ authorizer.Authorizer = (*impersonationAuthorizer)(nil)
|
var _ authorizer.Authorizer = (*impersonationAuthorizer)(nil)
|
||||||
|
|
||||||
func newImpersonationAuthorizer() *impersonationAuthorizer {
|
func NewImpersonationAuthorizer() *impersonationAuthorizer {
|
||||||
return &impersonationAuthorizer{}
|
return &impersonationAuthorizer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,19 +76,7 @@ var PathRewriters = []filters.PathRewriter{
|
|||||||
|
|
||||||
func GetDefaultBuildHandlerChainFunc(builders []APIGroupBuilder, reg prometheus.Registerer) BuildHandlerChainFunc {
|
func GetDefaultBuildHandlerChainFunc(builders []APIGroupBuilder, reg prometheus.Registerer) BuildHandlerChainFunc {
|
||||||
return func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler {
|
return func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler {
|
||||||
requestHandler, err := GetCustomRoutesHandler(
|
handler := filters.WithTracingHTTPLoggingAttributes(delegateHandler)
|
||||||
delegateHandler,
|
|
||||||
c.LoopbackClientConfig,
|
|
||||||
builders,
|
|
||||||
reg,
|
|
||||||
c.MergedResourceConfig,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("could not build the request handler for specified API builders: %s", err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Needs to run last in request chain to function as expected, hence we register it first.
|
|
||||||
handler := filters.WithTracingHTTPLoggingAttributes(requestHandler)
|
|
||||||
|
|
||||||
// filters.WithRequester needs to be after the K8s chain because it depends on the K8s user in context
|
// filters.WithRequester needs to be after the K8s chain because it depends on the K8s user in context
|
||||||
handler = filters.WithRequester(handler)
|
handler = filters.WithRequester(handler)
|
||||||
|
|||||||
@@ -3,146 +3,306 @@ package builder
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/emicklei/go-restful/v3"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
klog "k8s.io/klog/v2"
|
klog "k8s.io/klog/v2"
|
||||||
"k8s.io/kube-openapi/pkg/spec3"
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestHandler struct {
|
// convertHandlerToRouteFunction converts an http.HandlerFunc to a restful.RouteFunction
|
||||||
router *mux.Router
|
// It extracts path parameters from restful.Request and populates them in the request context
|
||||||
|
// so that mux.Vars can read them (for backward compatibility with handlers that use mux.Vars)
|
||||||
|
func convertHandlerToRouteFunction(handler http.HandlerFunc) restful.RouteFunction {
|
||||||
|
return func(req *restful.Request, resp *restful.Response) {
|
||||||
|
// Extract path parameters from restful.Request and populate mux.Vars
|
||||||
|
// This is needed for backward compatibility with handlers that use mux.Vars(r)
|
||||||
|
vars := make(map[string]string)
|
||||||
|
|
||||||
|
// Get all path parameters from the restful.Request
|
||||||
|
// The restful.Request has PathParameters() method that returns a map
|
||||||
|
pathParams := req.PathParameters()
|
||||||
|
for key, value := range pathParams {
|
||||||
|
vars[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the vars in the request context using mux.SetURLVars
|
||||||
|
// This makes mux.Vars(r) work correctly
|
||||||
|
if len(vars) > 0 {
|
||||||
|
req.Request = mux.SetURLVars(req.Request, vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler(resp.ResponseWriter, req.Request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder, metricsRegistry prometheus.Registerer, apiResourceConfig *serverstorage.ResourceConfig) (http.Handler, error) {
|
// AugmentWebServicesWithCustomRoutes adds custom routes from builders to existing WebServices
|
||||||
useful := false // only true if any routes exist anywhere
|
// in the container.
|
||||||
router := mux.NewRouter()
|
func AugmentWebServicesWithCustomRoutes(
|
||||||
|
container *restful.Container,
|
||||||
|
builders []APIGroupBuilder,
|
||||||
|
metricsRegistry prometheus.Registerer,
|
||||||
|
apiResourceConfig *serverstorage.ResourceConfig,
|
||||||
|
) error {
|
||||||
|
if container == nil {
|
||||||
|
return fmt.Errorf("container cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
metrics := NewCustomRouteMetrics(metricsRegistry)
|
metrics := NewCustomRouteMetrics(metricsRegistry)
|
||||||
|
|
||||||
for _, builder := range builders {
|
// Build a map of existing WebServices by root path
|
||||||
provider, ok := builder.(APIGroupRouteProvider)
|
existingWebServices := make(map[string]*restful.WebService)
|
||||||
|
for _, ws := range container.RegisteredWebServices() {
|
||||||
|
existingWebServices[ws.RootPath()] = ws
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range builders {
|
||||||
|
provider, ok := b.(APIGroupRouteProvider)
|
||||||
if !ok || provider == nil {
|
if !ok || provider == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, gv := range GetGroupVersions(builder) {
|
for _, gv := range GetGroupVersions(b) {
|
||||||
// filter out api groups that are disabled in APIEnablementOptions
|
// Filter out disabled API groups
|
||||||
gvr := gv.WithResource("")
|
gvr := gv.WithResource("")
|
||||||
if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) {
|
if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) {
|
||||||
klog.InfoS("Skipping custom route handler for disabled group version", "gv", gv.String())
|
klog.InfoS("Skipping custom routes for disabled group version", "gv", gv.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
routes := provider.GetAPIRoutes(gv)
|
routes := provider.GetAPIRoutes(gv)
|
||||||
if routes == nil {
|
if routes == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix := "/apis/" + gv.String()
|
// Find or create WebService for this group version
|
||||||
|
rootPath := "/apis/" + gv.String()
|
||||||
// Root handlers
|
ws, exists := existingWebServices[rootPath]
|
||||||
var sub *mux.Router
|
if !exists {
|
||||||
for _, route := range routes.Root {
|
// Create a new WebService if one doesn't exist
|
||||||
if sub == nil {
|
ws = new(restful.WebService)
|
||||||
sub = router.PathPrefix(prefix).Subrouter()
|
ws.Path(rootPath)
|
||||||
sub.MethodNotAllowedHandler = &methodNotAllowedHandler{}
|
container.Add(ws)
|
||||||
}
|
existingWebServices[rootPath] = ws
|
||||||
|
|
||||||
useful = true
|
|
||||||
methods, err := methodsFromSpec(route.Path, route.Spec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
instrumentedHandler := metrics.InstrumentHandler(
|
|
||||||
gv.Group,
|
|
||||||
gv.Version,
|
|
||||||
route.Path, // Use path as resource identifier
|
|
||||||
route.Handler,
|
|
||||||
)
|
|
||||||
|
|
||||||
sub.HandleFunc("/"+route.Path, instrumentedHandler).
|
|
||||||
Methods(methods...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace handlers
|
// Add root handlers using OpenAPI specs
|
||||||
sub = nil
|
for _, route := range routes.Root {
|
||||||
prefix += "/namespaces/{namespace}"
|
|
||||||
for _, route := range routes.Namespace {
|
|
||||||
if sub == nil {
|
|
||||||
sub = router.PathPrefix(prefix).Subrouter()
|
|
||||||
sub.MethodNotAllowedHandler = &methodNotAllowedHandler{}
|
|
||||||
}
|
|
||||||
|
|
||||||
useful = true
|
|
||||||
methods, err := methodsFromSpec(route.Path, route.Spec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
instrumentedHandler := metrics.InstrumentHandler(
|
instrumentedHandler := metrics.InstrumentHandler(
|
||||||
gv.Group,
|
gv.Group,
|
||||||
gv.Version,
|
gv.Version,
|
||||||
route.Path, // Use path as resource identifier
|
route.Path,
|
||||||
route.Handler,
|
route.Handler,
|
||||||
)
|
)
|
||||||
|
routeFunction := convertHandlerToRouteFunction(instrumentedHandler)
|
||||||
|
|
||||||
sub.HandleFunc("/"+route.Path, instrumentedHandler).
|
// Use OpenAPI spec to configure routes properly
|
||||||
Methods(methods...)
|
if err := addRouteFromSpec(ws, route.Path, route.Spec, routeFunction, false); err != nil {
|
||||||
|
return fmt.Errorf("failed to add root route %s: %w", route.Path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add namespace handlers using OpenAPI specs
|
||||||
|
for _, route := range routes.Namespace {
|
||||||
|
instrumentedHandler := metrics.InstrumentHandler(
|
||||||
|
gv.Group,
|
||||||
|
gv.Version,
|
||||||
|
route.Path,
|
||||||
|
route.Handler,
|
||||||
|
)
|
||||||
|
routeFunction := convertHandlerToRouteFunction(instrumentedHandler)
|
||||||
|
|
||||||
|
// Use OpenAPI spec to configure routes properly
|
||||||
|
if err := addRouteFromSpec(ws, route.Path, route.Spec, routeFunction, true); err != nil {
|
||||||
|
return fmt.Errorf("failed to add namespace route %s: %w", route.Path, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !useful {
|
return nil
|
||||||
return delegateHandler, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per Gorilla Mux issue here: https://github.com/gorilla/mux/issues/616#issuecomment-798807509
|
|
||||||
// default handler must come last
|
|
||||||
router.PathPrefix("/").Handler(delegateHandler)
|
|
||||||
|
|
||||||
return &requestHandler{
|
|
||||||
router: router,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *requestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
// addRouteFromSpec adds routes to a WebService using OpenAPI specs
|
||||||
h.router.ServeHTTP(w, req)
|
func addRouteFromSpec(ws *restful.WebService, routePath string, pathProps *spec3.PathProps, handler restful.RouteFunction, isNamespaced bool) error {
|
||||||
|
if pathProps == nil {
|
||||||
|
return fmt.Errorf("pathProps cannot be nil for route %s", routePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the full path (relative to WebService root)
|
||||||
|
var fullPath string
|
||||||
|
if isNamespaced {
|
||||||
|
fullPath = "/namespaces/{namespace}/" + routePath
|
||||||
|
} else {
|
||||||
|
fullPath = "/" + routePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add routes for each HTTP method defined in the OpenAPI spec
|
||||||
|
operations := map[string]*spec3.Operation{
|
||||||
|
"GET": pathProps.Get,
|
||||||
|
"POST": pathProps.Post,
|
||||||
|
"PUT": pathProps.Put,
|
||||||
|
"PATCH": pathProps.Patch,
|
||||||
|
"DELETE": pathProps.Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
for method, operation := range operations {
|
||||||
|
if operation == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create route builder for this method
|
||||||
|
var routeBuilder *restful.RouteBuilder
|
||||||
|
switch method {
|
||||||
|
case "GET":
|
||||||
|
routeBuilder = ws.GET(fullPath)
|
||||||
|
case "POST":
|
||||||
|
routeBuilder = ws.POST(fullPath)
|
||||||
|
case "PUT":
|
||||||
|
routeBuilder = ws.PUT(fullPath)
|
||||||
|
case "PATCH":
|
||||||
|
routeBuilder = ws.PATCH(fullPath)
|
||||||
|
case "DELETE":
|
||||||
|
routeBuilder = ws.DELETE(fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set operation ID from OpenAPI spec (with K8s verb prefix if needed)
|
||||||
|
operationID := operation.OperationId
|
||||||
|
if operationID == "" {
|
||||||
|
// Generate from path if not specified
|
||||||
|
operationID = generateOperationNameFromPath(routePath)
|
||||||
|
}
|
||||||
|
operationID = prefixRouteIDWithK8sVerbIfNotPresent(operationID, method)
|
||||||
|
routeBuilder = routeBuilder.Operation(operationID)
|
||||||
|
|
||||||
|
// Add description from OpenAPI spec
|
||||||
|
if operation.Description != "" {
|
||||||
|
routeBuilder = routeBuilder.Doc(operation.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if namespace parameter is already in the OpenAPI spec
|
||||||
|
hasNamespaceParam := false
|
||||||
|
if operation.Parameters != nil {
|
||||||
|
for _, param := range operation.Parameters {
|
||||||
|
if param.Name == "namespace" && param.In == "path" {
|
||||||
|
hasNamespaceParam = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add namespace parameter for namespaced routes if not already in spec
|
||||||
|
if isNamespaced && !hasNamespaceParam {
|
||||||
|
routeBuilder = routeBuilder.Param(restful.PathParameter("namespace", "object name and auth scope, such as for teams and projects"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add parameters from OpenAPI spec
|
||||||
|
if operation.Parameters != nil {
|
||||||
|
for _, param := range operation.Parameters {
|
||||||
|
switch param.In {
|
||||||
|
case "path":
|
||||||
|
routeBuilder = routeBuilder.Param(restful.PathParameter(param.Name, param.Description))
|
||||||
|
case "query":
|
||||||
|
routeBuilder = routeBuilder.Param(restful.QueryParameter(param.Name, param.Description))
|
||||||
|
case "header":
|
||||||
|
routeBuilder = routeBuilder.Param(restful.HeaderParameter(param.Name, param.Description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Request/response schemas are already defined in the OpenAPI spec from builders
|
||||||
|
// and will be added to the OpenAPI document via addBuilderRoutes in openapi.go.
|
||||||
|
// We don't duplicate that information here since restful uses the route metadata
|
||||||
|
// for OpenAPI generation, which is handled separately in this codebase.
|
||||||
|
|
||||||
|
// Register the route with handler
|
||||||
|
ws.Route(routeBuilder.To(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func methodsFromSpec(slug string, props *spec3.PathProps) ([]string, error) {
|
func prefixRouteIDWithK8sVerbIfNotPresent(operationID string, method string) string {
|
||||||
if props == nil {
|
for _, verb := range allowedK8sVerbs {
|
||||||
return []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, nil
|
if len(operationID) > len(verb) && operationID[:len(verb)] == verb {
|
||||||
|
return operationID
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return fmt.Sprintf("%s%s", httpMethodToK8sVerb[strings.ToUpper(method)], operationID)
|
||||||
methods := make([]string, 0)
|
|
||||||
if props.Get != nil {
|
|
||||||
methods = append(methods, "GET")
|
|
||||||
}
|
|
||||||
if props.Post != nil {
|
|
||||||
methods = append(methods, "POST")
|
|
||||||
}
|
|
||||||
if props.Put != nil {
|
|
||||||
methods = append(methods, "PUT")
|
|
||||||
}
|
|
||||||
if props.Patch != nil {
|
|
||||||
methods = append(methods, "PATCH")
|
|
||||||
}
|
|
||||||
if props.Delete != nil {
|
|
||||||
methods = append(methods, "DELETE")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(methods) == 0 {
|
|
||||||
return nil, fmt.Errorf("invalid OpenAPI Spec for slug=%s without any methods in PathProps", slug)
|
|
||||||
}
|
|
||||||
|
|
||||||
return methods, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type methodNotAllowedHandler struct{}
|
var allowedK8sVerbs = []string{
|
||||||
|
"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch",
|
||||||
func (h *methodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
}
|
||||||
w.WriteHeader(405) // method not allowed
|
|
||||||
|
var httpMethodToK8sVerb = map[string]string{
|
||||||
|
http.MethodGet: "get",
|
||||||
|
http.MethodPost: "create",
|
||||||
|
http.MethodPut: "replace",
|
||||||
|
http.MethodPatch: "patch",
|
||||||
|
http.MethodDelete: "delete",
|
||||||
|
http.MethodConnect: "connect",
|
||||||
|
http.MethodOptions: "connect", // No real equivalent to options and head
|
||||||
|
http.MethodHead: "connect",
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateOperationNameFromPath creates an operation name from a route path.
|
||||||
|
// The operation name is used by the OpenAPI generator and should be descriptive.
|
||||||
|
// It uses meaningful path segments to create readable yet unique operation names.
|
||||||
|
// Examples:
|
||||||
|
// - "/search" -> "Search"
|
||||||
|
// - "/snapshots/create" -> "SnapshotsCreate"
|
||||||
|
// - "ofrep/v1/evaluate/flags" -> "OfrepEvaluateFlags"
|
||||||
|
// - "ofrep/v1/evaluate/flags/{flagKey}" -> "OfrepEvaluateFlagsFlagKey"
|
||||||
|
func generateOperationNameFromPath(routePath string) string {
|
||||||
|
// Remove leading slash and split by path segments
|
||||||
|
parts := strings.Split(strings.TrimPrefix(routePath, "/"), "/")
|
||||||
|
|
||||||
|
// Filter to keep meaningful segments and path parameters
|
||||||
|
var nameParts []string
|
||||||
|
skipPrefixes := map[string]bool{
|
||||||
|
"namespaces": true,
|
||||||
|
"apis": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
if part == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract parameter name from {paramName} format
|
||||||
|
if strings.HasPrefix(part, "{") && strings.HasSuffix(part, "}") {
|
||||||
|
paramName := part[1 : len(part)-1]
|
||||||
|
// Skip generic parameters like {namespace}, but keep specific ones like {flagKey}
|
||||||
|
if paramName != "namespace" && paramName != "name" {
|
||||||
|
nameParts = append(nameParts, strings.ToUpper(paramName[:1])+paramName[1:])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip common prefixes
|
||||||
|
if skipPrefixes[strings.ToLower(part)] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip version segments like v1, v0alpha1, v2beta1, etc.
|
||||||
|
if strings.HasPrefix(strings.ToLower(part), "v") &&
|
||||||
|
(len(part) <= 3 || strings.Contains(strings.ToLower(part), "alpha") || strings.Contains(strings.ToLower(part), "beta")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capitalize first letter and add to parts
|
||||||
|
if len(part) > 0 {
|
||||||
|
nameParts = append(nameParts, strings.ToUpper(part[:1])+part[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nameParts) == 0 {
|
||||||
|
return "Route"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(nameParts, "")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/options"
|
"github.com/grafana/grafana/pkg/services/apiserver/options"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
@@ -41,14 +40,6 @@ func applyGrafanaConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles, o
|
|||||||
apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver")
|
apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver")
|
||||||
|
|
||||||
runtimeConfig := apiserverCfg.Key("runtime_config").String()
|
runtimeConfig := apiserverCfg.Key("runtime_config").String()
|
||||||
runtimeConfigSplit := strings.Split(runtimeConfig, ",")
|
|
||||||
|
|
||||||
// TODO: temporary fix to allow disabling local features service and still being able to use its authz handler
|
|
||||||
if !cfg.OpenFeature.APIEnabled {
|
|
||||||
runtimeConfigSplit = append(runtimeConfigSplit, "features.grafana.app/v0alpha1=false")
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimeConfig = strings.Join(runtimeConfigSplit, ",")
|
|
||||||
|
|
||||||
if runtimeConfig != "" {
|
if runtimeConfig != "" {
|
||||||
if err := o.APIEnablementOptions.RuntimeConfig.Set(runtimeConfig); err != nil {
|
if err := o.APIEnablementOptions.RuntimeConfig.Set(runtimeConfig); err != nil {
|
||||||
@@ -56,6 +47,12 @@ func applyGrafanaConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles, o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// equivalent to --request-timeout flag from k8s apiserver
|
||||||
|
requestTimeout := apiserverCfg.Key("request_timeout").MustDuration(0)
|
||||||
|
if requestTimeout > 0 {
|
||||||
|
o.ExtraOptions.RequestTimeout = requestTimeout
|
||||||
|
}
|
||||||
|
|
||||||
o.RecommendedOptions.Etcd.StorageConfig.Transport.ServerList = apiserverCfg.Key("etcd_servers").Strings(",")
|
o.RecommendedOptions.Etcd.StorageConfig.Transport.ServerList = apiserverCfg.Key("etcd_servers").Strings(",")
|
||||||
|
|
||||||
o.RecommendedOptions.SecureServing.BindAddress = ip
|
o.RecommendedOptions.SecureServing.BindAddress = ip
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package options
|
|||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
@@ -20,12 +21,14 @@ type ExtraOptions struct {
|
|||||||
ExternalAddress string
|
ExternalAddress string
|
||||||
APIURL string
|
APIURL string
|
||||||
Verbosity int
|
Verbosity int
|
||||||
|
RequestTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExtraOptions() *ExtraOptions {
|
func NewExtraOptions() *ExtraOptions {
|
||||||
return &ExtraOptions{
|
return &ExtraOptions{
|
||||||
DevMode: false,
|
DevMode: false,
|
||||||
Verbosity: 0,
|
Verbosity: 0,
|
||||||
|
RequestTimeout: 1 * time.Hour,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,15 @@ func (o *Options) ApplyTo(serverConfig *genericapiserver.RecommendedConfig) erro
|
|||||||
}
|
}
|
||||||
serverConfig.SecureServing = nil
|
serverConfig.SecureServing = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serverConfig.RequestTimeout is a k8s setting for all http requests, defaulting to 1 minute
|
||||||
|
// This setting is not removable so we force a long timeout to match existing behavior
|
||||||
|
// (ex: most (all?) sql datasources before apiservers were introduced did not have a global timeout and could run indefinitely)
|
||||||
|
// Normally for apiservers, this is set with a command line flag, --request-timeout, however in st-mode, we set a default in ExtraOptions
|
||||||
|
// and make it potentially configurable as needed by users in custom.ini
|
||||||
|
if o.ExtraOptions.RequestTimeout > 0 {
|
||||||
|
serverConfig.RequestTimeout = o.ExtraOptions.RequestTimeout
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ func ProvideService(
|
|||||||
features: features,
|
features: features,
|
||||||
rr: rr,
|
rr: rr,
|
||||||
builders: []builder.APIGroupBuilder{},
|
builders: []builder.APIGroupBuilder{},
|
||||||
authorizer: authorizer.NewGrafanaBuiltInSTAuthorizer(cfg),
|
authorizer: authorizer.NewGrafanaBuiltInSTAuthorizer(),
|
||||||
tracing: tracing,
|
tracing: tracing,
|
||||||
db: db, // For Unified storage
|
db: db, // For Unified storage
|
||||||
metrics: reg,
|
metrics: reg,
|
||||||
@@ -443,6 +443,19 @@ func (s *service) start(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Augment existing WebServices with custom routes from builders
|
||||||
|
// This directly adds routes to existing WebServices using the OpenAPI specs from builders
|
||||||
|
if server.Handler != nil && server.Handler.GoRestfulContainer != nil {
|
||||||
|
if err := builder.AugmentWebServicesWithCustomRoutes(
|
||||||
|
server.Handler.GoRestfulContainer,
|
||||||
|
builders,
|
||||||
|
s.metrics,
|
||||||
|
serverConfig.MergedResourceConfig,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("failed to augment web services with custom routes: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// stash the options for later use
|
// stash the options for later use
|
||||||
s.options = o
|
s.options = o
|
||||||
|
|
||||||
|
|||||||
@@ -182,25 +182,6 @@ func newFolderTranslation() translation {
|
|||||||
return folderTranslation
|
return folderTranslation
|
||||||
}
|
}
|
||||||
|
|
||||||
func newExternalGroupMappingTranslation() translation {
|
|
||||||
return translation{
|
|
||||||
resource: "teams.permissions",
|
|
||||||
attribute: "uid",
|
|
||||||
verbMapping: map[string]string{
|
|
||||||
utils.VerbGet: "teams.permissions:read",
|
|
||||||
utils.VerbList: "teams.permissions:read",
|
|
||||||
utils.VerbWatch: "teams.permissions:read",
|
|
||||||
utils.VerbCreate: "teams.permissions:write",
|
|
||||||
utils.VerbUpdate: "teams.permissions:write",
|
|
||||||
utils.VerbPatch: "teams.permissions:write",
|
|
||||||
utils.VerbDelete: "teams.permissions:write",
|
|
||||||
utils.VerbGetPermissions: "teams.permissions:write",
|
|
||||||
utils.VerbSetPermissions: "teams.permissions:write",
|
|
||||||
},
|
|
||||||
folderSupport: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMapperRegistry() MapperRegistry {
|
func NewMapperRegistry() MapperRegistry {
|
||||||
skipScopeOnAllVerbs := map[string]bool{
|
skipScopeOnAllVerbs := map[string]bool{
|
||||||
utils.VerbCreate: true,
|
utils.VerbCreate: true,
|
||||||
@@ -229,8 +210,6 @@ func NewMapperRegistry() MapperRegistry {
|
|||||||
"serviceaccounts": newResourceTranslation("serviceaccounts", "uid", false, map[string]bool{utils.VerbCreate: true}),
|
"serviceaccounts": newResourceTranslation("serviceaccounts", "uid", false, map[string]bool{utils.VerbCreate: true}),
|
||||||
// Teams is a special case. We translate user permissions from id to uid based.
|
// Teams is a special case. We translate user permissions from id to uid based.
|
||||||
"teams": newResourceTranslation("teams", "uid", false, map[string]bool{utils.VerbCreate: true}),
|
"teams": newResourceTranslation("teams", "uid", false, map[string]bool{utils.VerbCreate: true}),
|
||||||
// ExternalGroupMappings is a special case. We translate team permissions from id to uid based.
|
|
||||||
"externalgroupmappings": newExternalGroupMappingTranslation(),
|
|
||||||
"coreroles": translation{
|
"coreroles": translation{
|
||||||
resource: "roles",
|
resource: "roles",
|
||||||
attribute: "uid",
|
attribute: "uid",
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func ProvideZanzanaClient(cfg *setting.Cfg, db db.DB, tracer tracing.Tracer, fea
|
|||||||
authzv1.RegisterAuthzServiceServer(channel, srv)
|
authzv1.RegisterAuthzServiceServer(channel, srv)
|
||||||
authzextv1.RegisterAuthzExtentionServiceServer(channel, srv)
|
authzextv1.RegisterAuthzExtentionServiceServer(channel, srv)
|
||||||
|
|
||||||
client, err := zClient.New(channel)
|
client, err := zClient.New(channel, reg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize zanzana client: %w", err)
|
return nil, fmt.Errorf("failed to initialize zanzana client: %w", err)
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ func NewRemoteZanzanaClient(cfg ZanzanaClientConfig, reg prometheus.Registerer)
|
|||||||
return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err)
|
return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := zClient.New(conn)
|
client, err := zClient.New(conn, reg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize zanzana client: %w", err)
|
return nil, fmt.Errorf("failed to initialize zanzana client: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
authzlib "github.com/grafana/authlib/authz"
|
authzlib "github.com/grafana/authlib/authz"
|
||||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||||
authlib "github.com/grafana/authlib/types"
|
authlib "github.com/grafana/authlib/types"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
|
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
|
||||||
@@ -25,15 +26,17 @@ type Client struct {
|
|||||||
authz authzv1.AuthzServiceClient
|
authz authzv1.AuthzServiceClient
|
||||||
authzext authzextv1.AuthzExtentionServiceClient
|
authzext authzextv1.AuthzExtentionServiceClient
|
||||||
authzlibclient *authzlib.ClientImpl
|
authzlibclient *authzlib.ClientImpl
|
||||||
|
metrics *clientMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cc grpc.ClientConnInterface) (*Client, error) {
|
func New(cc grpc.ClientConnInterface, reg prometheus.Registerer) (*Client, error) {
|
||||||
authzlibclient := authzlib.NewClient(cc, authzlib.WithTracerClientOption(tracer))
|
authzlibclient := authzlib.NewClient(cc, authzlib.WithTracerClientOption(tracer))
|
||||||
c := &Client{
|
c := &Client{
|
||||||
authzlibclient: authzlibclient,
|
authzlibclient: authzlibclient,
|
||||||
authz: authzv1.NewAuthzServiceClient(cc),
|
authz: authzv1.NewAuthzServiceClient(cc),
|
||||||
authzext: authzextv1.NewAuthzExtentionServiceClient(cc),
|
authzext: authzextv1.NewAuthzExtentionServiceClient(cc),
|
||||||
logger: log.New("zanzana.client"),
|
logger: log.New("zanzana.client"),
|
||||||
|
metrics: newClientMetrics(reg),
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
@@ -43,6 +46,9 @@ func (c *Client) Check(ctx context.Context, id authlib.AuthInfo, req authlib.Che
|
|||||||
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check")
|
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Check", req.Namespace))
|
||||||
|
defer timer.ObserveDuration()
|
||||||
|
|
||||||
return c.authzlibclient.Check(ctx, id, req, folder)
|
return c.authzlibclient.Check(ctx, id, req, folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +56,9 @@ func (c *Client) Compile(ctx context.Context, id authlib.AuthInfo, req authlib.L
|
|||||||
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Compile")
|
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Compile")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Compile", req.Namespace))
|
||||||
|
defer timer.ObserveDuration()
|
||||||
|
|
||||||
return c.authzlibclient.Compile(ctx, id, req)
|
return c.authzlibclient.Compile(ctx, id, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +73,9 @@ func (c *Client) Write(ctx context.Context, req *authzextv1.WriteRequest) error
|
|||||||
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Write")
|
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Write")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Write", req.Namespace))
|
||||||
|
defer timer.ObserveDuration()
|
||||||
|
|
||||||
_, err := c.authzext.Write(ctx, req)
|
_, err := c.authzext.Write(ctx, req)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -72,6 +84,9 @@ func (c *Client) BatchCheck(ctx context.Context, req *authzextv1.BatchCheckReque
|
|||||||
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check")
|
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("BatchCheck", req.Namespace))
|
||||||
|
defer timer.ObserveDuration()
|
||||||
|
|
||||||
return c.authzext.BatchCheck(ctx, req)
|
return c.authzext.BatchCheck(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +102,9 @@ func (c *Client) Mutate(ctx context.Context, req *authzextv1.MutateRequest) erro
|
|||||||
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Mutate")
|
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Mutate")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Mutate", req.Namespace))
|
||||||
|
defer timer.ObserveDuration()
|
||||||
|
|
||||||
_, err := c.authzext.Mutate(ctx, req)
|
_, err := c.authzext.Mutate(ctx, req)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -95,5 +113,8 @@ func (c *Client) Query(ctx context.Context, req *authzextv1.QueryRequest) (*auth
|
|||||||
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Query")
|
ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Query")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
timer := prometheus.NewTimer(c.metrics.requestDurationSeconds.WithLabelValues("Query", req.Namespace))
|
||||||
|
defer timer.ObserveDuration()
|
||||||
|
|
||||||
return c.authzext.Query(ctx, req)
|
return c.authzext.Query(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
metricsNamespace = "iam"
|
metricsNamespace = "iam"
|
||||||
metricsSubSystem = "authz_zanzana"
|
metricsSubSystem = "authz_zanzana_client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type metrics struct {
|
type shadowClientMetrics struct {
|
||||||
// evaluationsSeconds is a summary for evaluating access for a specific engine (RBAC and zanzana)
|
// evaluationsSeconds is a summary for evaluating access for a specific engine (RBAC and zanzana)
|
||||||
evaluationsSeconds *prometheus.HistogramVec
|
evaluationsSeconds *prometheus.HistogramVec
|
||||||
// compileSeconds is a summary for compiling item checker for a specific engine (RBAC and zanzana)
|
// compileSeconds is a summary for compiling item checker for a specific engine (RBAC and zanzana)
|
||||||
@@ -19,8 +19,13 @@ type metrics struct {
|
|||||||
evaluationStatusTotal *prometheus.CounterVec
|
evaluationStatusTotal *prometheus.CounterVec
|
||||||
}
|
}
|
||||||
|
|
||||||
func newShadowClientMetrics(reg prometheus.Registerer) *metrics {
|
type clientMetrics struct {
|
||||||
return &metrics{
|
// requestDurationSeconds is a summary for zanzana client request duration
|
||||||
|
requestDurationSeconds *prometheus.HistogramVec
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShadowClientMetrics(reg prometheus.Registerer) *shadowClientMetrics {
|
||||||
|
return &shadowClientMetrics{
|
||||||
evaluationsSeconds: promauto.With(reg).NewHistogramVec(
|
evaluationsSeconds: promauto.With(reg).NewHistogramVec(
|
||||||
prometheus.HistogramOpts{
|
prometheus.HistogramOpts{
|
||||||
Name: "engine_evaluations_seconds",
|
Name: "engine_evaluations_seconds",
|
||||||
@@ -52,3 +57,18 @@ func newShadowClientMetrics(reg prometheus.Registerer) *metrics {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newClientMetrics(reg prometheus.Registerer) *clientMetrics {
|
||||||
|
return &clientMetrics{
|
||||||
|
requestDurationSeconds: promauto.With(reg).NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "request_duration_seconds",
|
||||||
|
Help: "Histogram for zanzana client request duration",
|
||||||
|
Namespace: metricsNamespace,
|
||||||
|
Subsystem: metricsSubSystem,
|
||||||
|
Buckets: prometheus.ExponentialBuckets(0.00001, 4, 10),
|
||||||
|
},
|
||||||
|
[]string{"method", "request_namespace"},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type ShadowClient struct {
|
|||||||
logger log.Logger
|
logger log.Logger
|
||||||
accessClient authlib.AccessClient
|
accessClient authlib.AccessClient
|
||||||
zanzanaClient authlib.AccessClient
|
zanzanaClient authlib.AccessClient
|
||||||
metrics *metrics
|
metrics *shadowClientMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithShadowClient returns a new access client that runs zanzana checks in the background.
|
// WithShadowClient returns a new access client that runs zanzana checks in the background.
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ type Service struct {
|
|||||||
pluginStore pluginstore.Store
|
pluginStore pluginstore.Store
|
||||||
pluginClient plugins.Client
|
pluginClient plugins.Client
|
||||||
basePluginContextProvider plugincontext.BasePluginContextProvider
|
basePluginContextProvider plugincontext.BasePluginContextProvider
|
||||||
|
retriever DataSourceRetriever
|
||||||
|
|
||||||
ptc proxyTransportCache
|
ptc proxyTransportCache
|
||||||
}
|
}
|
||||||
@@ -70,6 +71,7 @@ func ProvideService(
|
|||||||
features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl, datasourcePermissionsService accesscontrol.DatasourcePermissionsService,
|
features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl, datasourcePermissionsService accesscontrol.DatasourcePermissionsService,
|
||||||
quotaService quota.Service, pluginStore pluginstore.Store, pluginClient plugins.Client,
|
quotaService quota.Service, pluginStore pluginstore.Store, pluginClient plugins.Client,
|
||||||
basePluginContextProvider plugincontext.BasePluginContextProvider,
|
basePluginContextProvider plugincontext.BasePluginContextProvider,
|
||||||
|
retriever DataSourceRetriever,
|
||||||
) (*Service, error) {
|
) (*Service, error) {
|
||||||
dslogger := log.New("datasources")
|
dslogger := log.New("datasources")
|
||||||
store := &SqlStore{db: db, logger: dslogger, features: features}
|
store := &SqlStore{db: db, logger: dslogger, features: features}
|
||||||
@@ -89,6 +91,7 @@ func ProvideService(
|
|||||||
pluginStore: pluginStore,
|
pluginStore: pluginStore,
|
||||||
pluginClient: pluginClient,
|
pluginClient: pluginClient,
|
||||||
basePluginContextProvider: basePluginContextProvider,
|
basePluginContextProvider: basePluginContextProvider,
|
||||||
|
retriever: retriever,
|
||||||
}
|
}
|
||||||
|
|
||||||
ac.RegisterScopeAttributeResolver(NewNameScopeResolver(store))
|
ac.RegisterScopeAttributeResolver(NewNameScopeResolver(store))
|
||||||
@@ -175,11 +178,11 @@ func NewIDScopeResolver(db DataSourceRetriever) (string, accesscontrol.ScopeAttr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetDataSource(ctx context.Context, query *datasources.GetDataSourceQuery) (*datasources.DataSource, error) {
|
func (s *Service) GetDataSource(ctx context.Context, query *datasources.GetDataSourceQuery) (*datasources.DataSource, error) {
|
||||||
return s.SQLStore.GetDataSource(ctx, query)
|
return s.retriever.GetDataSource(ctx, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*datasources.DataSource, error) {
|
func (s *Service) GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*datasources.DataSource, error) {
|
||||||
return s.SQLStore.GetDataSourceInNamespace(ctx, namespace, name, group)
|
return s.retriever.GetDataSourceInNamespace(ctx, namespace, name, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetDataSources(ctx context.Context, query *datasources.GetDataSourcesQuery) ([]*datasources.DataSource, error) {
|
func (s *Service) GetDataSources(ctx context.Context, query *datasources.GetDataSourcesQuery) ([]*datasources.DataSource, error) {
|
||||||
|
|||||||
@@ -832,8 +832,9 @@ func TestIntegrationService_DeleteDataSource(t *testing.T) {
|
|||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
permissionSvc := acmock.NewMockedPermissionsService()
|
permissionSvc := acmock.NewMockedPermissionsService()
|
||||||
permissionSvc.On("DeleteResourcePermissions", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
|
permissionSvc.On("DeleteResourcePermissions", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
|
||||||
|
features := featuremgmt.WithFeatures()
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, featuremgmt.WithFeatures(), acmock.New(), permissionSvc, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, features, acmock.New(), permissionSvc, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
cmd := &datasources.DeleteDataSourceCommand{
|
cmd := &datasources.DeleteDataSourceCommand{
|
||||||
@@ -857,7 +858,9 @@ func TestIntegrationService_DeleteDataSource(t *testing.T) {
|
|||||||
permissionSvc.On("DeleteResourcePermissions", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
permissionSvc.On("DeleteResourcePermissions", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||||
cfg := &setting.Cfg{}
|
cfg := &setting.Cfg{}
|
||||||
enableRBACManagedPermissions(t, cfg)
|
enableRBACManagedPermissions(t, cfg)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), permissionSvc, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), permissionSvc, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// First add the datasource
|
// First add the datasource
|
||||||
@@ -1124,7 +1127,9 @@ func TestIntegrationService_GetHttpTransport(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rt1, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
|
rt1, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
|
||||||
@@ -1161,7 +1166,9 @@ func TestIntegrationService_GetHttpTransport(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ds := datasources.DataSource{
|
ds := datasources.DataSource{
|
||||||
@@ -1212,7 +1219,9 @@ func TestIntegrationService_GetHttpTransport(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ds := datasources.DataSource{
|
ds := datasources.DataSource{
|
||||||
@@ -1260,7 +1269,9 @@ func TestIntegrationService_GetHttpTransport(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ds := datasources.DataSource{
|
ds := datasources.DataSource{
|
||||||
@@ -1316,7 +1327,9 @@ func TestIntegrationService_GetHttpTransport(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ds := datasources.DataSource{
|
ds := datasources.DataSource{
|
||||||
@@ -1351,7 +1364,9 @@ func TestIntegrationService_GetHttpTransport(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ds := datasources.DataSource{
|
ds := datasources.DataSource{
|
||||||
@@ -1420,7 +1435,9 @@ func TestIntegrationService_GetHttpTransport(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ds := datasources.DataSource{
|
ds := datasources.DataSource{
|
||||||
@@ -1499,7 +1516,9 @@ func TestIntegrationService_GetHttpTransport(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ds := datasources.DataSource{
|
ds := datasources.DataSource{
|
||||||
@@ -1522,7 +1541,9 @@ func TestIntegrationService_getProxySettings(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("Should default to disabled", func(t *testing.T) {
|
t.Run("Should default to disabled", func(t *testing.T) {
|
||||||
@@ -1620,7 +1641,9 @@ func TestIntegrationService_getTimeout(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -1645,7 +1668,9 @@ func TestIntegrationService_GetDecryptedValues(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
jsonData := map[string]string{
|
jsonData := map[string]string{
|
||||||
@@ -1673,7 +1698,9 @@ func TestIntegrationService_GetDecryptedValues(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
jsonData := map[string]string{
|
jsonData := map[string]string{
|
||||||
@@ -1699,7 +1726,9 @@ func TestIntegrationDataSource_CustomHeaders(t *testing.T) {
|
|||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil, dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
dsService.cfg = setting.NewCfg()
|
dsService.cfg = setting.NewCfg()
|
||||||
@@ -1788,7 +1817,9 @@ func initDSService(t *testing.T) *Service {
|
|||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
mockPermission := acmock.NewMockedPermissionsService()
|
mockPermission := acmock.NewMockedPermissionsService()
|
||||||
mockPermission.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
mockPermission.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{
|
features := featuremgmt.WithFeatures()
|
||||||
|
dsRetriever := ProvideDataSourceRetriever(sqlStore, features)
|
||||||
|
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, features, actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{
|
||||||
PluginList: []pluginstore.Plugin{{
|
PluginList: []pluginstore.Plugin{{
|
||||||
JSONData: plugins.JSONData{
|
JSONData: plugins.JSONData{
|
||||||
ID: "test",
|
ID: "test",
|
||||||
@@ -1808,7 +1839,7 @@ func initDSService(t *testing.T) *Service {
|
|||||||
ObjectBytes: req.ObjectBytes,
|
ObjectBytes: req.ObjectBytes,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
}, plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
|
}, plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()), dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return dsService
|
return dsService
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DataSourceRetrieverImpl implements DataSourceRetriever by delegating to a Store.
|
||||||
|
type DataSourceRetrieverImpl struct {
|
||||||
|
store Store
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ DataSourceRetriever = (*DataSourceRetrieverImpl)(nil)
|
||||||
|
|
||||||
|
// ProvideDataSourceRetriever creates a DataSourceRetriever for wire injection.
|
||||||
|
func ProvideDataSourceRetriever(db db.DB, features featuremgmt.FeatureToggles) DataSourceRetriever {
|
||||||
|
dslogger := log.New("datasources-retriever")
|
||||||
|
store := &SqlStore{db: db, logger: dslogger, features: features}
|
||||||
|
return &DataSourceRetrieverImpl{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDataSource gets a datasource.
|
||||||
|
func (r *DataSourceRetrieverImpl) GetDataSource(ctx context.Context, query *datasources.GetDataSourceQuery) (*datasources.DataSource, error) {
|
||||||
|
return r.store.GetDataSource(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDataSourceInNamespace gets a datasource by namespace, name (datasource uid), and group (datasource type).
|
||||||
|
func (r *DataSourceRetrieverImpl) GetDataSourceInNamespace(ctx context.Context, namespace, name, group string) (*datasources.DataSource, error) {
|
||||||
|
return r.store.GetDataSourceInNamespace(ctx, namespace, name, group)
|
||||||
|
}
|
||||||
@@ -322,6 +322,13 @@ var (
|
|||||||
Owner: grafanaOperatorExperienceSquad,
|
Owner: grafanaOperatorExperienceSquad,
|
||||||
RequiresRestart: true,
|
RequiresRestart: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "reportingCsvEncodingOptions",
|
||||||
|
Description: "Enables CSV encoding options in the reporting feature",
|
||||||
|
Stage: FeatureStageExperimental,
|
||||||
|
FrontendOnly: false,
|
||||||
|
Owner: grafanaOperatorExperienceSquad,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "sseGroupByDatasource",
|
Name: "sseGroupByDatasource",
|
||||||
Description: "Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.",
|
Description: "Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.",
|
||||||
@@ -1299,13 +1306,6 @@ var (
|
|||||||
Owner: grafanaObservabilityLogsSquad,
|
Owner: grafanaObservabilityLogsSquad,
|
||||||
Expression: "true",
|
Expression: "true",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "investigationsBackend",
|
|
||||||
Description: "Enable the investigations backend API",
|
|
||||||
Stage: FeatureStageExperimental,
|
|
||||||
Owner: grafanaAppPlatformSquad,
|
|
||||||
Expression: "false",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "k8SFolderCounts",
|
Name: "k8SFolderCounts",
|
||||||
Description: "Enable folder's api server counts",
|
Description: "Enable folder's api server counts",
|
||||||
|
|||||||
Generated
+1
-1
@@ -43,6 +43,7 @@ configurableSchedulerTick,experimental,@grafana/alerting-squad,false,true,false
|
|||||||
dashgpt,GA,@grafana/dashboards-squad,false,false,true
|
dashgpt,GA,@grafana/dashboards-squad,false,false,true
|
||||||
aiGeneratedDashboardChanges,experimental,@grafana/dashboards-squad,false,false,true
|
aiGeneratedDashboardChanges,experimental,@grafana/dashboards-squad,false,false,true
|
||||||
reportingRetries,preview,@grafana/grafana-operator-experience-squad,false,true,false
|
reportingRetries,preview,@grafana/grafana-operator-experience-squad,false,true,false
|
||||||
|
reportingCsvEncodingOptions,experimental,@grafana/grafana-operator-experience-squad,false,false,false
|
||||||
sseGroupByDatasource,experimental,@grafana/grafana-datasources-core-services,false,false,false
|
sseGroupByDatasource,experimental,@grafana/grafana-datasources-core-services,false,false,false
|
||||||
lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,false,false,false
|
lokiRunQueriesInParallel,privatePreview,@grafana/observability-logs,false,false,false
|
||||||
externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false
|
externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false
|
||||||
@@ -179,7 +180,6 @@ unifiedStorageSearchUI,experimental,@grafana/search-and-storage,false,false,fals
|
|||||||
elasticsearchCrossClusterSearch,GA,@grafana/partner-datasources,false,false,false
|
elasticsearchCrossClusterSearch,GA,@grafana/partner-datasources,false,false,false
|
||||||
unifiedHistory,experimental,@grafana/grafana-search-navigate-organise,false,false,true
|
unifiedHistory,experimental,@grafana/grafana-search-navigate-organise,false,false,true
|
||||||
lokiLabelNamesQueryApi,GA,@grafana/observability-logs,false,false,false
|
lokiLabelNamesQueryApi,GA,@grafana/observability-logs,false,false,false
|
||||||
investigationsBackend,experimental,@grafana/grafana-app-platform-squad,false,false,false
|
|
||||||
k8SFolderCounts,experimental,@grafana/search-and-storage,false,false,false
|
k8SFolderCounts,experimental,@grafana/search-and-storage,false,false,false
|
||||||
k8SFolderMove,experimental,@grafana/search-and-storage,false,false,false
|
k8SFolderMove,experimental,@grafana/search-and-storage,false,false,false
|
||||||
improvedExternalSessionHandlingSAML,GA,@grafana/identity-access-team,false,false,false
|
improvedExternalSessionHandlingSAML,GA,@grafana/identity-access-team,false,false,false
|
||||||
|
|||||||
|
Generated
+4
-4
@@ -135,6 +135,10 @@ const (
|
|||||||
// Enables rendering retries for the reporting feature
|
// Enables rendering retries for the reporting feature
|
||||||
FlagReportingRetries = "reportingRetries"
|
FlagReportingRetries = "reportingRetries"
|
||||||
|
|
||||||
|
// FlagReportingCsvEncodingOptions
|
||||||
|
// Enables CSV encoding options in the reporting feature
|
||||||
|
FlagReportingCsvEncodingOptions = "reportingCsvEncodingOptions"
|
||||||
|
|
||||||
// FlagSseGroupByDatasource
|
// FlagSseGroupByDatasource
|
||||||
// Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.
|
// Send query to the same datasource in a single request when using server side expressions. The `cloudWatchBatchQueries` feature toggle should be enabled if this used with CloudWatch.
|
||||||
FlagSseGroupByDatasource = "sseGroupByDatasource"
|
FlagSseGroupByDatasource = "sseGroupByDatasource"
|
||||||
@@ -539,10 +543,6 @@ const (
|
|||||||
// Defaults to using the Loki `/labels` API instead of `/series`
|
// Defaults to using the Loki `/labels` API instead of `/series`
|
||||||
FlagLokiLabelNamesQueryApi = "lokiLabelNamesQueryApi"
|
FlagLokiLabelNamesQueryApi = "lokiLabelNamesQueryApi"
|
||||||
|
|
||||||
// FlagInvestigationsBackend
|
|
||||||
// Enable the investigations backend API
|
|
||||||
FlagInvestigationsBackend = "investigationsBackend"
|
|
||||||
|
|
||||||
// FlagK8SFolderCounts
|
// FlagK8SFolderCounts
|
||||||
// Enable folder's api server counts
|
// Enable folder's api server counts
|
||||||
FlagK8SFolderCounts = "k8SFolderCounts"
|
FlagK8SFolderCounts = "k8SFolderCounts"
|
||||||
|
|||||||
+14
-1
@@ -1793,7 +1793,8 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "investigationsBackend",
|
"name": "investigationsBackend",
|
||||||
"resourceVersion": "1764664939750",
|
"resourceVersion": "1764664939750",
|
||||||
"creationTimestamp": "2024-12-18T08:31:03Z"
|
"creationTimestamp": "2024-12-18T08:31:03Z",
|
||||||
|
"deletionTimestamp": "2025-12-16T16:06:24Z"
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"description": "Enable the investigations backend API",
|
"description": "Enable the investigations backend API",
|
||||||
@@ -3136,6 +3137,18 @@
|
|||||||
"hideFromDocs": true
|
"hideFromDocs": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "reportingCsvEncodingOptions",
|
||||||
|
"resourceVersion": "1766080709938",
|
||||||
|
"creationTimestamp": "2025-12-18T17:58:29Z"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"description": "Enables CSV encoding options in the reporting feature",
|
||||||
|
"stage": "experimental",
|
||||||
|
"codeowner": "@grafana/grafana-operator-experience-squad"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "reportingRetries",
|
"name": "reportingRetries",
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
|
|||||||
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingTriage) {
|
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagAlertingTriage) {
|
||||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
|
||||||
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
|
||||||
Text: "Alerts", SubTitle: "Visualize active and pending alerts", Id: "alert-alerts", Url: s.cfg.AppSubURL + "/alerting/alerts", Icon: "bell", IsNew: true,
|
Text: "Alert activity", SubTitle: "Visualize active and pending alerts", Id: "alert-alerts", Url: s.cfg.AppSubURL + "/alerting/alerts", Icon: "bell", IsNew: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -542,9 +542,10 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe
|
|||||||
dashService.RegisterDashboardPermissions(acmock.NewMockedPermissionsService())
|
dashService.RegisterDashboardPermissions(acmock.NewMockedPermissionsService())
|
||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||||
|
dsRetriever := dsservice.ProvideDataSourceRetriever(sqlStore, featuremgmt.WithFeatures())
|
||||||
_, err = dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(),
|
_, err = dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(),
|
||||||
quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, plugincontext.
|
quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, plugincontext.
|
||||||
ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
|
ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()), dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
m := metrics.NewNGAlert(prometheus.NewRegistry())
|
m := metrics.NewNGAlert(prometheus.NewRegistry())
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ func SetupTestDataSourceSecretMigrationService(t *testing.T, sqlStore db.DB, kvS
|
|||||||
features := featuremgmt.WithFeatures()
|
features := featuremgmt.WithFeatures()
|
||||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
quotaService := quotatest.New(false, nil)
|
quotaService := quotatest.New(false, nil)
|
||||||
|
dsRetriever := dsservice.ProvideDataSourceRetriever(sqlStore, features)
|
||||||
dsService, err := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(),
|
dsService, err := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(),
|
||||||
acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{},
|
||||||
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
|
plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()), dsRetriever)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
migService := ProvideDataSourceMigrationService(dsService, kvStore, features)
|
migService := ProvideDataSourceMigrationService(dsService, kvStore, features)
|
||||||
return migService
|
return migService
|
||||||
|
|||||||
@@ -637,6 +637,8 @@ type UnifiedStorageConfig struct {
|
|||||||
// EnableMigration indicates whether migration is enabled for the resource.
|
// EnableMigration indicates whether migration is enabled for the resource.
|
||||||
// If not set, will use the default from MigratedUnifiedResources.
|
// If not set, will use the default from MigratedUnifiedResources.
|
||||||
EnableMigration bool
|
EnableMigration bool
|
||||||
|
// AutoMigrationThreshold is the threshold below which a resource is automatically migrated.
|
||||||
|
AutoMigrationThreshold int
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstallPlugin struct {
|
type InstallPlugin struct {
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/util/osutil"
|
"github.com/grafana/grafana/pkg/util/osutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultAutoMigrationThreshold is the default threshold for auto migration switching.
|
||||||
|
// If a resource has entries at or below this count, it will be migrated.
|
||||||
|
const DefaultAutoMigrationThreshold = 10
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PlaylistResource = "playlists.playlist.grafana.app"
|
PlaylistResource = "playlists.playlist.grafana.app"
|
||||||
FolderResource = "folders.folder.grafana.app"
|
FolderResource = "folders.folder.grafana.app"
|
||||||
@@ -21,6 +25,13 @@ var MigratedUnifiedResources = map[string]bool{
|
|||||||
DashboardResource: false,
|
DashboardResource: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AutoMigratedUnifiedResources maps resources that support auto-migration
|
||||||
|
// TODO: remove this before Grafana 13 GA: https://github.com/grafana/search-and-storage-team/issues/613
|
||||||
|
var AutoMigratedUnifiedResources = map[string]bool{
|
||||||
|
FolderResource: true,
|
||||||
|
DashboardResource: true,
|
||||||
|
}
|
||||||
|
|
||||||
// read storage configs from ini file. They look like:
|
// read storage configs from ini file. They look like:
|
||||||
// [unified_storage.<group>.<resource>]
|
// [unified_storage.<group>.<resource>]
|
||||||
// <field> = <value>
|
// <field> = <value>
|
||||||
@@ -59,6 +70,13 @@ func (cfg *Cfg) setUnifiedStorageConfig() {
|
|||||||
enableMigration = section.Key("enableMigration").MustBool(MigratedUnifiedResources[resourceName])
|
enableMigration = section.Key("enableMigration").MustBool(MigratedUnifiedResources[resourceName])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse autoMigrationThreshold from resource section
|
||||||
|
autoMigrationThreshold := 0
|
||||||
|
autoMigrate := AutoMigratedUnifiedResources[resourceName]
|
||||||
|
if autoMigrate {
|
||||||
|
autoMigrationThreshold = section.Key("autoMigrationThreshold").MustInt(DefaultAutoMigrationThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
storageConfig[resourceName] = UnifiedStorageConfig{
|
storageConfig[resourceName] = UnifiedStorageConfig{
|
||||||
DualWriterMode: rest.DualWriterMode(dualWriterMode),
|
DualWriterMode: rest.DualWriterMode(dualWriterMode),
|
||||||
DualWriterPeriodicDataSyncJobEnabled: dualWriterPeriodicDataSyncJobEnabled,
|
DualWriterPeriodicDataSyncJobEnabled: dualWriterPeriodicDataSyncJobEnabled,
|
||||||
@@ -66,6 +84,7 @@ func (cfg *Cfg) setUnifiedStorageConfig() {
|
|||||||
DataSyncerRecordsLimit: dataSyncerRecordsLimit,
|
DataSyncerRecordsLimit: dataSyncerRecordsLimit,
|
||||||
DataSyncerInterval: dataSyncerInterval,
|
DataSyncerInterval: dataSyncerInterval,
|
||||||
EnableMigration: enableMigration,
|
EnableMigration: enableMigration,
|
||||||
|
AutoMigrationThreshold: autoMigrationThreshold,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cfg.UnifiedStorage = storageConfig
|
cfg.UnifiedStorage = storageConfig
|
||||||
@@ -73,13 +92,13 @@ func (cfg *Cfg) setUnifiedStorageConfig() {
|
|||||||
// Set indexer config for unified storage
|
// Set indexer config for unified storage
|
||||||
section := cfg.Raw.Section("unified_storage")
|
section := cfg.Raw.Section("unified_storage")
|
||||||
cfg.DisableDataMigrations = section.Key("disable_data_migrations").MustBool(false)
|
cfg.DisableDataMigrations = section.Key("disable_data_migrations").MustBool(false)
|
||||||
if !cfg.DisableDataMigrations && cfg.getUnifiedStorageType() == "unified" {
|
if !cfg.DisableDataMigrations && cfg.UnifiedStorageType() == "unified" {
|
||||||
// Helper log to find instances running migrations in the future
|
// Helper log to find instances running migrations in the future
|
||||||
cfg.Logger.Info("Unified migration configs enforced")
|
cfg.Logger.Info("Unified migration configs enforced")
|
||||||
cfg.enforceMigrationToUnifiedConfigs()
|
cfg.enforceMigrationToUnifiedConfigs()
|
||||||
} else {
|
} else {
|
||||||
// Helper log to find instances disabling migration
|
// Helper log to find instances disabling migration
|
||||||
cfg.Logger.Info("Unified migration configs enforcement disabled", "storage_type", cfg.getUnifiedStorageType(), "disable_data_migrations", cfg.DisableDataMigrations)
|
cfg.Logger.Info("Unified migration configs enforcement disabled", "storage_type", cfg.UnifiedStorageType(), "disable_data_migrations", cfg.DisableDataMigrations)
|
||||||
}
|
}
|
||||||
cfg.EnableSearch = section.Key("enable_search").MustBool(false)
|
cfg.EnableSearch = section.Key("enable_search").MustBool(false)
|
||||||
cfg.MaxPageSizeBytes = section.Key("max_page_size_bytes").MustInt(0)
|
cfg.MaxPageSizeBytes = section.Key("max_page_size_bytes").MustInt(0)
|
||||||
@@ -147,14 +166,15 @@ func (cfg *Cfg) enforceMigrationToUnifiedConfigs() {
|
|||||||
DualWriterMode: 5,
|
DualWriterMode: 5,
|
||||||
DualWriterMigrationDataSyncDisabled: true,
|
DualWriterMigrationDataSyncDisabled: true,
|
||||||
EnableMigration: true,
|
EnableMigration: true,
|
||||||
|
AutoMigrationThreshold: resourceCfg.AutoMigrationThreshold,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUnifiedStorageType returns the configured storage type without creating or mutating keys.
|
// UnifiedStorageType returns the configured storage type without creating or mutating keys.
|
||||||
// Precedence: env > ini > default ("unified").
|
// Precedence: env > ini > default ("unified").
|
||||||
// Used to decide unified storage behavior early without side effects.
|
// Used to decide unified storage behavior early without side effects.
|
||||||
func (cfg *Cfg) getUnifiedStorageType() string {
|
func (cfg *Cfg) UnifiedStorageType() string {
|
||||||
const (
|
const (
|
||||||
grafanaAPIServerSectionName = "grafana-apiserver"
|
grafanaAPIServerSectionName = "grafana-apiserver"
|
||||||
storageTypeKeyName = "storage_type"
|
storageTypeKeyName = "storage_type"
|
||||||
@@ -168,3 +188,23 @@ func (cfg *Cfg) getUnifiedStorageType() string {
|
|||||||
}
|
}
|
||||||
return defaultStorageType
|
return defaultStorageType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnifiedStorageConfig returns the UnifiedStorageConfig for a resource.
|
||||||
|
func (cfg *Cfg) UnifiedStorageConfig(resource string) UnifiedStorageConfig {
|
||||||
|
if cfg.UnifiedStorage == nil {
|
||||||
|
return UnifiedStorageConfig{}
|
||||||
|
}
|
||||||
|
return cfg.UnifiedStorage[resource]
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableMode5 enables migration and sets mode 5 for a resource.
|
||||||
|
func (cfg *Cfg) EnableMode5(resource string) {
|
||||||
|
if cfg.UnifiedStorage == nil {
|
||||||
|
cfg.UnifiedStorage = make(map[string]UnifiedStorageConfig)
|
||||||
|
}
|
||||||
|
config := cfg.UnifiedStorage[resource]
|
||||||
|
config.DualWriterMode = rest.Mode5
|
||||||
|
config.DualWriterMigrationDataSyncDisabled = true
|
||||||
|
config.EnableMigration = true
|
||||||
|
cfg.UnifiedStorage[resource] = config
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,10 +43,16 @@ func TestCfg_setUnifiedStorageConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, exists, true, migratedResource)
|
assert.Equal(t, exists, true, migratedResource)
|
||||||
|
|
||||||
|
expectedThreshold := 0
|
||||||
|
if AutoMigratedUnifiedResources[migratedResource] {
|
||||||
|
expectedThreshold = DefaultAutoMigrationThreshold
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, UnifiedStorageConfig{
|
assert.Equal(t, UnifiedStorageConfig{
|
||||||
DualWriterMode: 5,
|
DualWriterMode: 5,
|
||||||
DualWriterMigrationDataSyncDisabled: true,
|
DualWriterMigrationDataSyncDisabled: true,
|
||||||
EnableMigration: isEnabled,
|
EnableMigration: isEnabled,
|
||||||
|
AutoMigrationThreshold: expectedThreshold,
|
||||||
}, resourceCfg, migratedResource)
|
}, resourceCfg, migratedResource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,6 +77,7 @@ func TestCfg_setUnifiedStorageConfig(t *testing.T) {
|
|||||||
DualWriterPeriodicDataSyncJobEnabled: true,
|
DualWriterPeriodicDataSyncJobEnabled: true,
|
||||||
DataSyncerRecordsLimit: 1001,
|
DataSyncerRecordsLimit: 1001,
|
||||||
DataSyncerInterval: time.Minute * 10,
|
DataSyncerInterval: time.Minute * 10,
|
||||||
|
AutoMigrationThreshold: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
validateMigratedResources(false)
|
validateMigratedResources(false)
|
||||||
|
|||||||
@@ -293,15 +293,15 @@ overrides_path = overrides.yaml
|
|||||||
overrides_reload_period = 5s
|
overrides_reload_period = 5s
|
||||||
```
|
```
|
||||||
|
|
||||||
To overrides the default quota for a tenant, add the following to the overrides.yaml file:
|
To override the default quota for a tenant, add the following to the `overrides.yaml` file:
|
||||||
```yaml
|
```yaml
|
||||||
overrides:
|
overrides:
|
||||||
<NAMESPACE>:
|
<NAMESPACE>:
|
||||||
quotas:
|
quotas:
|
||||||
<GROUP>.<RESOURCE>:
|
<GROUP>/<RESOURCE>:
|
||||||
limit: 10
|
limit: 10
|
||||||
```
|
```
|
||||||
Unless otherwise set, the NAMESPACE when running locally is `default`.
|
Unless otherwise set, the `NAMESPACE` when running locally is `default`.
|
||||||
|
|
||||||
To access quotas, use the following API endpoint:
|
To access quotas, use the following API endpoint:
|
||||||
```
|
```
|
||||||
@@ -806,8 +806,10 @@ flowchart TD
|
|||||||
|
|
||||||
#### Setting Dual Writer Mode
|
#### Setting Dual Writer Mode
|
||||||
```ini
|
```ini
|
||||||
[unified_storage.{resource}.{kind}.{group}]
|
; [unified_storage.{resource}.{group}]
|
||||||
dualWriterMode = {0-5}
|
[unified_storage.dashboards.dashboard.grafana.app]
|
||||||
|
; modes {0-5}
|
||||||
|
dualWriterMode = 0
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Background Sync Configuration
|
#### Background Sync Configuration
|
||||||
@@ -1376,4 +1378,3 @@ disable_data_migrations = false
|
|||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
For detailed information about migration architecture, validators, and troubleshooting, refer to [migrations/README.md](./migrations/README.md).
|
For detailed information about migration architecture, validators, and troubleshooting, refer to [migrations/README.md](./migrations/README.md).
|
||||||
|
|
||||||
@@ -214,8 +214,18 @@ func runMigrationTestSuite(t *testing.T, testCases []resourceMigratorTestCase) {
|
|||||||
|
|
||||||
for _, state := range testStates {
|
for _, state := range testStates {
|
||||||
t.Run(state.tc.name(), func(t *testing.T) {
|
t.Run(state.tc.name(), func(t *testing.T) {
|
||||||
// Verify resources now exist in unified storage after migration
|
shouldExist := true
|
||||||
state.tc.verify(t, helper, true)
|
for _, gvr := range state.tc.resources() {
|
||||||
|
resourceKey := fmt.Sprintf("%s.%s", gvr.Resource, gvr.Group)
|
||||||
|
// Resources exist if they're either:
|
||||||
|
// 1. In MigratedUnifiedResources (enabled by default), OR
|
||||||
|
// 2. In AutoMigratedUnifiedResources (auto-migrated because count is below threshold)
|
||||||
|
if !setting.MigratedUnifiedResources[resourceKey] && !setting.AutoMigratedUnifiedResources[resourceKey] {
|
||||||
|
shouldExist = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.tc.verify(t, helper, shouldExist)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +280,7 @@ const (
|
|||||||
|
|
||||||
var migrationIDsToDefault = map[string]bool{
|
var migrationIDsToDefault = map[string]bool{
|
||||||
playlistsID: true,
|
playlistsID: true,
|
||||||
foldersAndDashboardsID: false,
|
foldersAndDashboardsID: true, // Auto-migrated when resource count is below threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyRegisteredMigrations(t *testing.T, helper *apis.K8sTestHelper, onlyDefault bool, optOut bool) {
|
func verifyRegisteredMigrations(t *testing.T, helper *apis.K8sTestHelper, onlyDefault bool, optOut bool) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user