Merge branch 'main' into leeoniya/state-timeline-value-mappings
This commit is contained in:
+8
-32
@@ -285,6 +285,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"packages/grafana-data/src/types/scopes.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"packages/grafana-data/src/types/select.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
@@ -1491,9 +1494,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
|
||||
],
|
||||
"public/app/features/alerting/unified/Templates.test.tsx:5381": [
|
||||
[0, 0, 0, "\'@grafana/ui/src/components/Monaco/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/AlertLabel.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
],
|
||||
@@ -1728,9 +1728,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPoint.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPointMenu.tsx:5381": [
|
||||
[0, 0, 0, "Do not re-export imported variable (\`app/features/explore/extensions/ToolbarExtensionPointMenu\`)", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/mute-timings/MuteTimingActionsButtons.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
|
||||
@@ -1901,9 +1898,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "11"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "12"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/receivers/TemplateEditor.tsx:5381": [
|
||||
[0, 0, 0, "\'@grafana/ui/src/components/Monaco/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/receivers/TemplateForm.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
@@ -1988,10 +1982,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/receivers/form/fields/KeyValueMapInput.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
@@ -2022,9 +2014,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.test.tsx:5381": [
|
||||
[0, 0, 0, "\'@grafana/ui/src/components/Monaco/types\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
@@ -2298,14 +2287,13 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx:5381": [
|
||||
[0, 0, 0, "\'@grafana/ui/src/components/Text/Text\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
@@ -2371,12 +2359,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/alerting/unified/components/rule-viewer/tabs/Routing.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rules/ActionButton.tsx:5381": [
|
||||
[0, 0, 0, "\'@grafana/ui/src/components/Button\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rules/ActionIcon.tsx:5381": [
|
||||
[0, 0, 0, "\'@grafana/ui/src/components/Tooltip\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rules/AlertInstanceDetails.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
],
|
||||
@@ -2814,15 +2796,9 @@ exports[`better eslint`] = {
|
||||
"public/app/features/alerting/unified/rule-list/components/RuleGroup.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx:5381": [
|
||||
[0, 0, 0, "\'@grafana/ui/src/components/Text/Text\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts:5381": [
|
||||
[0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/state/actions.ts:5381": [
|
||||
[0, 0, 0, "\'@grafana/runtime/src/utils/logging\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/types/receiver-form.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
|
||||
@@ -776,6 +776,7 @@ embed.go @grafana/grafana-as-code
|
||||
/.github/workflows/doc-validator.yml @grafana/docs-tooling
|
||||
/.github/workflows/deploy-pr-preview.yml @grafana/docs-tooling
|
||||
/.github/workflows/epic-add-to-platform-ux-parent-project.yml @meanmina
|
||||
/.github/workflows/feature-toggles-ci.yml @grafana/docs-tooling
|
||||
/.github/workflows/github-release.yml @grafana/grafana-developer-enablement-squad
|
||||
/.github/workflows/issue-opened.yml @grafana/grafana-community-support
|
||||
/.github/workflows/metrics-collector.yml @torkelo
|
||||
@@ -812,6 +813,7 @@ embed.go @grafana/grafana-as-code
|
||||
/.github/workflows/trivy-scan.yml @grafana/grafana-backend-services-squad
|
||||
/.github/workflows/changelog.yml @zserge
|
||||
/.github/workflows/actions/changelog @zserge
|
||||
/.github/workflows/frontend-unit-tests.yml @grafana/grafana-frontend-platform
|
||||
|
||||
# Generated files not requiring owner approval
|
||||
/packages/grafana-data/src/types/featureToggles.gen.ts @grafanabot
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
extends: ["schedule:monthly"],
|
||||
groupName: "Storybook updates",
|
||||
matchPackageNames: ["/^@?storybook/"],
|
||||
rangeStrategy: "bump",
|
||||
},
|
||||
{
|
||||
groupName: "React Aria",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Feature toggles CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'pkg/services/featuremgmt/toggles_gen_test.go'
|
||||
- 'pkg/services/featuremgmt/registry.go'
|
||||
- 'docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
cache: true
|
||||
|
||||
- name: Run feature toggle tests
|
||||
run: go test -v -run TestFeatureToggleFiles ./pkg/services/featuremgmt/
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Frontend tests
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release-*.*.*
|
||||
|
||||
jobs:
|
||||
test-frontend:
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
name: "Unit tests (${{ matrix.chunk }} / 8)"
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
chunk: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
- run: yarn install --immutable --check-cache
|
||||
- run: yarn run test:ci
|
||||
env:
|
||||
TEST_MAX_WORKERS: 2
|
||||
TEST_SHARD: ${{ matrix.chunk }}
|
||||
TEST_SHARD_TOTAL: 8
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -83,7 +83,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -60,8 +60,8 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -269,8 +269,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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=
|
||||
|
||||
@@ -27,7 +27,7 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -73,7 +73,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.32.1 // indirect
|
||||
|
||||
@@ -40,8 +40,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -195,8 +195,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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=
|
||||
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -74,7 +74,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.32.1 // indirect
|
||||
|
||||
@@ -40,8 +40,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -195,8 +195,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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=
|
||||
|
||||
@@ -65,7 +65,7 @@ require (
|
||||
github.com/golang/mock v1.7.0-rc.1 // @grafana/alerting-backend
|
||||
github.com/golang/protobuf v1.5.4 // @grafana/grafana-backend-group
|
||||
github.com/golang/snappy v0.0.4 // @grafana/alerting-backend
|
||||
github.com/google/go-cmp v0.6.0 // @grafana/grafana-backend-group
|
||||
github.com/google/go-cmp v0.7.0 // @grafana/grafana-backend-group
|
||||
github.com/google/go-querystring v1.1.0 // indirect; @grafana/oss-big-tent
|
||||
github.com/google/uuid v1.6.0 // @grafana/grafana-backend-group
|
||||
github.com/google/wire v0.6.0 // @grafana/grafana-backend-group
|
||||
@@ -88,7 +88,7 @@ require (
|
||||
github.com/grafana/grafana-cloud-migration-snapshot v1.6.0 // @grafana/grafana-operator-experience-squad
|
||||
github.com/grafana/grafana-google-sdk-go v0.2.1 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs
|
||||
github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // @grafana/observability-traces-and-profiling
|
||||
@@ -180,7 +180,7 @@ require (
|
||||
gonum.org/v1/gonum v0.15.1 // @grafana/oss-big-tent
|
||||
google.golang.org/api v0.216.0 // @grafana/grafana-backend-group
|
||||
google.golang.org/grpc v1.70.0 // @grafana/plugins-platform-backend
|
||||
google.golang.org/protobuf v1.36.4 // @grafana/plugins-platform-backend
|
||||
google.golang.org/protobuf v1.36.5 // @grafana/plugins-platform-backend
|
||||
gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-backend
|
||||
gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group
|
||||
gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend
|
||||
@@ -331,7 +331,7 @@ require (
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/edsrzf/mmap-go v1.2.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emicklei/proto v1.13.2 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
|
||||
@@ -1094,8 +1094,8 @@ github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8E
|
||||
github.com/efficientgo/core v1.0.0-rc.3 h1:X6CdgycYWDcbYiJr1H1+lQGzx13o7bq3EUkbB9DsSPc=
|
||||
github.com/efficientgo/core v1.0.0-rc.3/go.mod h1:FfGdkzWarkuzOlY04VY+bGfb1lWrjaL6x/GLcQ4vJps=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v1.7.0 h1:EXv2nV4EjM60ZtsEVLYJG4oBXhDGutMKperpHsZ/v+0=
|
||||
github.com/elazarl/goproxy v1.7.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/elazarl/goproxy v1.7.1 h1:1P7LPSxbqtNxusFnXclj6O56pjfq1xOQZ6a0mwwKUlY=
|
||||
github.com/elazarl/goproxy v1.7.1/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
@@ -1422,8 +1422,9 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
@@ -1558,8 +1559,8 @@ github.com/grafana/grafana-google-sdk-go v0.2.1 h1:XeFdKnkXBjOJjXc1gf4iMx4h5aCHT
|
||||
github.com/grafana/grafana-google-sdk-go v0.2.1/go.mod h1:RiITSHwBhqVTTd3se3HQq5Ncs/wzzhTB9OK5N0J0PEU=
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs=
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0 h1:YP+iEpXH3HRX9Xo4NHjsrJhN2W7uVTtkLNzMHYbmiLI=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0/go.mod h1:bxkXrBQ4QSmOncsWdIOcpgP+M6wajQNMAPXlbWrqAWY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0 h1:4AcuLEE0UeFG0Eo3P8r1FDS89hgZZ73K550DRz1fgjQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q=
|
||||
github.com/grafana/grafana/apps/advisor v0.0.0-20250220163425-b4c4b9abbdc8 h1:mG/6nDlEBVxWlo2GQJVASzucw3ByPIBsec06XcPrjgQ=
|
||||
github.com/grafana/grafana/apps/advisor v0.0.0-20250220163425-b4c4b9abbdc8/go.mod h1:9I1dKV3Dqr0NPR9Af0WJGxOytp5/6W3JLiNChOz8r+c=
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250220163425-b4c4b9abbdc8 h1:w42GlvkmHG4nM/p1kb2nKmROVP+AHtL3qWEYMhnhCVM=
|
||||
@@ -3357,8 +3358,8 @@ google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
|
||||
+4
-16
@@ -142,8 +142,6 @@ cloud.google.com/go/memcache v1.11.1 h1:2FGuyd3WY7buNDAkMBdmeIOheNWA3gwaXrttLrEd
|
||||
cloud.google.com/go/memcache v1.11.1/go.mod h1:3zF+dEqmEmElHuO4NtHiShekQY5okQtssjPBv7jpmZ8=
|
||||
cloud.google.com/go/metastore v1.14.1 h1:kGx+IUSSYCVn8LisCT4fpxCC9rauEVonzi7RlygdqWY=
|
||||
cloud.google.com/go/metastore v1.14.1/go.mod h1:WDvsAcbQLl9M4xL+eIpbKogH7aEaPWMhO9aRBcFOnJE=
|
||||
cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc=
|
||||
cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c=
|
||||
cloud.google.com/go/networkconnectivity v1.15.1 h1:EizN+cFGHzRAyiFTK8jT1PqTo+cSnbc2IGh6OmllS7Y=
|
||||
cloud.google.com/go/networkconnectivity v1.15.1/go.mod h1:tYAcT4Ahvq+BiePXL/slYipf/8FF0oNJw3MqFhBnSPI=
|
||||
cloud.google.com/go/networkmanagement v1.14.1 h1:0x3hVI6xbp3N/choffKPHMSxbzaPdHSD92cBElebXEk=
|
||||
@@ -204,8 +202,6 @@ cloud.google.com/go/servicemanagement v1.8.0 h1:fopAQI/IAzlxnVeiKn/8WiV6zKndjFkv
|
||||
cloud.google.com/go/serviceusage v1.6.0 h1:rXyq+0+RSIm3HFypctp7WoXxIA563rn206CfMWdqXX4=
|
||||
cloud.google.com/go/shell v1.8.1 h1:etoJal+LB7Pn8+5vE2aAh6QcFbBmerIOh5MxNDoXykw=
|
||||
cloud.google.com/go/shell v1.8.1/go.mod h1:jaU7OHeldDhTwgs3+clM0KYEDYnBAPevUI6wNLf7ycE=
|
||||
cloud.google.com/go/spanner v1.70.0 h1:nj6p/GJTgMDiSQ1gQ034ItsKuJgHiMOjtOlONOg8PSo=
|
||||
cloud.google.com/go/spanner v1.70.0/go.mod h1:X5T0XftydYp0K1adeJQDJtdWpbrOeJ7wHecM4tK6FiE=
|
||||
cloud.google.com/go/speech v1.25.1 h1:iGZJS3wrdkje/Vqiacx1+r+zVwUZoXVMdklYIVsvfNw=
|
||||
cloud.google.com/go/speech v1.25.1/go.mod h1:WgQghvghkZ1htG6BhYn98mP7Tg0mti8dBFDLMVXH/vM=
|
||||
cloud.google.com/go/storagetransfer v1.11.1 h1:Hd7H1zXGQGEWyWXxWVXDMuNCGasNQim1y9CIaMZIBX8=
|
||||
@@ -280,10 +276,6 @@ github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW5
|
||||
github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbOrtjYTXPtWNSyOFlcxkBU=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE=
|
||||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 h1:oVLqHXhnYtUwM89y9T1fXGaK9wTkXHgNp8/ZNMQzUxE=
|
||||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
|
||||
github.com/IBM/go-sdk-core/v5 v5.17.4 h1:VGb9+mRrnS2HpHZFM5hy4J6ppIWnwNrw0G+tLSgcJLc=
|
||||
github.com/IBM/go-sdk-core/v5 v5.17.4/go.mod h1:KsAAI7eStAWwQa4F96MLy+whYSh39JzNjklZRbN/8ns=
|
||||
github.com/IBM/ibm-cos-sdk-go v1.11.0 h1:Jp55NLN3OvBwucMGpP5wNybyjncsmTZ9+GPHai/1cE8=
|
||||
@@ -387,7 +379,6 @@ github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9Mwe
|
||||
github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
|
||||
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
@@ -510,6 +501,7 @@ github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJ
|
||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
||||
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
||||
github.com/elazarl/goproxy v1.3.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/elazarl/goproxy v1.7.1 h1:1P7LPSxbqtNxusFnXclj6O56pjfq1xOQZ6a0mwwKUlY=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
@@ -593,6 +585,7 @@ github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs0
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg=
|
||||
github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0=
|
||||
@@ -907,7 +900,6 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJ
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stoewer/parquet-cli v0.0.7 h1:rhdZODIbyMS3twr4OM3am8BPPT5pbfMcHLH93whDM5o=
|
||||
github.com/stoewer/parquet-cli v0.0.7/go.mod h1:bskxHdj8q3H1EmfuCqjViFoeO3NEvs5lzZAQvI8Nfjk=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/substrait-io/substrait v0.57.1 h1:GW8nnYfSowMseHR8Os82/X6lNtQGIK7p4p+lr6r+auw=
|
||||
github.com/substrait-io/substrait v0.57.1/go.mod h1:q9s+tjo+gK0lsA+SqYB0lhojNuxvdPdfYlGUP0hjbrA=
|
||||
github.com/substrait-io/substrait-go v1.2.0 h1:3ZNRkc8FYD7ifCagKEOZQtUcgMceMQfwo2N1NGaK4Q4=
|
||||
@@ -1055,8 +1047,6 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.53.0 h1:BdkKDtcrHThgjcEia1737O
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.53.0/go.mod h1:ZkhVxcJgeXlL/lVyT/vxNHVFiSG5qOaDwYaSgD8IfZo=
|
||||
go.opentelemetry.io/contrib/config v0.7.0 h1:b1rK5tGTuhhPirJiMxOcyQfZs76j2VapY6ODn3b2Dbs=
|
||||
go.opentelemetry.io/contrib/config v0.7.0/go.mod h1:8tdiFd8N5etOi3XzBmAoMxplEzI3TcL8dU5rM5/xcOQ=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.53.0 h1:13K+tY7E8GJInkrvRiPAhC0gi/7vKjzDNhtmCf+QXG8=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.53.0/go.mod h1:lyQF6xQ4iDnMg4sccNdFs1zf62xd79YI8vZqKjOTwMs=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
|
||||
@@ -1137,12 +1127,9 @@ golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1200,6 +1187,7 @@ google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
|
||||
+3
-3
@@ -31,7 +31,7 @@
|
||||
"lint": "yarn run lint:ts && yarn run lint:sass",
|
||||
"lint:ts": "eslint ./ ./public/app/extensions/ --cache --no-error-on-unmatched-pattern",
|
||||
"lint:sass": "yarn stylelint '{public/sass,packages}/**/*.scss' --cache",
|
||||
"test:ci": "mkdir -p reports/junit && JEST_JUNIT_OUTPUT_DIR=reports/junit jest --ci --reporters=default --reporters=jest-junit -w ${TEST_MAX_WORKERS:-100%}",
|
||||
"test:ci": "mkdir -p reports/junit && JEST_JUNIT_OUTPUT_DIR=reports/junit jest --ci --reporters=default --reporters=jest-junit -w ${TEST_MAX_WORKERS:-100%} --shard=${TEST_SHARD:-1}/${TEST_SHARD_TOTAL:-1}",
|
||||
"lint:fix": "yarn lint:ts --fix",
|
||||
"packages:build": "nx run-many -t build --projects='tag:scope:package'",
|
||||
"packages:clean": "rimraf ./npm-artifacts && nx run-many -t clean --projects='tag:scope:package' --maxParallel=100",
|
||||
@@ -276,8 +276,8 @@
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/saga-icons": "workspace:*",
|
||||
"@grafana/scenes": "6.0.2",
|
||||
"@grafana/scenes-react": "6.0.2",
|
||||
"@grafana/scenes": "6.1.4",
|
||||
"@grafana/scenes-react": "6.1.4",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -576,6 +576,8 @@ export {
|
||||
type ScopeNodeSpec,
|
||||
type ScopeNode,
|
||||
scopeFilterOperatorMap,
|
||||
reverseScopeFilterOperatorMap,
|
||||
isEqualityOrMultiOperator,
|
||||
} from './types/scopes';
|
||||
export {
|
||||
PluginState,
|
||||
|
||||
@@ -18,6 +18,12 @@ export interface ScopeDashboardBinding {
|
||||
}
|
||||
|
||||
export type ScopeFilterOperator = 'equals' | 'not-equals' | 'regex-match' | 'regex-not-match' | 'one-of' | 'not-one-of';
|
||||
export type EqualityOrMultiOperator = Extract<ScopeFilterOperator, 'equals' | 'not-equals' | 'one-of' | 'not-one-of'>;
|
||||
|
||||
export function isEqualityOrMultiOperator(value: string): value is EqualityOrMultiOperator {
|
||||
const operators = new Set(['equals', 'not-equals', 'one-of', 'not-one-of']);
|
||||
return operators.has(value);
|
||||
}
|
||||
|
||||
export const scopeFilterOperatorMap: Record<string, ScopeFilterOperator> = {
|
||||
'=': 'equals',
|
||||
@@ -28,6 +34,11 @@ export const scopeFilterOperatorMap: Record<string, ScopeFilterOperator> = {
|
||||
'!=|': 'not-one-of',
|
||||
};
|
||||
|
||||
export const reverseScopeFilterOperatorMap: Record<ScopeFilterOperator, string> = Object.fromEntries(
|
||||
Object.entries(scopeFilterOperatorMap).map(([symbol, operator]) => [operator, symbol])
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
) as Record<ScopeFilterOperator, string>;
|
||||
|
||||
export interface ScopeSpecFilter {
|
||||
key: string;
|
||||
value: string;
|
||||
|
||||
@@ -574,6 +574,7 @@ ResponsiveGridLayoutItemKind: {
|
||||
|
||||
ResponsiveGridLayoutItemSpec: {
|
||||
element: ElementReference
|
||||
repeat?: ResponsiveGridRepeatOptions
|
||||
}
|
||||
|
||||
TabsLayoutKind: {
|
||||
|
||||
@@ -867,6 +867,7 @@ export const defaultResponsiveGridLayoutItemKind = (): ResponsiveGridLayoutItemK
|
||||
|
||||
export interface ResponsiveGridLayoutItemSpec {
|
||||
element: ElementReference;
|
||||
repeat?: ResponsiveGridRepeatOptions;
|
||||
}
|
||||
|
||||
export const defaultResponsiveGridLayoutItemSpec = (): ResponsiveGridLayoutItemSpec => ({
|
||||
|
||||
@@ -6,7 +6,7 @@ toolchain go1.23.6
|
||||
|
||||
require (
|
||||
github.com/emicklei/go-restful/v3 v3.11.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808213237-f4d2e064f435
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435
|
||||
github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/elazarl/goproxy v1.7.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.1 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
github.com/google/cel-go v0.22.1 // indirect
|
||||
github.com/google/flatbuffers v24.3.25+incompatible // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -150,7 +150,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
@@ -49,8 +49,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elazarl/goproxy v1.7.0 h1:EXv2nV4EjM60ZtsEVLYJG4oBXhDGutMKperpHsZ/v+0=
|
||||
github.com/elazarl/goproxy v1.7.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/elazarl/goproxy v1.7.1 h1:1P7LPSxbqtNxusFnXclj6O56pjfq1xOQZ6a0mwwKUlY=
|
||||
github.com/elazarl/goproxy v1.7.1/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -118,8 +118,9 @@ github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvR
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -134,8 +135,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0 h1:YP+iEpXH3HRX9Xo4NHjsrJhN2W7uVTtkLNzMHYbmiLI=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0/go.mod h1:bxkXrBQ4QSmOncsWdIOcpgP+M6wajQNMAPXlbWrqAWY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0 h1:4AcuLEE0UeFG0Eo3P8r1FDS89hgZZ73K550DRz1fgjQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808213237-f4d2e064f435 h1:lmw60EW7JWlAEvgggktOyVkH4hF1m/+LSF/Ap0NCyi8=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808213237-f4d2e064f435/go.mod h1:ORVFiW/KNRY52lNjkGwnFWCxNVfE97bJG2jr2fetq0I=
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435 h1:SNEeqY22DrGr5E9kGF1mKSqlOom14W9+b1u4XEGJowA=
|
||||
@@ -495,8 +496,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
@@ -23,6 +23,7 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
@@ -43,7 +44,7 @@ require (
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
|
||||
@@ -25,8 +25,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -151,8 +151,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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=
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.23.4
|
||||
toolchain go1.23.6
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/grafana/authlib/types v0.0.0-20250224151205-5ef97131cc82
|
||||
github.com/grafana/grafana-app-sdk/logging v0.30.0
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1
|
||||
@@ -93,7 +93,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -68,8 +68,8 @@ github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvR
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -312,8 +312,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
@@ -26,8 +26,18 @@ var (
|
||||
_ rest.SingularNameProvider = (DualWriter)(nil)
|
||||
)
|
||||
|
||||
type dualWriteContextKey struct{}
|
||||
|
||||
func IsDualWriteUpdate(ctx context.Context) bool {
|
||||
return ctx.Value(dualWriteContextKey{}) == true
|
||||
}
|
||||
|
||||
func WithDualWriteUpdate(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, dualWriteContextKey{}, true)
|
||||
}
|
||||
|
||||
// Function that will create a dual writer
|
||||
type DualWriteBuilder func(gr schema.GroupResource, legacy LegacyStorage, storage Storage) (Storage, error)
|
||||
type DualWriteBuilder func(gr schema.GroupResource, legacy Storage, unified Storage) (Storage, error)
|
||||
|
||||
// Storage is a storage implementation that satisfies the same interfaces as genericregistry.Store.
|
||||
type Storage interface {
|
||||
@@ -36,26 +46,12 @@ type Storage interface {
|
||||
rest.TableConvertor
|
||||
rest.SingularNameProvider
|
||||
rest.Getter
|
||||
// TODO: when watch is implemented, we can replace all the below with rest.StandardStorage
|
||||
rest.Lister
|
||||
rest.CreaterUpdater
|
||||
rest.GracefulDeleter
|
||||
rest.CollectionDeleter
|
||||
}
|
||||
|
||||
// LegacyStorage is a storage implementation that writes to the Grafana SQL database.
|
||||
type LegacyStorage interface {
|
||||
rest.Storage
|
||||
rest.Scoper
|
||||
rest.SingularNameProvider
|
||||
rest.CreaterUpdater
|
||||
rest.Lister
|
||||
rest.GracefulDeleter
|
||||
rest.CollectionDeleter
|
||||
rest.TableConvertor
|
||||
rest.Getter
|
||||
}
|
||||
|
||||
// DualWriter is a storage implementation that writes first to LegacyStorage and then to Storage.
|
||||
// If writing to LegacyStorage fails, the write to Storage is skipped and the error is returned.
|
||||
// Storage is used for all read operations. This is useful as a migration step from SQL based
|
||||
@@ -79,7 +75,6 @@ type LegacyStorage interface {
|
||||
|
||||
type DualWriter interface {
|
||||
Storage
|
||||
LegacyStorage
|
||||
Mode() DualWriterMode
|
||||
}
|
||||
|
||||
@@ -110,8 +105,8 @@ const (
|
||||
// NewDualWriter returns a new DualWriter.
|
||||
func NewDualWriter(
|
||||
mode DualWriterMode,
|
||||
legacy LegacyStorage,
|
||||
storage Storage,
|
||||
legacy Storage,
|
||||
unified Storage,
|
||||
reg prometheus.Registerer,
|
||||
resource string,
|
||||
) Storage {
|
||||
@@ -122,17 +117,17 @@ func NewDualWriter(
|
||||
return legacy
|
||||
case Mode1:
|
||||
// read and write only from legacy storage
|
||||
return newDualWriterMode1(legacy, storage, metrics, resource)
|
||||
return newDualWriterMode1(legacy, unified, metrics, resource)
|
||||
case Mode2:
|
||||
// write to both, read from storage but use legacy as backup
|
||||
return newDualWriterMode2(legacy, storage, metrics, resource)
|
||||
return newDualWriterMode2(legacy, unified, metrics, resource)
|
||||
case Mode3:
|
||||
// write to both, read from storage only
|
||||
return newDualWriterMode3(legacy, storage, metrics, resource)
|
||||
return newDualWriterMode3(legacy, unified, metrics, resource)
|
||||
case Mode4, Mode5:
|
||||
return storage
|
||||
return unified
|
||||
default:
|
||||
return newDualWriterMode1(legacy, storage, metrics, resource)
|
||||
return newDualWriterMode1(legacy, unified, metrics, resource)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
type DualWriterMode1 struct {
|
||||
Legacy LegacyStorage
|
||||
Legacy Storage
|
||||
Storage Storage
|
||||
*dualWriterMetrics
|
||||
resource string
|
||||
@@ -26,7 +26,7 @@ const mode1Str = "1"
|
||||
|
||||
// NewDualWriterMode1 returns a new DualWriter in mode 1.
|
||||
// Mode 1 represents writing to and reading from LegacyStorage.
|
||||
func newDualWriterMode1(legacy LegacyStorage, storage Storage, dwm *dualWriterMetrics, resource string) *DualWriterMode1 {
|
||||
func newDualWriterMode1(legacy Storage, storage Storage, dwm *dualWriterMetrics, resource string) *DualWriterMode1 {
|
||||
return &DualWriterMode1{
|
||||
Legacy: legacy,
|
||||
Storage: storage,
|
||||
|
||||
@@ -60,10 +60,10 @@ func TestMode1_Create(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -122,10 +122,10 @@ func TestMode1_CreateOnUnifiedStorage(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -190,10 +190,10 @@ func TestMode1_Get(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -251,10 +251,10 @@ func TestMode1_GetFromUnifiedStorage(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -308,10 +308,10 @@ func TestMode1_List(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -365,10 +365,10 @@ func TestMode1_ListFromUnifiedStorage(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -434,10 +434,10 @@ func TestMode1_Delete(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -494,10 +494,10 @@ func TestMode1_DeleteFromUnifiedStorage(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -565,10 +565,10 @@ func TestMode1_DeleteCollection(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -626,10 +626,10 @@ func TestMode1_DeleteCollectionFromUnifiedStorage(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -695,10 +695,10 @@ func TestMode1_Update(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -761,10 +761,10 @@ func TestMode1_UpdateOnUnifiedStorage(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
|
||||
@@ -16,15 +16,9 @@ import (
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
||||
type dualWriteContextKey struct{}
|
||||
|
||||
func IsDualWriteUpdate(ctx context.Context) bool {
|
||||
return ctx.Value(dualWriteContextKey{}) == true
|
||||
}
|
||||
|
||||
type DualWriterMode2 struct {
|
||||
Storage Storage
|
||||
Legacy LegacyStorage
|
||||
Legacy Storage
|
||||
*dualWriterMetrics
|
||||
resource string
|
||||
Log klog.Logger
|
||||
@@ -35,7 +29,7 @@ const mode2Str = "2"
|
||||
// newDualWriterMode2 returns a new DualWriter in mode 2.
|
||||
// Mode 2 represents writing to LegacyStorage first, then to Storage.
|
||||
// When reading, values from LegacyStorage will be returned.
|
||||
func newDualWriterMode2(legacy LegacyStorage, storage Storage, dwm *dualWriterMetrics, resource string) *DualWriterMode2 {
|
||||
func newDualWriterMode2(legacy Storage, storage Storage, dwm *dualWriterMetrics, resource string) *DualWriterMode2 {
|
||||
return &DualWriterMode2{
|
||||
Legacy: legacy,
|
||||
Storage: storage,
|
||||
|
||||
@@ -54,10 +54,10 @@ func TestMode2_Create(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -138,10 +138,10 @@ func TestMode2_Get(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -212,10 +212,10 @@ func TestMode2_List(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -313,10 +313,10 @@ func TestMode2_Delete(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -382,10 +382,10 @@ func TestMode2_DeleteCollection(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -451,10 +451,10 @@ func TestMode2_Update(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
type DualWriterMode3 struct {
|
||||
Legacy LegacyStorage
|
||||
Legacy Storage
|
||||
Storage Storage
|
||||
watchImp rest.Watcher // watch is only available in mode 3 and 4
|
||||
*dualWriterMetrics
|
||||
@@ -27,7 +27,7 @@ type DualWriterMode3 struct {
|
||||
|
||||
// newDualWriterMode3 returns a new DualWriter in mode 3.
|
||||
// Mode 3 represents writing to LegacyStorage and Storage and reading from Storage.
|
||||
func newDualWriterMode3(legacy LegacyStorage, storage Storage, dwm *dualWriterMetrics, resource string) *DualWriterMode3 {
|
||||
func newDualWriterMode3(legacy Storage, storage Storage, dwm *dualWriterMetrics, resource string) *DualWriterMode3 {
|
||||
return &DualWriterMode3{
|
||||
Legacy: legacy,
|
||||
Storage: storage,
|
||||
|
||||
@@ -61,10 +61,10 @@ func TestMode3_Create(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -128,10 +128,10 @@ func TestMode3_Get(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -187,10 +187,10 @@ func TestMode1_GetFromLegacyStorage(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -234,10 +234,10 @@ func TestMode3_List(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupStorageFn != nil {
|
||||
@@ -311,10 +311,10 @@ func TestMode3_Delete(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -380,10 +380,10 @@ func TestMode3_DeleteCollection(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -449,10 +449,10 @@ func TestMode3_Update(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
|
||||
@@ -33,7 +33,7 @@ type SyncerConfig struct {
|
||||
RequestInfo *request.RequestInfo
|
||||
|
||||
Mode DualWriterMode
|
||||
LegacyStorage LegacyStorage
|
||||
LegacyStorage Storage
|
||||
Storage Storage
|
||||
ServerLockService ServerLockService
|
||||
|
||||
|
||||
@@ -181,12 +181,12 @@ func TestLegacyToUnifiedStorage_DataSyncer(t *testing.T) {
|
||||
// mode 1
|
||||
for _, tt := range tests {
|
||||
t.Run("Mode-1-"+tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
lm := &mock.Mock{}
|
||||
um := &mock.Mock{}
|
||||
|
||||
ls := legacyStoreMock{lm, l}
|
||||
ls := storageMock{lm, l}
|
||||
us := storageMock{um, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -221,12 +221,12 @@ func TestLegacyToUnifiedStorage_DataSyncer(t *testing.T) {
|
||||
// mode 2
|
||||
for _, tt := range tests {
|
||||
t.Run("Mode-2-"+tt.name, func(t *testing.T) {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
lm := &mock.Mock{}
|
||||
um := &mock.Mock{}
|
||||
|
||||
ls := legacyStoreMock{lm, l}
|
||||
ls := storageMock{lm, l}
|
||||
us := storageMock{um, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestSetDualWritingMode(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
l := (LegacyStorage)(nil)
|
||||
l := (Storage)(nil)
|
||||
s := (Storage)(nil)
|
||||
|
||||
sm := &mock.Mock{}
|
||||
@@ -75,7 +75,7 @@ func TestSetDualWritingMode(t *testing.T) {
|
||||
|
||||
lm := &mock.Mock{}
|
||||
lm.On("List", mock.Anything, mock.Anything).Return(exampleList, nil)
|
||||
ls := legacyStoreMock{lm, l}
|
||||
ls := storageMock{lm, l}
|
||||
|
||||
serverLockSvc := &fakeServerLock{
|
||||
err: tt.serverLockError,
|
||||
|
||||
@@ -11,102 +11,11 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
)
|
||||
|
||||
type legacyStoreMock struct {
|
||||
*mock.Mock
|
||||
LegacyStorage
|
||||
}
|
||||
|
||||
type storageMock struct {
|
||||
*mock.Mock
|
||||
Storage
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
|
||||
args := m.Called(ctx, name, options)
|
||||
if err := args.Get(1); err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Error(1)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
|
||||
args := m.Called(ctx, obj, createValidation, options)
|
||||
if err := args.Get(1); err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Error(1)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
|
||||
args := m.Called(ctx, options)
|
||||
if err := args.Get(1); err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Error(1)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) NewList() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, false, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
args := m.Called(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options)
|
||||
if err := args.Get(2); err != nil {
|
||||
return nil, false, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, false, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
|
||||
args := m.Called(ctx, name, deleteValidation, options)
|
||||
if err := args.Get(2); err != nil {
|
||||
return nil, false, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
args := m.Called(ctx, deleteValidation, options, listOptions)
|
||||
if err := args.Get(1); err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Error(1)
|
||||
}
|
||||
|
||||
// Unified Store
|
||||
func (m storageMock) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
select {
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ require (
|
||||
github.com/docker/docker v27.4.1+incompatible // @grafana/grafana-developer-enablement-squad
|
||||
github.com/drone/drone-cli v1.8.0 // @grafana/grafana-developer-enablement-squad
|
||||
github.com/gogo/protobuf v1.3.2 // indirect; @grafana/alerting-backend
|
||||
github.com/google/go-cmp v0.6.0 // @grafana/grafana-backend-group
|
||||
github.com/google/go-cmp v0.7.0 // @grafana/grafana-backend-group
|
||||
github.com/google/go-github/v69 v69.2.0 // @grafana/grafana-developer-enablement-squad
|
||||
github.com/google/uuid v1.6.0 // indirect; @grafana/grafana-backend-group
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect; @grafana/grafana-backend-group
|
||||
@@ -35,7 +35,7 @@ require (
|
||||
golang.org/x/time v0.9.0 // indirect; @grafana/grafana-backend-group
|
||||
google.golang.org/api v0.216.0 // @grafana/grafana-backend-group
|
||||
google.golang.org/grpc v1.70.0 // indirect; @grafana/plugins-platform-backend
|
||||
google.golang.org/protobuf v1.36.4 // indirect; @grafana/plugins-platform-backend
|
||||
google.golang.org/protobuf v1.36.5 // indirect; @grafana/plugins-platform-backend
|
||||
gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend
|
||||
)
|
||||
|
||||
|
||||
+4
-4
@@ -126,8 +126,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE=
|
||||
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
@@ -362,8 +362,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/build/wire
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/subcommands v1.2.0
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
|
||||
golang.org/x/tools v0.29.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/golang/glog v1.2.4 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
||||
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
|
||||
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/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/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build enterprise
|
||||
// +build enterprise
|
||||
//go:build enterprise || pro
|
||||
// +build enterprise pro
|
||||
|
||||
package extensions
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ require (
|
||||
github.com/getkin/kin-openapi v0.129.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
|
||||
@@ -24,8 +24,8 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/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/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
|
||||
|
||||
+4
-4
@@ -6,7 +6,7 @@ toolchain go1.23.6
|
||||
|
||||
require (
|
||||
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/prometheus/common v0.62.0
|
||||
@@ -33,7 +33,7 @@ require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dennwc/varint v1.0.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
@@ -48,7 +48,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/flatbuffers v24.3.25+incompatible // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
@@ -121,7 +121,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
+8
-7
@@ -55,8 +55,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE=
|
||||
github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=
|
||||
github.com/elazarl/goproxy v1.7.0 h1:EXv2nV4EjM60ZtsEVLYJG4oBXhDGutMKperpHsZ/v+0=
|
||||
github.com/elazarl/goproxy v1.7.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/elazarl/goproxy v1.7.1 h1:1P7LPSxbqtNxusFnXclj6O56pjfq1xOQZ6a0mwwKUlY=
|
||||
github.com/elazarl/goproxy v1.7.1/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
@@ -100,8 +100,9 @@ github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZat
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -120,8 +121,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 h1:IR+UNYHqaU31t8/TArJk8K/GlDwOyxMpGNkWCXeZ28g=
|
||||
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040/go.mod h1:SPLNCARd4xdjCkue0O6hvuoveuS1dGJjDnfxYe405YQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0 h1:YP+iEpXH3HRX9Xo4NHjsrJhN2W7uVTtkLNzMHYbmiLI=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0/go.mod h1:bxkXrBQ4QSmOncsWdIOcpgP+M6wajQNMAPXlbWrqAWY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0 h1:4AcuLEE0UeFG0Eo3P8r1FDS89hgZZ73K550DRz1fgjQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q=
|
||||
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
|
||||
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
|
||||
@@ -394,8 +395,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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=
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
model "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/receiver/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
grafanaRest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
alertingac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
_ grafanaRest.LegacyStorage = (*legacyStorage)(nil)
|
||||
_ grafanarest.Storage = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
type ReceiverService interface {
|
||||
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
model "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/routingtree/v0alpha1"
|
||||
grafanaRest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
var (
|
||||
_ grafanaRest.LegacyStorage = (*legacyStorage)(nil)
|
||||
_ grafanarest.Storage = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
type RouteService interface {
|
||||
|
||||
@@ -4,13 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/alerting/templates"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
"github.com/grafana/alerting/templates"
|
||||
|
||||
model "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/templategroup/v0alpha1"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
@@ -19,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
_ grafanarest.LegacyStorage = (*legacyStorage)(nil)
|
||||
_ grafanarest.Storage = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
type TemplateService interface {
|
||||
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
model "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/timeinterval/v0alpha1"
|
||||
grafanaRest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
var (
|
||||
_ grafanaRest.LegacyStorage = (*legacyStorage)(nil)
|
||||
_ grafanarest.Storage = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
type TimeIntervalService interface {
|
||||
|
||||
@@ -28,7 +28,7 @@ type DashboardStorage struct {
|
||||
Features featuremgmt.FeatureToggles
|
||||
}
|
||||
|
||||
func (s *DashboardStorage) NewStore(scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter, reg prometheus.Registerer) (grafanarest.LegacyStorage, error) {
|
||||
func (s *DashboardStorage) NewStore(scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter, reg prometheus.Registerer) (grafanarest.Storage, error) {
|
||||
server, err := resource.NewResourceServer(resource.ResourceServerOptions{
|
||||
Backend: s.Access,
|
||||
Reg: reg,
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
dashboardinternal "github.com/grafana/grafana/pkg/apis/dashboard"
|
||||
@@ -42,7 +44,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/apistore"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -247,7 +248,7 @@ func (b *DashboardsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver
|
||||
|
||||
func (b *DashboardsAPIBuilder) storageForVersion(
|
||||
opts builder.APIGroupOptions,
|
||||
legacyStore grafanarest.LegacyStorage,
|
||||
legacyStore grafanarest.Storage,
|
||||
largeObjects apistore.LargeObjectSupport,
|
||||
newDTOFunc func() runtime.Object,
|
||||
) (map[string]rest.Storage, error) {
|
||||
|
||||
@@ -116,7 +116,7 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO
|
||||
}
|
||||
list.Items = append(list.Items, *r)
|
||||
}
|
||||
if len(list.Items) >= int(paging.limit) {
|
||||
if int64(len(list.Items)) >= paging.limit {
|
||||
list.Continue = paging.GetNextPageToken()
|
||||
}
|
||||
return list, nil
|
||||
|
||||
@@ -48,7 +48,7 @@ func RegisterApp(
|
||||
return provider
|
||||
}
|
||||
|
||||
func (p *PlaylistAppProvider) legacyStorageGetter(requested schema.GroupVersionResource) grafanarest.LegacyStorage {
|
||||
func (p *PlaylistAppProvider) legacyStorageGetter(requested schema.GroupVersionResource) grafanarest.Storage {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: playlistv0alpha1.PlaylistKind().Group(),
|
||||
Version: playlistv0alpha1.PlaylistKind().Version(),
|
||||
|
||||
@@ -6,5 +6,6 @@ require go.opentelemetry.io/otel v1.34.0
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
)
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
|
||||
@@ -286,7 +286,7 @@ func InstallAPIs(
|
||||
|
||||
// nolint:staticcheck
|
||||
if storageOpts.StorageType != options.StorageTypeLegacy {
|
||||
dualWrite = func(gr schema.GroupResource, legacy grafanarest.LegacyStorage, storage grafanarest.Storage) (grafanarest.Storage, error) {
|
||||
dualWrite = func(gr schema.GroupResource, legacy grafanarest.Storage, storage grafanarest.Storage) (grafanarest.Storage, error) {
|
||||
// Dashboards + Folders may be managed (depends on feature toggles and database state)
|
||||
if dualWriteService != nil && dualWriteService.ShouldManage(gr) {
|
||||
return dualWriteService.NewStorage(gr, legacy, storage) // eventually this can replace this whole function
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
var _ AppBuilder = (*appBuilder)(nil)
|
||||
|
||||
type LegacyStorageGetter func(schema.GroupVersionResource) grafanarest.LegacyStorage
|
||||
type LegacyStorageGetter func(schema.GroupVersionResource) grafanarest.Storage
|
||||
|
||||
type AppBuilderConfig struct {
|
||||
Authorizer authorizer.Authorizer
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
func ProvideService(cfg *setting.Cfg, authenticator authn.Authenticator, features featuremgmt.FeatureToggles,
|
||||
) *ContextHandler {
|
||||
return &ContextHandler{
|
||||
Cfg: cfg,
|
||||
cfg: cfg,
|
||||
authenticator: authenticator,
|
||||
features: features,
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func ProvideService(cfg *setting.Cfg, authenticator authn.Authenticator, feature
|
||||
|
||||
// ContextHandler is a middleware.
|
||||
type ContextHandler struct {
|
||||
Cfg *setting.Cfg
|
||||
cfg *setting.Cfg
|
||||
authenticator authn.Authenticator
|
||||
features featuremgmt.FeatureToggles
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
|
||||
// inject ReqContext in the context
|
||||
ctx = context.WithValue(ctx, reqContextKey{}, reqContext)
|
||||
// store list of possible auth header in context
|
||||
ctx = WithAuthHTTPHeaders(ctx, h.Cfg)
|
||||
ctx = WithAuthHTTPHeaders(ctx, h.cfg)
|
||||
// Set the context for the http.Request.Context
|
||||
// This modifies both r and reqContext.Req since they point to the same value
|
||||
*reqContext.Req = *reqContext.Req.WithContext(ctx)
|
||||
@@ -137,7 +137,7 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
|
||||
attribute.Int64("userId", reqContext.UserID),
|
||||
))
|
||||
|
||||
if h.Cfg.IDResponseHeaderEnabled && reqContext.SignedInUser != nil {
|
||||
if h.cfg.IDResponseHeaderEnabled && reqContext.SignedInUser != nil {
|
||||
reqContext.Resp.Before(h.addIDHeaderEndOfRequestFunc(reqContext.SignedInUser))
|
||||
}
|
||||
|
||||
@@ -167,11 +167,11 @@ func (h *ContextHandler) addIDHeaderEndOfRequestFunc(ident identity.Requester) w
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := h.Cfg.IDResponseHeaderNamespaces[string(ident.GetIdentityType())]; !ok {
|
||||
if _, ok := h.cfg.IDResponseHeaderNamespaces[string(ident.GetIdentityType())]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
headerName := fmt.Sprintf("%s-Identity-Id", h.Cfg.IDResponseHeaderPrefix)
|
||||
headerName := fmt.Sprintf("%s-Identity-Id", h.cfg.IDResponseHeaderPrefix)
|
||||
w.Header().Add(headerName, ident.GetID())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,10 +334,9 @@ func getRestoreVersion(msg string) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
ver, err := strconv.ParseInt(parts[1], 10, 64)
|
||||
ver, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(ver), nil
|
||||
return ver, nil
|
||||
}
|
||||
|
||||
@@ -103,12 +103,46 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRules(c *contextmodel.
|
||||
// RouteConvertPrometheusDeleteNamespace deletes all rule groups that were imported from a Prometheus-compatible source
|
||||
// within a specified namespace.
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteNamespace(c *contextmodel.ReqContext, namespaceTitle string) response.Response {
|
||||
return response.Error(501, "Not implemented", nil)
|
||||
logger := srv.logger.FromContext(c.Req.Context())
|
||||
|
||||
logger.Debug("Looking up folder in the root by title", "folder_title", namespaceTitle)
|
||||
namespace, err := srv.ruleStore.GetNamespaceInRootByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser)
|
||||
if err != nil {
|
||||
return namespaceErrorResponse(err)
|
||||
}
|
||||
logger.Info("Deleting all Prometheus-imported rule groups", "folder_uid", namespace.UID, "folder_title", namespaceTitle)
|
||||
|
||||
filterOpts := &provisioning.FilterOptions{
|
||||
NamespaceUIDs: []string{namespace.UID},
|
||||
ImportedPrometheusRule: util.Pointer(true),
|
||||
}
|
||||
err = srv.alertRuleService.DeleteRuleGroups(c.Req.Context(), c.SignedInUser, models.ProvenanceConvertedPrometheus, filterOpts)
|
||||
if err != nil {
|
||||
logger.Error("Failed to delete rule groups", "folder_uid", namespace.UID, "error", err)
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
return successfulResponse()
|
||||
}
|
||||
|
||||
// RouteConvertPrometheusDeleteRuleGroup deletes a specific rule group if it was imported from a Prometheus-compatible source.
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, group string) response.Response {
|
||||
return response.Error(501, "Not implemented", nil)
|
||||
logger := srv.logger.FromContext(c.Req.Context())
|
||||
|
||||
logger.Debug("Looking up folder in the root by title", "folder_title", namespaceTitle)
|
||||
folder, err := srv.ruleStore.GetNamespaceInRootByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.GetOrgID(), c.SignedInUser)
|
||||
if err != nil {
|
||||
return namespaceErrorResponse(err)
|
||||
}
|
||||
logger.Info("Deleting Prometheus-imported rule group", "folder_uid", folder.UID, "folder_title", namespaceTitle, "group", group)
|
||||
|
||||
err = srv.alertRuleService.DeleteRuleGroup(c.Req.Context(), c.SignedInUser, folder.UID, group, models.ProvenanceConvertedPrometheus)
|
||||
if err != nil {
|
||||
logger.Error("Failed to delete rule group", "folder_uid", folder.UID, "group", group, "error", err)
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
return successfulResponse()
|
||||
}
|
||||
|
||||
// RouteConvertPrometheusGetNamespace returns the Grafana-managed alert rules for a specified namespace (folder).
|
||||
@@ -220,7 +254,7 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroup(c *contextm
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusAccepted, map[string]string{"status": "success"})
|
||||
return successfulResponse()
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) getOrCreateNamespace(c *contextmodel.ReqContext, title string, logger log.Logger) (*folder.Folder, response.Response) {
|
||||
@@ -363,3 +397,9 @@ func namespaceErrorResponse(err error) response.Response {
|
||||
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
|
||||
func successfulResponse() response.Response {
|
||||
return response.JSON(http.StatusAccepted, apimodels.ConvertPrometheusResponse{
|
||||
Status: "success",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -77,6 +78,79 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
})
|
||||
|
||||
t.Run("should replace an existing rule group", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore))
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
fldr.ParentUID = ""
|
||||
folderService.ExpectedFolder = fldr
|
||||
folderService.ExpectedFolders = []*folder.Folder{fldr}
|
||||
ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr)
|
||||
|
||||
// And a rule
|
||||
rule := models.RuleGen.
|
||||
With(models.RuleGen.WithNamespaceUID(fldr.UID)).
|
||||
With(models.RuleGen.WithGroupName(simpleGroup.Name)).
|
||||
With(models.RuleGen.WithOrgID(1)).
|
||||
With(models.RuleGen.WithPrometheusOriginalRuleDefinition("123")).
|
||||
GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), rule)
|
||||
|
||||
rc := createRequestCtx()
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, fldr.Title, simpleGroup)
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
|
||||
// Get the updated rule
|
||||
remaining, err := ruleStore.ListAlertRules(context.Background(), &models.ListAlertRulesQuery{
|
||||
OrgID: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, remaining, 1)
|
||||
|
||||
require.Equal(t, simpleGroup.Name, remaining[0].RuleGroup)
|
||||
require.Equal(t, fmt.Sprintf("[%s] %s", simpleGroup.Name, simpleGroup.Rules[0].Alert), remaining[0].Title)
|
||||
promRuleYAML, err := yaml.Marshal(simpleGroup.Rules[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(promRuleYAML), remaining[0].PrometheusRuleDefinition())
|
||||
})
|
||||
|
||||
t.Run("should fail to replace a provisioned rule group", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore))
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
fldr.ParentUID = ""
|
||||
folderService.ExpectedFolder = fldr
|
||||
folderService.ExpectedFolders = []*folder.Folder{fldr}
|
||||
ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr)
|
||||
|
||||
rule := models.RuleGen.
|
||||
With(models.RuleGen.WithNamespaceUID(fldr.UID)).
|
||||
With(models.RuleGen.WithGroupName(simpleGroup.Name)).
|
||||
With(models.RuleGen.WithOrgID(1)).
|
||||
With(models.RuleGen.WithPrometheusOriginalRuleDefinition("123")).
|
||||
GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), rule)
|
||||
// mark the rule as provisioned
|
||||
err := provenanceStore.SetProvenance(context.Background(), rule, 1, models.ProvenanceAPI)
|
||||
require.NoError(t, err)
|
||||
|
||||
rc := createRequestCtx()
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, fldr.Title, simpleGroup)
|
||||
require.Equal(t, http.StatusConflict, response.Status())
|
||||
|
||||
// Verify the rule is still present
|
||||
remaining, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{
|
||||
UID: rule.UID,
|
||||
OrgID: rule.OrgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, remaining)
|
||||
})
|
||||
|
||||
t.Run("with valid pause header values should return 202", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -420,9 +494,219 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func createConvertPrometheusSrv(t *testing.T) (*ConvertPrometheusSrv, datasources.CacheService, *fakes.RuleStore, *foldertest.FakeService) {
|
||||
func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
t.Run("for non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteNamespace(rc, "non-existent")
|
||||
require.Equal(t, http.StatusNotFound, response.Status())
|
||||
})
|
||||
|
||||
t.Run("valid request should delete rules", func(t *testing.T) {
|
||||
initNamespace := func(promDefinition string, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *fakes.RuleStore, *folder.Folder, *models.AlertRule) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, opts...)
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
fldr.ParentUID = ""
|
||||
folderService.ExpectedFolder = fldr
|
||||
folderService.ExpectedFolders = []*folder.Folder{fldr}
|
||||
ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr)
|
||||
|
||||
rule := models.RuleGen.
|
||||
With(models.RuleGen.WithNamespaceUID(fldr.UID)).
|
||||
With(models.RuleGen.WithOrgID(1)).
|
||||
With(models.RuleGen.WithPrometheusOriginalRuleDefinition(promDefinition)).
|
||||
GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), rule)
|
||||
|
||||
return srv, ruleStore, fldr, rule
|
||||
}
|
||||
|
||||
t.Run("valid request should delete rules", func(t *testing.T) {
|
||||
srv, ruleStore, fldr, rule := initNamespace("prometheus definition")
|
||||
|
||||
// Create another rule group in a different namespace that should not be deleted
|
||||
otherGroupName := "other-group"
|
||||
otherRule := models.RuleGen.
|
||||
With(models.RuleGen.WithOrgID(1)).
|
||||
With(models.RuleGen.WithGroupName(otherGroupName)).
|
||||
With(models.RuleGen.WithPrometheusOriginalRuleDefinition("other prometheus definition")).
|
||||
GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), otherRule)
|
||||
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteNamespace(rc, fldr.Title)
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
|
||||
// Verify the rule in the specified group was deleted
|
||||
remaining, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{
|
||||
UID: rule.UID,
|
||||
OrgID: rule.OrgID,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, remaining)
|
||||
|
||||
// Verify the rule in the other group still exists
|
||||
remainingOther, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{
|
||||
UID: otherRule.UID,
|
||||
OrgID: otherRule.OrgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, remainingOther)
|
||||
})
|
||||
|
||||
t.Run("fails to delete rules when they are provisioned", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, ruleStore, fldr, rule := initNamespace("", withProvenanceStore(provenanceStore))
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create a provisioned rule
|
||||
rule2 := models.RuleGen.
|
||||
With(models.RuleGen.WithNamespaceUID(fldr.UID)).
|
||||
With(models.RuleGen.WithOrgID(1)).
|
||||
With(models.RuleGen.WithPrometheusOriginalRuleDefinition("prometheus definition")).
|
||||
GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), rule2)
|
||||
err := provenanceStore.SetProvenance(context.Background(), rule2, 1, models.ProvenanceAPI)
|
||||
require.NoError(t, err)
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteNamespace(rc, fldr.Title)
|
||||
require.Equal(t, http.StatusConflict, response.Status())
|
||||
|
||||
// Verify the rule is still present
|
||||
remaining, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{
|
||||
UID: rule.UID,
|
||||
OrgID: rule.OrgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, remaining)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
t.Run("for non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteRuleGroup(rc, "non-existent", "test-group")
|
||||
require.Equal(t, http.StatusNotFound, response.Status())
|
||||
})
|
||||
|
||||
const groupName = "test-group"
|
||||
|
||||
t.Run("valid request should delete rules", func(t *testing.T) {
|
||||
initGroup := func(promDefinition string, groupName string, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *fakes.RuleStore, *folder.Folder, *models.AlertRule) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, opts...)
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
fldr.ParentUID = ""
|
||||
folderService.ExpectedFolder = fldr
|
||||
folderService.ExpectedFolders = []*folder.Folder{fldr}
|
||||
ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr)
|
||||
|
||||
rule := models.RuleGen.
|
||||
With(models.RuleGen.WithNamespaceUID(fldr.UID)).
|
||||
With(models.RuleGen.WithOrgID(1)).
|
||||
With(models.RuleGen.WithGroupName(groupName)).
|
||||
With(models.RuleGen.WithPrometheusOriginalRuleDefinition(promDefinition)).
|
||||
GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), rule)
|
||||
|
||||
return srv, ruleStore, fldr, rule
|
||||
}
|
||||
|
||||
t.Run("valid request should delete rules", func(t *testing.T) {
|
||||
srv, ruleStore, fldr, rule := initGroup("prometheus definition", groupName)
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create another rule in a different group that should not be deleted
|
||||
otherGroupName := "other-group"
|
||||
otherRule := models.RuleGen.
|
||||
With(models.RuleGen.WithNamespaceUID(fldr.UID)).
|
||||
With(models.RuleGen.WithOrgID(1)).
|
||||
With(models.RuleGen.WithGroupName(otherGroupName)).
|
||||
With(models.RuleGen.WithPrometheusOriginalRuleDefinition("other prometheus definition")).
|
||||
GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), otherRule)
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteRuleGroup(rc, fldr.Title, groupName)
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
|
||||
// Verify the rule was deleted
|
||||
remaining, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{
|
||||
UID: rule.UID,
|
||||
OrgID: rule.OrgID,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, remaining)
|
||||
|
||||
// Verify the otherRule from the "other-group" is still present
|
||||
otherRuleRefreshed, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{
|
||||
UID: otherRule.UID,
|
||||
OrgID: otherRule.OrgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, otherRuleRefreshed)
|
||||
})
|
||||
|
||||
t.Run("fails to delete rules when they are provisioned", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, ruleStore, fldr, rule := initGroup("", groupName, withProvenanceStore(provenanceStore))
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create a provisioned rule
|
||||
rule2 := models.RuleGen.
|
||||
With(models.RuleGen.WithNamespaceUID(fldr.UID)).
|
||||
With(models.RuleGen.WithOrgID(1)).
|
||||
With(models.RuleGen.WithGroupName(groupName)).
|
||||
With(models.RuleGen.WithPrometheusOriginalRuleDefinition("prometheus definition")).
|
||||
GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), rule2)
|
||||
err := provenanceStore.SetProvenance(context.Background(), rule2, 1, models.ProvenanceAPI)
|
||||
require.NoError(t, err)
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteRuleGroup(rc, fldr.Title, groupName)
|
||||
require.Equal(t, http.StatusConflict, response.Status())
|
||||
|
||||
// Verify the rule is still present
|
||||
remaining, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{
|
||||
UID: rule.UID,
|
||||
OrgID: rule.OrgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, remaining)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type convertPrometheusSrvOptions struct {
|
||||
provenanceStore provisioning.ProvisioningStore
|
||||
}
|
||||
|
||||
type convertPrometheusSrvOptionsFunc func(*convertPrometheusSrvOptions)
|
||||
|
||||
func withProvenanceStore(store provisioning.ProvisioningStore) convertPrometheusSrvOptionsFunc {
|
||||
return func(opts *convertPrometheusSrvOptions) {
|
||||
opts.provenanceStore = store
|
||||
}
|
||||
}
|
||||
|
||||
func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, datasources.CacheService, *fakes.RuleStore, *foldertest.FakeService) {
|
||||
t.Helper()
|
||||
|
||||
options := convertPrometheusSrvOptions{
|
||||
provenanceStore: fakes.NewFakeProvisioningStore(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
folder := randFolder()
|
||||
ruleStore.Folders[1] = append(ruleStore.Folders[1], folder)
|
||||
@@ -441,7 +725,7 @@ func createConvertPrometheusSrv(t *testing.T) (*ConvertPrometheusSrv, datasource
|
||||
|
||||
alertRuleService := provisioning.NewAlertRuleService(
|
||||
ruleStore,
|
||||
fakes.NewFakeProvisioningStore(),
|
||||
options.provenanceStore,
|
||||
folderService,
|
||||
quotas,
|
||||
&provisioning.NopTransactionManager{},
|
||||
|
||||
@@ -75,6 +75,7 @@ type AlertRuleService interface {
|
||||
GetRuleGroup(ctx context.Context, user identity.Requester, folder, group string) (alerting_models.AlertRuleGroup, error)
|
||||
ReplaceRuleGroup(ctx context.Context, user identity.Requester, group alerting_models.AlertRuleGroup, provenance alerting_models.Provenance) error
|
||||
DeleteRuleGroup(ctx context.Context, user identity.Requester, folder, group string, provenance alerting_models.Provenance) error
|
||||
DeleteRuleGroups(ctx context.Context, user identity.Requester, provenance alerting_models.Provenance, opts *provisioning.FilterOptions) error
|
||||
GetAlertRuleWithFolderFullpath(ctx context.Context, u identity.Requester, ruleUID string) (provisioning.AlertRuleWithFolderFullpath, error)
|
||||
GetAlertRuleGroupWithFolderFullpath(ctx context.Context, u identity.Requester, folder, group string) (alerting_models.AlertRuleGroupWithFolderFullpath, error)
|
||||
GetAlertGroupsWithFolderFullpath(ctx context.Context, u identity.Requester, opts *provisioning.FilterOptions) ([]alerting_models.AlertRuleGroupWithFolderFullpath, error)
|
||||
|
||||
@@ -125,26 +125,32 @@ func (api *API) authorize(method, path string) web.Handler {
|
||||
|
||||
// convert/prometheus API paths
|
||||
case http.MethodGet + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}",
|
||||
http.MethodGet + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}":
|
||||
http.MethodGet + "/api/convert/api/prom/rules/{NamespaceTitle}/{Group}",
|
||||
http.MethodGet + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}",
|
||||
http.MethodGet + "/api/convert/api/prom/rules/{NamespaceTitle}":
|
||||
eval = ac.EvalAll(
|
||||
ac.EvalPermission(ac.ActionAlertingRuleRead),
|
||||
ac.EvalPermission(dashboards.ActionFoldersRead),
|
||||
)
|
||||
|
||||
case http.MethodGet + "/api/convert/prometheus/config/v1/rules":
|
||||
case http.MethodGet + "/api/convert/prometheus/config/v1/rules",
|
||||
http.MethodGet + "/api/convert/api/prom/rules":
|
||||
eval = ac.EvalAll(
|
||||
ac.EvalPermission(ac.ActionAlertingRuleRead),
|
||||
ac.EvalPermission(dashboards.ActionFoldersRead),
|
||||
)
|
||||
|
||||
case http.MethodPost + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}":
|
||||
case http.MethodPost + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}",
|
||||
http.MethodPost + "/api/convert/api/prom/rules/{NamespaceTitle}":
|
||||
eval = ac.EvalAll(
|
||||
ac.EvalPermission(ac.ActionAlertingRuleCreate),
|
||||
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
|
||||
)
|
||||
|
||||
case http.MethodDelete + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}",
|
||||
http.MethodDelete + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}":
|
||||
http.MethodDelete + "/api/convert/api/prom/rules/{NamespaceTitle}/{Group}",
|
||||
http.MethodDelete + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}",
|
||||
http.MethodDelete + "/api/convert/api/prom/rules/{NamespaceTitle}":
|
||||
eval = ac.EvalAny(
|
||||
ac.EvalAll(
|
||||
ac.EvalPermission(ac.ActionAlertingRuleRead),
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestAuthorize(t *testing.T) {
|
||||
}
|
||||
paths[p] = methods
|
||||
}
|
||||
require.Len(t, paths, 63)
|
||||
require.Len(t, paths, 66)
|
||||
|
||||
ac := acmock.New()
|
||||
api := &API{AccessControl: ac, FeatureManager: featuremgmt.WithFeatures()}
|
||||
|
||||
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
@@ -33,7 +34,7 @@ func errorToResponse(err error) response.Response {
|
||||
return response.Err(err)
|
||||
}
|
||||
if errors.Is(err, datasources.ErrDataSourceNotFound) {
|
||||
return ErrResp(404, err, "")
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
if errors.Is(err, errUnexpectedDatasourceType) {
|
||||
return ErrResp(400, err, "")
|
||||
@@ -41,5 +42,5 @@ func errorToResponse(err error) response.Response {
|
||||
if errors.Is(err, errFolderAccess) {
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
return ErrResp(500, err, "")
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ import (
|
||||
)
|
||||
|
||||
type ConvertPrometheusApi interface {
|
||||
RouteConvertPrometheusCortexDeleteNamespace(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusCortexDeleteRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusCortexGetNamespace(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusCortexGetRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusCortexGetRules(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusCortexPostRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusDeleteNamespace(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusDeleteRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusGetNamespace(*contextmodel.ReqContext) response.Response
|
||||
@@ -27,6 +33,36 @@ type ConvertPrometheusApi interface {
|
||||
RouteConvertPrometheusPostRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
}
|
||||
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusCortexDeleteNamespace(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"]
|
||||
return f.handleRouteConvertPrometheusCortexDeleteNamespace(ctx, namespaceTitleParam)
|
||||
}
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusCortexDeleteRuleGroup(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"]
|
||||
groupParam := web.Params(ctx.Req)[":Group"]
|
||||
return f.handleRouteConvertPrometheusCortexDeleteRuleGroup(ctx, namespaceTitleParam, groupParam)
|
||||
}
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusCortexGetNamespace(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"]
|
||||
return f.handleRouteConvertPrometheusCortexGetNamespace(ctx, namespaceTitleParam)
|
||||
}
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusCortexGetRuleGroup(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"]
|
||||
groupParam := web.Params(ctx.Req)[":Group"]
|
||||
return f.handleRouteConvertPrometheusCortexGetRuleGroup(ctx, namespaceTitleParam, groupParam)
|
||||
}
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusCortexGetRules(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteConvertPrometheusCortexGetRules(ctx)
|
||||
}
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusCortexPostRuleGroup(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"]
|
||||
return f.handleRouteConvertPrometheusCortexPostRuleGroup(ctx, namespaceTitleParam)
|
||||
}
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusDeleteNamespace(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"]
|
||||
@@ -60,6 +96,78 @@ func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusPostRuleGroup(ctx *c
|
||||
|
||||
func (api *API) RegisterConvertPrometheusApiEndpoints(srv ConvertPrometheusApi, m *metrics.API) {
|
||||
api.RouteRegister.Group("", func(group routing.RouteRegister) {
|
||||
group.Delete(
|
||||
toMacaronPath("/api/convert/api/prom/rules/{NamespaceTitle}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodDelete, "/api/convert/api/prom/rules/{NamespaceTitle}"),
|
||||
metrics.Instrument(
|
||||
http.MethodDelete,
|
||||
"/api/convert/api/prom/rules/{NamespaceTitle}",
|
||||
api.Hooks.Wrap(srv.RouteConvertPrometheusCortexDeleteNamespace),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Delete(
|
||||
toMacaronPath("/api/convert/api/prom/rules/{NamespaceTitle}/{Group}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodDelete, "/api/convert/api/prom/rules/{NamespaceTitle}/{Group}"),
|
||||
metrics.Instrument(
|
||||
http.MethodDelete,
|
||||
"/api/convert/api/prom/rules/{NamespaceTitle}/{Group}",
|
||||
api.Hooks.Wrap(srv.RouteConvertPrometheusCortexDeleteRuleGroup),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/convert/api/prom/rules/{NamespaceTitle}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodGet, "/api/convert/api/prom/rules/{NamespaceTitle}"),
|
||||
metrics.Instrument(
|
||||
http.MethodGet,
|
||||
"/api/convert/api/prom/rules/{NamespaceTitle}",
|
||||
api.Hooks.Wrap(srv.RouteConvertPrometheusCortexGetNamespace),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/convert/api/prom/rules/{NamespaceTitle}/{Group}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodGet, "/api/convert/api/prom/rules/{NamespaceTitle}/{Group}"),
|
||||
metrics.Instrument(
|
||||
http.MethodGet,
|
||||
"/api/convert/api/prom/rules/{NamespaceTitle}/{Group}",
|
||||
api.Hooks.Wrap(srv.RouteConvertPrometheusCortexGetRuleGroup),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/convert/api/prom/rules"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodGet, "/api/convert/api/prom/rules"),
|
||||
metrics.Instrument(
|
||||
http.MethodGet,
|
||||
"/api/convert/api/prom/rules",
|
||||
api.Hooks.Wrap(srv.RouteConvertPrometheusCortexGetRules),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Post(
|
||||
toMacaronPath("/api/convert/api/prom/rules/{NamespaceTitle}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodPost, "/api/convert/api/prom/rules/{NamespaceTitle}"),
|
||||
metrics.Instrument(
|
||||
http.MethodPost,
|
||||
"/api/convert/api/prom/rules/{NamespaceTitle}",
|
||||
api.Hooks.Wrap(srv.RouteConvertPrometheusCortexPostRuleGroup),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Delete(
|
||||
toMacaronPath("/api/convert/prometheus/config/v1/rules/{NamespaceTitle}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
|
||||
@@ -20,6 +20,7 @@ func NewConvertPrometheusApi(svc *ConvertPrometheusSrv) *ConvertPrometheusApiHan
|
||||
}
|
||||
}
|
||||
|
||||
// mimirtool
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusGetRules(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.svc.RouteConvertPrometheusGetRules(ctx)
|
||||
}
|
||||
@@ -54,3 +55,28 @@ func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusPostRuleGroup(
|
||||
|
||||
return f.svc.RouteConvertPrometheusPostRuleGroup(ctx, namespaceTitle, promGroup)
|
||||
}
|
||||
|
||||
// cortextool
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusCortexGetRules(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteConvertPrometheusGetRules(ctx)
|
||||
}
|
||||
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusCortexDeleteNamespace(ctx *contextmodel.ReqContext, namespaceTitle string) response.Response {
|
||||
return f.handleRouteConvertPrometheusDeleteNamespace(ctx, namespaceTitle)
|
||||
}
|
||||
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusCortexDeleteRuleGroup(ctx *contextmodel.ReqContext, namespaceTitle string, group string) response.Response {
|
||||
return f.handleRouteConvertPrometheusDeleteRuleGroup(ctx, namespaceTitle, group)
|
||||
}
|
||||
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusCortexGetNamespace(ctx *contextmodel.ReqContext, namespaceTitle string) response.Response {
|
||||
return f.handleRouteConvertPrometheusGetNamespace(ctx, namespaceTitle)
|
||||
}
|
||||
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusCortexGetRuleGroup(ctx *contextmodel.ReqContext, namespaceTitle string, group string) response.Response {
|
||||
return f.handleRouteConvertPrometheusGetRuleGroup(ctx, namespaceTitle, group)
|
||||
}
|
||||
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusCortexPostRuleGroup(ctx *contextmodel.ReqContext, namespaceTitle string) response.Response {
|
||||
return f.handleRouteConvertPrometheusPostRuleGroup(ctx, namespaceTitle)
|
||||
}
|
||||
|
||||
@@ -4493,7 +4493,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@@ -4529,7 +4528,7 @@
|
||||
"$ref": "#/definitions/Userinfo"
|
||||
}
|
||||
},
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateRuleGroupResponse": {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// Route for mimirtool
|
||||
// swagger:route GET /convert/prometheus/config/v1/rules convert_prometheus RouteConvertPrometheusGetRules
|
||||
//
|
||||
// Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.
|
||||
@@ -16,6 +17,20 @@ import (
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// Route for cortextool
|
||||
// swagger:route GET /convert/api/prom/rules convert_prometheus RouteConvertPrometheusCortexGetRules
|
||||
//
|
||||
// Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.
|
||||
//
|
||||
// Produces:
|
||||
// - application/yaml
|
||||
//
|
||||
// Responses:
|
||||
// 200: PrometheusNamespace
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// Route for mimirtool
|
||||
// swagger:route GET /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusGetNamespace
|
||||
//
|
||||
// Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).
|
||||
@@ -28,6 +43,20 @@ import (
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// Route for cortextool
|
||||
// swagger:route GET /convert/api/prom/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusCortexGetNamespace
|
||||
//
|
||||
// Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).
|
||||
//
|
||||
// Produces:
|
||||
// - application/yaml
|
||||
//
|
||||
// Responses:
|
||||
// 200: PrometheusNamespace
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// Route for mimirtool
|
||||
// swagger:route GET /convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusGetRuleGroup
|
||||
//
|
||||
// Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.
|
||||
@@ -40,6 +69,20 @@ import (
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// Route for cortextool
|
||||
// swagger:route GET /convert/api/prom/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusCortexGetRuleGroup
|
||||
//
|
||||
// Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.
|
||||
//
|
||||
// Produces:
|
||||
// - application/yaml
|
||||
//
|
||||
// Responses:
|
||||
// 200: PrometheusRuleGroup
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// Route for mimirtool
|
||||
// swagger:route POST /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusPostRuleGroup
|
||||
//
|
||||
// Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.
|
||||
@@ -59,6 +102,27 @@ import (
|
||||
// Extensions:
|
||||
// x-raw-request: true
|
||||
|
||||
// Route for cortextool
|
||||
// swagger:route POST /convert/api/prom/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusCortexPostRuleGroup
|
||||
//
|
||||
// Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.
|
||||
// If the group already exists and was not imported from a Prometheus-compatible source initially,
|
||||
// it will not be replaced and an error will be returned.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/yaml
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: ConvertPrometheusResponse
|
||||
// 403: ForbiddenError
|
||||
//
|
||||
// Extensions:
|
||||
// x-raw-request: true
|
||||
|
||||
// Route for mimirtool
|
||||
// swagger:route DELETE /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusDeleteNamespace
|
||||
//
|
||||
// Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.
|
||||
@@ -70,6 +134,19 @@ import (
|
||||
// 202: ConvertPrometheusResponse
|
||||
// 403: ForbiddenError
|
||||
|
||||
// Route for cortextool
|
||||
// swagger:route DELETE /convert/api/prom/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusCortexDeleteNamespace
|
||||
//
|
||||
// Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: ConvertPrometheusResponse
|
||||
// 403: ForbiddenError
|
||||
|
||||
// Route for mimirtool
|
||||
// swagger:route DELETE /convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusDeleteRuleGroup
|
||||
//
|
||||
// Deletes a specific rule group if it was imported from a Prometheus-compatible source.
|
||||
@@ -81,7 +158,19 @@ import (
|
||||
// 202: ConvertPrometheusResponse
|
||||
// 403: ForbiddenError
|
||||
|
||||
// swagger:parameters RouteConvertPrometheusPostRuleGroup
|
||||
// Route for cortextool
|
||||
// swagger:route DELETE /convert/api/prom/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusCortexDeleteRuleGroup
|
||||
//
|
||||
// Deletes a specific rule group if it was imported from a Prometheus-compatible source.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: ConvertPrometheusResponse
|
||||
// 403: ForbiddenError
|
||||
|
||||
// swagger:parameters RouteConvertPrometheusPostRuleGroup RouteConvertPrometheusCortexPostRuleGroup
|
||||
type RouteConvertPrometheusPostRuleGroupParams struct {
|
||||
// in: path
|
||||
NamespaceTitle string
|
||||
@@ -119,7 +208,7 @@ type PrometheusRule struct {
|
||||
Record string `yaml:"record,omitempty"`
|
||||
}
|
||||
|
||||
// swagger:parameters RouteConvertPrometheusDeleteRuleGroup RouteConvertPrometheusGetRuleGroup
|
||||
// swagger:parameters RouteConvertPrometheusDeleteRuleGroup RouteConvertPrometheusCortexDeleteRuleGroup RouteConvertPrometheusGetRuleGroup RouteConvertPrometheusCortexGetRuleGroup
|
||||
type RouteConvertPrometheusDeleteRuleGroupParams struct {
|
||||
// in: path
|
||||
NamespaceTitle string
|
||||
@@ -127,7 +216,7 @@ type RouteConvertPrometheusDeleteRuleGroupParams struct {
|
||||
Group string
|
||||
}
|
||||
|
||||
// swagger:parameters RouteConvertPrometheusDeleteNamespace RouteConvertPrometheusGetNamespace
|
||||
// swagger:parameters RouteConvertPrometheusDeleteNamespace RouteConvertPrometheusCortexDeleteNamespace RouteConvertPrometheusGetNamespace RouteConvertPrometheusCortexGetNamespace
|
||||
type RouteConvertPrometheusDeleteNamespaceParams struct {
|
||||
// in: path
|
||||
NamespaceTitle string
|
||||
|
||||
@@ -4493,6 +4493,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@@ -4528,7 +4529,7 @@
|
||||
"$ref": "#/definitions/Userinfo"
|
||||
}
|
||||
},
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateRuleGroupResponse": {
|
||||
@@ -4770,6 +4771,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup",
|
||||
"type": "object"
|
||||
@@ -5056,6 +5058,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence",
|
||||
"type": "object"
|
||||
@@ -6398,6 +6401,253 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/convert/api/prom/rules": {
|
||||
"get": {
|
||||
"operationId": "RouteConvertPrometheusCortexGetRules",
|
||||
"produces": [
|
||||
"application/yaml"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PrometheusNamespace",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PrometheusNamespace"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.",
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/convert/api/prom/rules/{NamespaceTitle}": {
|
||||
"delete": {
|
||||
"operationId": "RouteConvertPrometheusCortexDeleteNamespace",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "NamespaceTitle",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "ConvertPrometheusResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConvertPrometheusResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.",
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "RouteConvertPrometheusCortexGetNamespace",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "NamespaceTitle",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/yaml"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PrometheusNamespace",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PrometheusNamespace"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).",
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/yaml"
|
||||
],
|
||||
"description": "If the group already exists and was not imported from a Prometheus-compatible source initially,\nit will not be replaced and an error will be returned.",
|
||||
"operationId": "RouteConvertPrometheusCortexPostRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "NamespaceTitle",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "x-grafana-alerting-datasource-uid",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "x-grafana-alerting-recording-rules-paused",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "x-grafana-alerting-alert-rules-paused",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"in": "body",
|
||||
"name": "Body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PrometheusRuleGroup"
|
||||
}
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "ConvertPrometheusResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConvertPrometheusResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.",
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"x-raw-request": "true"
|
||||
}
|
||||
},
|
||||
"/convert/api/prom/rules/{NamespaceTitle}/{Group}": {
|
||||
"delete": {
|
||||
"operationId": "RouteConvertPrometheusCortexDeleteRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "NamespaceTitle",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "Group",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "ConvertPrometheusResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConvertPrometheusResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.",
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "RouteConvertPrometheusCortexGetRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "NamespaceTitle",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "Group",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/yaml"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PrometheusRuleGroup",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PrometheusRuleGroup"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.",
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/convert/prometheus/config/v1/rules": {
|
||||
"get": {
|
||||
"operationId": "RouteConvertPrometheusGetRules",
|
||||
|
||||
@@ -1102,6 +1102,253 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/convert/api/prom/rules": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/yaml"
|
||||
],
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.",
|
||||
"operationId": "RouteConvertPrometheusCortexGetRules",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PrometheusNamespace",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PrometheusNamespace"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/convert/api/prom/rules/{NamespaceTitle}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/yaml"
|
||||
],
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).",
|
||||
"operationId": "RouteConvertPrometheusCortexGetNamespace",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "NamespaceTitle",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PrometheusNamespace",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PrometheusNamespace"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "If the group already exists and was not imported from a Prometheus-compatible source initially,\nit will not be replaced and an error will be returned.",
|
||||
"consumes": [
|
||||
"application/yaml"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.",
|
||||
"operationId": "RouteConvertPrometheusCortexPostRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "NamespaceTitle",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "x-grafana-alerting-datasource-uid",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "x-grafana-alerting-recording-rules-paused",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "x-grafana-alerting-alert-rules-paused",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PrometheusRuleGroup"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "ConvertPrometheusResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConvertPrometheusResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-raw-request": "true"
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.",
|
||||
"operationId": "RouteConvertPrometheusCortexDeleteNamespace",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "NamespaceTitle",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "ConvertPrometheusResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConvertPrometheusResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/convert/api/prom/rules/{NamespaceTitle}/{Group}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/yaml"
|
||||
],
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.",
|
||||
"operationId": "RouteConvertPrometheusCortexGetRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "NamespaceTitle",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Group",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "PrometheusRuleGroup",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/PrometheusRuleGroup"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.",
|
||||
"operationId": "RouteConvertPrometheusCortexDeleteRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "NamespaceTitle",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Group",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "ConvertPrometheusResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConvertPrometheusResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/convert/prometheus/config/v1/rules": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@@ -8434,8 +8681,9 @@
|
||||
}
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.",
|
||||
"type": "object",
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@@ -8711,6 +8959,7 @@
|
||||
}
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
@@ -8997,6 +9246,7 @@
|
||||
}
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
|
||||
@@ -501,7 +501,7 @@ func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.Q
|
||||
if frame.Fields[0].Len() == 1 {
|
||||
v = frame.At(0, 0).(*float64) // type checked above
|
||||
}
|
||||
captureFn(frame.RefID, frame.Fields[0].Labels, v)
|
||||
captureFn(refID, frame.Fields[0].Labels, v)
|
||||
}
|
||||
|
||||
if refID == c.Condition {
|
||||
|
||||
@@ -739,243 +739,322 @@ func TestEvaluate(t *testing.T) {
|
||||
resp backend.QueryDataResponse
|
||||
expected Results
|
||||
error string
|
||||
}{{
|
||||
name: "is no data with no frames",
|
||||
cond: models.Condition{
|
||||
Data: []models.AlertQuery{{
|
||||
RefID: "A",
|
||||
DatasourceUID: "test",
|
||||
}, {
|
||||
RefID: "B",
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
}, {
|
||||
RefID: "C",
|
||||
DatasourceUID: expr.OldDatasourceUID,
|
||||
}, {
|
||||
RefID: "D",
|
||||
DatasourceUID: expr.MLDatasourceUID,
|
||||
}{
|
||||
{
|
||||
name: "is no data with no frames",
|
||||
cond: models.Condition{
|
||||
Data: []models.AlertQuery{{
|
||||
RefID: "A",
|
||||
DatasourceUID: "test",
|
||||
}, {
|
||||
RefID: "B",
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
}, {
|
||||
RefID: "C",
|
||||
DatasourceUID: expr.OldDatasourceUID,
|
||||
}, {
|
||||
RefID: "D",
|
||||
DatasourceUID: expr.MLDatasourceUID,
|
||||
}},
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {Frames: nil},
|
||||
"B": {Frames: []*data.Frame{{Fields: nil}}},
|
||||
"C": {Frames: nil},
|
||||
"D": {Frames: []*data.Frame{{Fields: nil}}},
|
||||
},
|
||||
},
|
||||
expected: Results{{
|
||||
State: NoData,
|
||||
Instance: data.Labels{
|
||||
"datasource_uid": "test",
|
||||
"ref_id": "A",
|
||||
},
|
||||
}},
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {Frames: nil},
|
||||
"B": {Frames: []*data.Frame{{Fields: nil}}},
|
||||
"C": {Frames: nil},
|
||||
"D": {Frames: []*data.Frame{{Fields: nil}}},
|
||||
{
|
||||
name: "is no data for one frame with no fields",
|
||||
cond: models.Condition{
|
||||
Data: []models.AlertQuery{{
|
||||
RefID: "A",
|
||||
DatasourceUID: "test",
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: Results{{
|
||||
State: NoData,
|
||||
Instance: data.Labels{
|
||||
"datasource_uid": "test",
|
||||
"ref_id": "A",
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {Frames: []*data.Frame{{Fields: nil}}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}, {
|
||||
name: "is no data for one frame with no fields",
|
||||
cond: models.Condition{
|
||||
Data: []models.AlertQuery{{
|
||||
RefID: "A",
|
||||
DatasourceUID: "test",
|
||||
expected: Results{{
|
||||
State: NoData,
|
||||
Instance: data.Labels{
|
||||
"datasource_uid": "test",
|
||||
"ref_id": "A",
|
||||
},
|
||||
}},
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {Frames: []*data.Frame{{Fields: nil}}},
|
||||
{
|
||||
name: "results contains captured values for exact label matches",
|
||||
cond: models.Condition{
|
||||
Condition: "B",
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
"B": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "B",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: Results{{
|
||||
State: Alerting,
|
||||
Instance: data.Labels{
|
||||
"foo": "bar",
|
||||
},
|
||||
Values: map[string]NumberValueCapture{
|
||||
"A": {
|
||||
Var: "A",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(10.0),
|
||||
},
|
||||
"B": {
|
||||
Var: "B",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(1.0),
|
||||
},
|
||||
},
|
||||
EvaluationString: "[ var='A' labels={foo=bar} value=10 ], [ var='B' labels={foo=bar} value=1 ]",
|
||||
}},
|
||||
},
|
||||
expected: Results{{
|
||||
State: NoData,
|
||||
Instance: data.Labels{
|
||||
"datasource_uid": "test",
|
||||
"ref_id": "A",
|
||||
{
|
||||
name: "results contains captured values for subset of labels",
|
||||
cond: models.Condition{
|
||||
Condition: "B",
|
||||
},
|
||||
}},
|
||||
}, {
|
||||
name: "results contains captured values for exact label matches",
|
||||
cond: models.Condition{
|
||||
Condition: "B",
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
"B": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "B",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar", "bar": "baz"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: Results{{
|
||||
State: Alerting,
|
||||
Instance: data.Labels{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
},
|
||||
Values: map[string]NumberValueCapture{
|
||||
"A": {
|
||||
Var: "A",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(10.0),
|
||||
},
|
||||
"B": {
|
||||
Var: "B",
|
||||
Labels: data.Labels{"foo": "bar", "bar": "baz"},
|
||||
Value: util.Pointer(1.0),
|
||||
},
|
||||
},
|
||||
EvaluationString: "[ var='A' labels={foo=bar} value=10 ], [ var='B' labels={bar=baz, foo=bar} value=1 ]",
|
||||
}},
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
"B": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "B",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
{
|
||||
name: "results contains error if condition frame has error",
|
||||
cond: models.Condition{
|
||||
Condition: "B",
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
"B": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "B",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar", "bar": "baz"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
Error: errors.New("some frame error"),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: Results{{
|
||||
State: Error,
|
||||
Error: errors.New("some frame error"),
|
||||
EvaluationString: "",
|
||||
}},
|
||||
},
|
||||
expected: Results{{
|
||||
State: Alerting,
|
||||
Instance: data.Labels{
|
||||
"foo": "bar",
|
||||
{
|
||||
name: "results contain underlying error if condition frame has error that depends on another node",
|
||||
cond: models.Condition{
|
||||
Condition: "B",
|
||||
},
|
||||
Values: map[string]NumberValueCapture{
|
||||
"A": {
|
||||
Var: "A",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(10.0),
|
||||
},
|
||||
"B": {
|
||||
Var: "B",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(1.0),
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
Error: errors.New("another error depends on me"),
|
||||
},
|
||||
"B": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "B",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar", "bar": "baz"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
Error: expr.MakeDependencyError("B", "A"),
|
||||
},
|
||||
},
|
||||
},
|
||||
EvaluationString: "[ var='A' labels={foo=bar} value=10 ], [ var='B' labels={foo=bar} value=1 ]",
|
||||
}},
|
||||
}, {
|
||||
name: "results contains captured values for subset of labels",
|
||||
cond: models.Condition{
|
||||
Condition: "B",
|
||||
expected: Results{{
|
||||
State: Error,
|
||||
Error: errors.New("another error depends on me"),
|
||||
EvaluationString: "",
|
||||
}},
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
"B": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "B",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar", "bar": "baz"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
{
|
||||
name: "result values for all refIDs when a math no-op expression is used and two results share the same frame pointer",
|
||||
cond: models.Condition{
|
||||
Condition: "C",
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
"B": {
|
||||
// in a math no-op expression the frame for B is the same as A
|
||||
// e.g. A = some_query, B = `${A}`
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
"C": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "C",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: Results{{
|
||||
State: Alerting,
|
||||
Instance: data.Labels{
|
||||
"foo": "bar",
|
||||
},
|
||||
Values: map[string]NumberValueCapture{
|
||||
"A": {
|
||||
Var: "A",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(10.0),
|
||||
},
|
||||
"B": {
|
||||
Var: "B",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(10.0),
|
||||
},
|
||||
"C": {
|
||||
Var: "C",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(1.0),
|
||||
},
|
||||
},
|
||||
EvaluationString: "[ var='A' labels={foo=bar} value=10 ], [ var='B' labels={foo=bar} value=10 ], [ var='C' labels={foo=bar} value=1 ]",
|
||||
}},
|
||||
},
|
||||
expected: Results{{
|
||||
State: Alerting,
|
||||
Instance: data.Labels{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
},
|
||||
Values: map[string]NumberValueCapture{
|
||||
"A": {
|
||||
Var: "A",
|
||||
Labels: data.Labels{"foo": "bar"},
|
||||
Value: util.Pointer(10.0),
|
||||
},
|
||||
"B": {
|
||||
Var: "B",
|
||||
Labels: data.Labels{"foo": "bar", "bar": "baz"},
|
||||
Value: util.Pointer(1.0),
|
||||
},
|
||||
},
|
||||
EvaluationString: "[ var='A' labels={foo=bar} value=10 ], [ var='B' labels={bar=baz, foo=bar} value=1 ]",
|
||||
}},
|
||||
}, {
|
||||
name: "results contains error if condition frame has error",
|
||||
cond: models.Condition{
|
||||
Condition: "B",
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
},
|
||||
"B": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "B",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar", "bar": "baz"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
Error: errors.New("some frame error"),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: Results{{
|
||||
State: Error,
|
||||
Error: errors.New("some frame error"),
|
||||
EvaluationString: "",
|
||||
}},
|
||||
}, {
|
||||
name: "results contain underlying error if condition frame has error that depends on another node",
|
||||
cond: models.Condition{
|
||||
Condition: "B",
|
||||
},
|
||||
resp: backend.QueryDataResponse{
|
||||
Responses: backend.Responses{
|
||||
"A": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "A",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar"},
|
||||
[]*float64{util.Pointer(10.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
Error: errors.New("another error depends on me"),
|
||||
},
|
||||
"B": {
|
||||
Frames: []*data.Frame{{
|
||||
RefID: "B",
|
||||
Fields: []*data.Field{
|
||||
data.NewField(
|
||||
"Value",
|
||||
data.Labels{"foo": "bar", "bar": "baz"},
|
||||
[]*float64{util.Pointer(1.0)},
|
||||
),
|
||||
},
|
||||
}},
|
||||
Error: expr.MakeDependencyError("B", "A"),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: Results{{
|
||||
State: Error,
|
||||
Error: errors.New("another error depends on me"),
|
||||
EvaluationString: "",
|
||||
}},
|
||||
}}
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@@ -29,6 +30,11 @@ type ruleAccessControlService interface {
|
||||
CanWriteAllRules(ctx context.Context, user identity.Requester) (bool, error)
|
||||
}
|
||||
|
||||
var errProvenanceMismatch = errutil.NewBase(errutil.StatusConflict, "alerting.provenanceMismatch").MustTemplate(
|
||||
"cannot {{ .Public.Operation }} with provided provenance '{{ .Public.ProvidedProvenance }}', needs '{{ .Public.StoredProvenance }}'",
|
||||
errutil.WithPublic("cannot {{ .Public.Operation }} with provided provenance '{{ .Public.ProvidedProvenance }}', needs '{{ .Public.StoredProvenance }}'"),
|
||||
)
|
||||
|
||||
type NotificationSettingsValidatorProvider interface {
|
||||
Validator(ctx context.Context, orgID int64) (notifier.NotificationSettingsValidator, error)
|
||||
}
|
||||
@@ -445,27 +451,42 @@ func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, user iden
|
||||
}
|
||||
|
||||
func (service *AlertRuleService) DeleteRuleGroup(ctx context.Context, user identity.Requester, namespaceUID, group string, provenance models.Provenance) error {
|
||||
delta, err := store.CalculateRuleGroupDelete(ctx, service.ruleStore, models.AlertRuleGroupKey{
|
||||
OrgID: user.GetOrgID(),
|
||||
NamespaceUID: namespaceUID,
|
||||
RuleGroup: group,
|
||||
return service.DeleteRuleGroups(ctx, user, provenance, &FilterOptions{
|
||||
NamespaceUIDs: []string{namespaceUID},
|
||||
RuleGroups: []string{group},
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteRuleGroups deletes alert rule groups by the specified filter options.
|
||||
func (service *AlertRuleService) DeleteRuleGroups(ctx context.Context, user identity.Requester, provenance models.Provenance, filterOpts *FilterOptions) error {
|
||||
q := models.ListAlertRulesQuery{}
|
||||
q = filterOpts.apply(q)
|
||||
q.OrgID = user.GetOrgID()
|
||||
|
||||
deltas, err := store.CalculateRuleGroupsDelete(ctx, service.ruleStore, user.GetOrgID(), &q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if the current user has permissions to all rules and can bypass the regular authorization validation.
|
||||
can, err := service.authz.CanWriteAllRules(ctx, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !can {
|
||||
if err := service.authz.AuthorizeRuleGroupWrite(ctx, user, delta); err != nil {
|
||||
return err
|
||||
// Perform all deletions in a transaction
|
||||
return service.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
for _, delta := range deltas {
|
||||
can, err := service.authz.CanWriteAllRules(ctx, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !can {
|
||||
if err := service.authz.AuthorizeRuleGroupWrite(ctx, user, delta); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = service.persistDelta(ctx, user, delta, provenance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return service.persistDelta(ctx, user, delta, provenance)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (service *AlertRuleService) calcDelta(ctx context.Context, user identity.Requester, group models.AlertRuleGroup) (*store.GroupDelta, error) {
|
||||
@@ -526,7 +547,13 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, user identity
|
||||
return err
|
||||
}
|
||||
if canUpdate := validation.CanUpdateProvenanceInRuleGroup(storedProvenance, provenance); !canUpdate {
|
||||
return fmt.Errorf("cannot delete with provided provenance '%s', needs '%s'", provenance, storedProvenance)
|
||||
return errProvenanceMismatch.Build(errutil.TemplateData{
|
||||
Public: map[string]interface{}{
|
||||
"ProvidedProvenance": provenance,
|
||||
"StoredProvenance": storedProvenance,
|
||||
"Operation": "delete",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := service.deleteRules(ctx, user.GetOrgID(), delta.Delete...); err != nil {
|
||||
@@ -543,7 +570,13 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, user identity
|
||||
return err
|
||||
}
|
||||
if canUpdate := validation.CanUpdateProvenanceInRuleGroup(storedProvenance, provenance); !canUpdate {
|
||||
return fmt.Errorf("cannot update with provided provenance '%s', needs '%s'", provenance, storedProvenance)
|
||||
return errProvenanceMismatch.Build(errutil.TemplateData{
|
||||
Public: map[string]interface{}{
|
||||
"ProvidedProvenance": provenance,
|
||||
"StoredProvenance": storedProvenance,
|
||||
"Operation": "update",
|
||||
},
|
||||
})
|
||||
}
|
||||
updates = append(updates, models.UpdateRule{
|
||||
Existing: update.Existing,
|
||||
@@ -689,7 +722,13 @@ func (service *AlertRuleService) DeleteAlertRule(ctx context.Context, user ident
|
||||
return err
|
||||
}
|
||||
if storedProvenance != provenance && storedProvenance != models.ProvenanceNone {
|
||||
return fmt.Errorf("cannot delete with provided provenance '%s', needs '%s'", provenance, storedProvenance)
|
||||
return errProvenanceMismatch.Build(errutil.TemplateData{
|
||||
Public: map[string]interface{}{
|
||||
"ProvidedProvenance": provenance,
|
||||
"StoredProvenance": storedProvenance,
|
||||
"Operation": "delete",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
can, err := service.authz.CanWriteAllRules(ctx, user)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -1577,7 +1578,7 @@ func TestDeleteRuleGroup(t *testing.T) {
|
||||
assert.Equal(t, u, user)
|
||||
assert.Equal(t, groupKey, change.GroupKey)
|
||||
assert.Contains(t, change.AffectedGroups, groupKey)
|
||||
assert.EqualValues(t, rules, change.AffectedGroups[groupKey])
|
||||
assert.ElementsMatch(t, rules, change.AffectedGroups[groupKey])
|
||||
assert.Empty(t, change.Update)
|
||||
assert.Empty(t, change.New)
|
||||
assert.Len(t, change.Delete, len(rules))
|
||||
@@ -1597,6 +1598,228 @@ func TestDeleteRuleGroup(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteRuleGroups(t *testing.T) {
|
||||
orgID1 := rand.Int63()
|
||||
orgID2 := rand.Int63()
|
||||
u := &user.SignedInUser{OrgID: orgID1}
|
||||
|
||||
// Create groups across different orgs and namespaces
|
||||
groupKey1 := models.AlertRuleGroupKey{
|
||||
OrgID: orgID1,
|
||||
NamespaceUID: "namespace1",
|
||||
RuleGroup: "group1",
|
||||
}
|
||||
groupKey2 := models.AlertRuleGroupKey{
|
||||
OrgID: orgID1,
|
||||
NamespaceUID: "namespace2",
|
||||
RuleGroup: "group2",
|
||||
}
|
||||
groupKey3 := models.AlertRuleGroupKey{
|
||||
OrgID: orgID1,
|
||||
NamespaceUID: "namespace3",
|
||||
RuleGroup: "group3",
|
||||
}
|
||||
groupKey4 := models.AlertRuleGroupKey{
|
||||
OrgID: orgID2, // Different org
|
||||
NamespaceUID: "namespace1",
|
||||
RuleGroup: "group1",
|
||||
}
|
||||
|
||||
gen := models.RuleGen
|
||||
// Create rules for each group
|
||||
rules1 := gen.With(gen.WithGroupKey(groupKey1)).GenerateManyRef(2)
|
||||
rules2 := gen.With(gen.WithGroupKey(groupKey2)).GenerateManyRef(3)
|
||||
rules3 := gen.With(gen.WithGroupKey(groupKey3)).GenerateManyRef(2)
|
||||
rules4 := gen.With(gen.WithGroupKey(groupKey4)).GenerateManyRef(2)
|
||||
|
||||
org1Rules := slices.Concat(rules1, rules2, rules3)
|
||||
org2Rules := rules4
|
||||
|
||||
initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) {
|
||||
service, ruleStore, provenanceStore, ac := initService(t)
|
||||
ruleStore.Rules = map[int64][]*models.AlertRule{
|
||||
orgID1: org1Rules,
|
||||
orgID2: org2Rules,
|
||||
}
|
||||
// Set provenance for all rules
|
||||
for _, rules := range []([]*models.AlertRule){org1Rules, org2Rules} {
|
||||
for _, rule := range rules {
|
||||
err := provenanceStore.SetProvenance(context.Background(), rule, rule.OrgID, models.ProvenanceAPI)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
return service, ruleStore, provenanceStore, ac
|
||||
}
|
||||
|
||||
getUIDs := func(rules []*models.AlertRule) []string {
|
||||
uids := make([]string, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
uids = append(uids, rule.UID)
|
||||
}
|
||||
return uids
|
||||
}
|
||||
|
||||
t.Run("when deleting specific groups", func(t *testing.T) {
|
||||
filterOpts := &FilterOptions{
|
||||
NamespaceUIDs: []string{"namespace1"},
|
||||
RuleGroups: []string{"group1"},
|
||||
}
|
||||
|
||||
t.Run("when user can write all rules", func(t *testing.T) {
|
||||
service, ruleStore, _, ac := initServiceWithData(t)
|
||||
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err := service.DeleteRuleGroups(context.Background(), u, models.ProvenanceAPI, filterOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, ac.Calls, 1)
|
||||
assert.Equal(t, "CanWriteAllRules", ac.Calls[0].Method)
|
||||
|
||||
// Verify only rules from group1 in org1 were deleted
|
||||
deletes := getDeletedRules(t, ruleStore)
|
||||
require.Len(t, deletes, 1)
|
||||
require.ElementsMatch(t, getUIDs(rules1), deletes[0].uids)
|
||||
})
|
||||
|
||||
t.Run("when user cannot write all rules", func(t *testing.T) {
|
||||
t.Run("should not delete if not authorized", func(t *testing.T) {
|
||||
service, ruleStore, _, ac := initServiceWithData(t)
|
||||
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
expectedErr := errors.New("test error")
|
||||
ac.AuthorizeRuleChangesFunc = func(ctx context.Context, user identity.Requester, change *store.GroupDelta) error {
|
||||
return expectedErr
|
||||
}
|
||||
|
||||
err := service.DeleteRuleGroups(context.Background(), u, models.ProvenanceAPI, filterOpts)
|
||||
require.ErrorIs(t, err, expectedErr)
|
||||
|
||||
require.Len(t, ac.Calls, 2)
|
||||
assert.Equal(t, "CanWriteAllRules", ac.Calls[0].Method)
|
||||
assert.Equal(t, "AuthorizeRuleGroupWrite", ac.Calls[1].Method)
|
||||
|
||||
deletes := getDeletedRules(t, ruleStore)
|
||||
require.Empty(t, deletes)
|
||||
})
|
||||
|
||||
t.Run("should delete group1 when authorized", func(t *testing.T) {
|
||||
service, ruleStore, _, ac := initServiceWithData(t)
|
||||
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
ac.AuthorizeRuleChangesFunc = func(ctx context.Context, user identity.Requester, change *store.GroupDelta) error {
|
||||
assert.Equal(t, u, user)
|
||||
assert.Equal(t, groupKey1, change.GroupKey)
|
||||
assert.ElementsMatch(t, rules1, change.AffectedGroups[groupKey1])
|
||||
assert.Empty(t, change.Update)
|
||||
assert.Empty(t, change.New)
|
||||
assert.Len(t, change.Delete, len(rules1))
|
||||
return nil
|
||||
}
|
||||
|
||||
err := service.DeleteRuleGroups(context.Background(), u, models.ProvenanceAPI, filterOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, ac.Calls, 2)
|
||||
assert.Equal(t, "CanWriteAllRules", ac.Calls[0].Method)
|
||||
assert.Equal(t, "AuthorizeRuleGroupWrite", ac.Calls[1].Method)
|
||||
|
||||
deletes := getDeletedRules(t, ruleStore)
|
||||
require.Len(t, deletes, 1)
|
||||
require.ElementsMatch(t, getUIDs(rules1), deletes[0].uids)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when deleting multiple groups from multiple namespaces", func(t *testing.T) {
|
||||
filterOpts := &FilterOptions{
|
||||
NamespaceUIDs: []string{"namespace1", "namespace2"},
|
||||
RuleGroups: []string{"group1", "group2"},
|
||||
}
|
||||
|
||||
t.Run("should delete all matching groups from correct org", func(t *testing.T) {
|
||||
service, ruleStore, _, ac := initServiceWithData(t)
|
||||
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err := service.DeleteRuleGroups(context.Background(), u, models.ProvenanceAPI, filterOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
deletes := getDeletedRules(t, ruleStore)
|
||||
require.Len(t, deletes, 2)
|
||||
require.ElementsMatch(
|
||||
t,
|
||||
slices.Concat(getUIDs(rules1), getUIDs(rules2)),
|
||||
slices.Concat(deletes[0].uids, deletes[1].uids),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when filtering by imported Prometheus rules", func(t *testing.T) {
|
||||
filterOpts := &FilterOptions{
|
||||
ImportedPrometheusRule: util.Pointer(true),
|
||||
NamespaceUIDs: []string{"namespace1"},
|
||||
}
|
||||
|
||||
t.Run("when the group is not imported", func(t *testing.T) {
|
||||
filterOpts.RuleGroups = []string{groupKey1.RuleGroup}
|
||||
service, _, _, ac := initServiceWithData(t)
|
||||
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err := service.DeleteRuleGroups(context.Background(), u, models.ProvenanceAPI, filterOpts)
|
||||
require.ErrorIs(t, err, models.ErrAlertRuleGroupNotFound)
|
||||
})
|
||||
|
||||
t.Run("when the group is imported", func(t *testing.T) {
|
||||
importedGroup := models.AlertRuleGroupKey{
|
||||
OrgID: orgID1,
|
||||
NamespaceUID: "namespace1",
|
||||
RuleGroup: "newgroup",
|
||||
}
|
||||
importedRules := gen.With(
|
||||
gen.WithGroupKey(importedGroup),
|
||||
gen.WithPrometheusOriginalRuleDefinition("something"),
|
||||
).GenerateManyRef(2)
|
||||
filterOpts.RuleGroups = []string{importedGroup.RuleGroup}
|
||||
service, ruleStore, _, ac := initServiceWithData(t)
|
||||
ruleStore.Rules[orgID1] = append(ruleStore.Rules[orgID1], importedRules...)
|
||||
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err := service.DeleteRuleGroups(context.Background(), u, models.ProvenanceAPI, filterOpts)
|
||||
require.NoError(t, err)
|
||||
deletes := getDeletedRules(t, ruleStore)
|
||||
require.Len(t, deletes, 1)
|
||||
require.ElementsMatch(t, getUIDs(importedRules), deletes[0].uids)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with no matching rule groups", func(t *testing.T) {
|
||||
filterOpts := &FilterOptions{
|
||||
NamespaceUIDs: []string{"non-existent"},
|
||||
RuleGroups: []string{"non-existent"},
|
||||
}
|
||||
|
||||
service, ruleStore, _, ac := initServiceWithData(t)
|
||||
ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err := service.DeleteRuleGroups(context.Background(), u, models.ProvenanceAPI, filterOpts)
|
||||
require.ErrorIs(t, err, models.ErrAlertRuleGroupNotFound)
|
||||
|
||||
deletes := getDeletedRules(t, ruleStore)
|
||||
require.Empty(t, deletes)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvisiongWithFullpath(t *testing.T) {
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
inProcBus := bus.ProvideBus(tracer)
|
||||
@@ -1696,6 +1919,31 @@ func getDeleteQueries(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery {
|
||||
return result
|
||||
}
|
||||
|
||||
type deleteRuleOperation struct {
|
||||
orgID int64
|
||||
uids []string
|
||||
}
|
||||
|
||||
func getDeletedRules(t *testing.T, ruleStore *fakes.RuleStore) []deleteRuleOperation {
|
||||
t.Helper()
|
||||
|
||||
queries := getDeleteQueries(ruleStore)
|
||||
operations := make([]deleteRuleOperation, 0, len(queries))
|
||||
for _, q := range queries {
|
||||
orgID, ok := q.Params[0].(int64)
|
||||
require.True(t, ok, "orgID parameter should be int64")
|
||||
|
||||
uids, ok := q.Params[1].([]string)
|
||||
require.True(t, ok, "uids parameter should be []string")
|
||||
|
||||
operations = append(operations, deleteRuleOperation{
|
||||
orgID: orgID,
|
||||
uids: uids,
|
||||
})
|
||||
}
|
||||
return operations
|
||||
}
|
||||
|
||||
func createAlertRuleService(t *testing.T, folderService folder.Service) AlertRuleService {
|
||||
t.Helper()
|
||||
sqlStore := db.InitTestDB(t)
|
||||
|
||||
@@ -221,15 +221,13 @@ func CalculateRuleUpdate(ctx context.Context, ruleReader RuleReader, rule *model
|
||||
return calculateChanges(ctx, ruleReader, rule.GetGroupKey(), existingGroupRules, newGroup)
|
||||
}
|
||||
|
||||
// CalculateRuleGroupDelete calculates GroupDelta that reflects an operation of removing entire group
|
||||
func CalculateRuleGroupDelete(ctx context.Context, ruleReader RuleReader, groupKey models.AlertRuleGroupKey) (*GroupDelta, error) {
|
||||
// List all rules in the group.
|
||||
q := models.ListAlertRulesQuery{
|
||||
OrgID: groupKey.OrgID,
|
||||
NamespaceUIDs: []string{groupKey.NamespaceUID},
|
||||
RuleGroups: []string{groupKey.RuleGroup},
|
||||
// CalculateRuleGroupsDelete calculates []*GroupDelta that reflects an operation of removing multiple groups
|
||||
func CalculateRuleGroupsDelete(ctx context.Context, ruleReader RuleReader, orgID int64, query *models.ListAlertRulesQuery) ([]*GroupDelta, error) {
|
||||
if query == nil {
|
||||
query = &models.ListAlertRulesQuery{}
|
||||
}
|
||||
ruleList, err := ruleReader.ListAlertRules(ctx, &q)
|
||||
query.OrgID = orgID
|
||||
ruleList, err := ruleReader.ListAlertRules(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -237,14 +235,40 @@ func CalculateRuleGroupDelete(ctx context.Context, ruleReader RuleReader, groupK
|
||||
return nil, models.ErrAlertRuleGroupNotFound.Errorf("")
|
||||
}
|
||||
|
||||
delta := &GroupDelta{
|
||||
GroupKey: groupKey,
|
||||
Delete: ruleList,
|
||||
AffectedGroups: map[models.AlertRuleGroupKey]models.RulesGroup{
|
||||
groupKey: ruleList,
|
||||
},
|
||||
groups := models.GroupByAlertRuleGroupKey(ruleList)
|
||||
deltas := make([]*GroupDelta, 0, len(groups))
|
||||
for groupKey := range groups {
|
||||
delta := &GroupDelta{
|
||||
GroupKey: groupKey,
|
||||
Delete: groups[groupKey],
|
||||
AffectedGroups: map[models.AlertRuleGroupKey]models.RulesGroup{
|
||||
groupKey: groups[groupKey],
|
||||
},
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deltas = append(deltas, delta)
|
||||
}
|
||||
return delta, nil
|
||||
|
||||
return deltas, nil
|
||||
}
|
||||
|
||||
// CalculateRuleGroupDelete calculates GroupDelta that reflects an operation of removing entire group
|
||||
func CalculateRuleGroupDelete(ctx context.Context, ruleReader RuleReader, groupKey models.AlertRuleGroupKey) (*GroupDelta, error) {
|
||||
q := &models.ListAlertRulesQuery{
|
||||
NamespaceUIDs: []string{groupKey.NamespaceUID},
|
||||
RuleGroups: []string{groupKey.RuleGroup},
|
||||
}
|
||||
deltas, err := CalculateRuleGroupsDelete(ctx, ruleReader, groupKey.OrgID, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(deltas) != 1 {
|
||||
return nil, fmt.Errorf("expected to get a single group delta, got %d", len(deltas))
|
||||
}
|
||||
|
||||
return deltas[0], nil
|
||||
}
|
||||
|
||||
// CalculateRuleDelete calculates GroupDelta that reflects an operation of removing a rule from the group.
|
||||
|
||||
@@ -428,6 +428,91 @@ func TestCalculateAutomaticChanges(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCalculateRuleGroupsDelete(t *testing.T) {
|
||||
orgId := int64(rand.Int31())
|
||||
gen := models.RuleGen
|
||||
|
||||
t.Run("returns ErrAlertRuleGroupNotFound when namespace has no rules", func(t *testing.T) {
|
||||
fakeStore := fakes.NewRuleStore(t)
|
||||
otherRules := gen.With(gen.WithOrgID(orgId), gen.WithNamespaceUID("ns-1")).GenerateManyRef(3)
|
||||
fakeStore.Rules[orgId] = otherRules
|
||||
|
||||
query := &models.ListAlertRulesQuery{
|
||||
NamespaceUIDs: []string{"ns-2"},
|
||||
}
|
||||
deltas, err := CalculateRuleGroupsDelete(context.Background(), fakeStore, orgId, query)
|
||||
require.ErrorIs(t, err, models.ErrAlertRuleGroupNotFound)
|
||||
require.Nil(t, deltas)
|
||||
})
|
||||
|
||||
t.Run("returns deltas for all affected groups in namespace", func(t *testing.T) {
|
||||
fakeStore := fakes.NewRuleStore(t)
|
||||
folder := randFolder()
|
||||
|
||||
// Create rules in two groups in target namespace
|
||||
group1Key := models.AlertRuleGroupKey{
|
||||
OrgID: orgId,
|
||||
NamespaceUID: folder.UID,
|
||||
RuleGroup: util.GenerateShortUID(),
|
||||
}
|
||||
group2Key := models.AlertRuleGroupKey{
|
||||
OrgID: orgId,
|
||||
NamespaceUID: folder.UID,
|
||||
RuleGroup: util.GenerateShortUID(),
|
||||
}
|
||||
|
||||
group1Rules := gen.With(gen.WithGroupKey(group1Key)).GenerateManyRef(3)
|
||||
group2Rules := gen.With(gen.WithGroupKey(group2Key)).GenerateManyRef(2)
|
||||
allNamespaceRules := append(group1Rules, group2Rules...)
|
||||
|
||||
// Create rules in different namespace
|
||||
otherRules := gen.With(gen.WithOrgID(orgId), gen.WithNamespaceUIDNotIn(folder.UID)).GenerateManyRef(3)
|
||||
|
||||
fakeStore.Rules[orgId] = append(allNamespaceRules, otherRules...)
|
||||
|
||||
query := &models.ListAlertRulesQuery{
|
||||
NamespaceUIDs: []string{folder.UID},
|
||||
}
|
||||
|
||||
deltas, err := CalculateRuleGroupsDelete(context.Background(), fakeStore, orgId, query)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, deltas, 2, "expected deltas for two groups")
|
||||
|
||||
// Verify each group's delta
|
||||
for _, delta := range deltas {
|
||||
require.True(t, delta.GroupKey == group1Key || delta.GroupKey == group2Key)
|
||||
require.Empty(t, delta.Update)
|
||||
require.Empty(t, delta.New)
|
||||
|
||||
require.Contains(t, delta.AffectedGroups, delta.GroupKey)
|
||||
if delta.GroupKey == group1Key {
|
||||
require.ElementsMatch(t, group1Rules, delta.Delete)
|
||||
require.ElementsMatch(t, group1Rules, delta.AffectedGroups[delta.GroupKey])
|
||||
} else {
|
||||
require.ElementsMatch(t, group2Rules, delta.Delete)
|
||||
require.ElementsMatch(t, group2Rules, delta.AffectedGroups[delta.GroupKey])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails if store returns error", func(t *testing.T) {
|
||||
fakeStore := fakes.NewRuleStore(t)
|
||||
expectedErr := errors.New("store error")
|
||||
fakeStore.Hook = func(cmd any) error {
|
||||
switch cmd.(type) {
|
||||
case models.ListAlertRulesQuery:
|
||||
return expectedErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
deltas, err := CalculateRuleGroupsDelete(context.Background(), fakeStore, orgId, nil)
|
||||
require.ErrorIs(t, err, expectedErr)
|
||||
require.Nil(t, deltas)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCalculateRuleGroupDelete(t *testing.T) {
|
||||
gen := models.RuleGen
|
||||
fakeStore := fakes.NewRuleStore(t)
|
||||
@@ -449,13 +534,13 @@ func TestCalculateRuleGroupDelete(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, groupKey, delta.GroupKey)
|
||||
assert.EqualValues(t, groupRules, delta.Delete)
|
||||
assert.ElementsMatch(t, groupRules, delta.Delete)
|
||||
|
||||
assert.Empty(t, delta.Update)
|
||||
assert.Empty(t, delta.New)
|
||||
|
||||
assert.Len(t, delta.AffectedGroups, 1)
|
||||
assert.Equal(t, models.RulesGroup(groupRules), delta.AffectedGroups[delta.GroupKey])
|
||||
assert.ElementsMatch(t, groupRules, delta.AffectedGroups[delta.GroupKey])
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,15 @@ func (f *RuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQu
|
||||
if len(q.RuleUIDs) > 0 && !slices.Contains(q.RuleUIDs, r.UID) {
|
||||
continue
|
||||
}
|
||||
if q.ImportedPrometheusRule != nil {
|
||||
hasOriginalRuleDefinition := r.PrometheusRuleDefinition() != ""
|
||||
if *q.ImportedPrometheusRule && !hasOriginalRuleDefinition {
|
||||
continue
|
||||
}
|
||||
if !*q.ImportedPrometheusRule && hasOriginalRuleDefinition {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ruleList = append(ruleList, r)
|
||||
}
|
||||
@@ -308,12 +317,21 @@ func (f *RuleStore) InsertAlertRules(_ context.Context, _ *models.UserUID, q []m
|
||||
defer f.mtx.Unlock()
|
||||
f.RecordedOps = append(f.RecordedOps, q)
|
||||
ids := make([]models.AlertRuleKeyWithId, 0, len(q))
|
||||
rulesPerOrg := map[int64][]models.AlertRule{}
|
||||
for _, rule := range q {
|
||||
ids = append(ids, models.AlertRuleKeyWithId{
|
||||
AlertRuleKey: rule.GetKey(),
|
||||
ID: rand.Int63(),
|
||||
})
|
||||
rulesPerOrg[rule.OrgID] = append(rulesPerOrg[rule.OrgID], rule)
|
||||
}
|
||||
|
||||
for orgID, rules := range rulesPerOrg {
|
||||
for _, rule := range rules {
|
||||
f.Rules[orgID] = append(f.Rules[orgID], &rule)
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.Hook(q); err != nil {
|
||||
return ids, err
|
||||
}
|
||||
|
||||
@@ -14,7 +14,12 @@ func (cfg *Cfg) readAnonymousSettings() {
|
||||
anonSettings := AnonymousSettings{}
|
||||
anonSettings.Enabled = anonSection.Key("enabled").MustBool(false)
|
||||
anonSettings.OrgName = valueAsString(anonSection, "org_name", "")
|
||||
// Deprecated:
|
||||
// only viewer role is supported
|
||||
anonSettings.OrgRole = valueAsString(anonSection, "org_role", "")
|
||||
if anonSettings.OrgRole != "Viewer" {
|
||||
cfg.Logger.Warn("auth.anonymous.org_role is deprecated, only viewer role is supported")
|
||||
}
|
||||
anonSettings.HideVersion = anonSection.Key("hide_version").MustBool(false)
|
||||
anonSettings.DeviceLimit = anonSection.Key("device_limit").MustInt64(0)
|
||||
cfg.Anonymous = anonSettings
|
||||
|
||||
@@ -26,7 +26,7 @@ type mockService struct {
|
||||
}
|
||||
|
||||
// NewStorage implements Service.
|
||||
func (m *mockService) NewStorage(gr schema.GroupResource, legacy rest.LegacyStorage, storage rest.Storage) (rest.Storage, error) {
|
||||
func (m *mockService) NewStorage(gr schema.GroupResource, legacy rest.Storage, storage rest.Storage) (rest.Storage, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func (m *service) NewStorage(gr schema.GroupResource,
|
||||
legacy grafanarest.LegacyStorage,
|
||||
legacy grafanarest.Storage,
|
||||
storage grafanarest.Storage,
|
||||
) (grafanarest.Storage, error) {
|
||||
status, err := m.Status(context.Background(), gr)
|
||||
@@ -53,7 +53,7 @@ func (m *service) NewStorage(gr schema.GroupResource,
|
||||
// When a resource is marked as "migrating", all write requests will be 503 unavailable
|
||||
type runtimeDualWriter struct {
|
||||
service Service
|
||||
legacy grafanarest.LegacyStorage
|
||||
legacy grafanarest.Storage
|
||||
unified grafanarest.Storage
|
||||
dualwrite grafanarest.Storage
|
||||
gr schema.GroupResource
|
||||
|
||||
@@ -76,10 +76,10 @@ func TestManagedMode3_Create(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (rest.LegacyStorage)(nil)
|
||||
l := (rest.Storage)(nil)
|
||||
s := (rest.Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -148,10 +148,10 @@ func TestManagedMode3_Get(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (rest.LegacyStorage)(nil)
|
||||
l := (rest.Storage)(nil)
|
||||
s := (rest.Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
@@ -249,10 +249,10 @@ func TestManagedMode3_CreateWhileMigrating(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := (rest.LegacyStorage)(nil)
|
||||
l := (rest.Storage)(nil)
|
||||
s := (rest.Storage)(nil)
|
||||
|
||||
ls := legacyStoreMock{&mock.Mock{}, l}
|
||||
ls := storageMock{&mock.Mock{}, l}
|
||||
us := storageMock{&mock.Mock{}, s}
|
||||
|
||||
if tt.setupLegacyFn != nil {
|
||||
|
||||
@@ -14,7 +14,7 @@ type staticService struct {
|
||||
cfg *setting.Cfg
|
||||
}
|
||||
|
||||
func (m *staticService) NewStorage(gr schema.GroupResource, legacy rest.LegacyStorage, storage rest.Storage) (rest.Storage, error) {
|
||||
func (m *staticService) NewStorage(gr schema.GroupResource, legacy rest.Storage, storage rest.Storage) (rest.Storage, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
|
||||
@@ -13,102 +13,11 @@ import (
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
)
|
||||
|
||||
type legacyStoreMock struct {
|
||||
*mock.Mock
|
||||
grafanarest.LegacyStorage
|
||||
}
|
||||
|
||||
type storageMock struct {
|
||||
*mock.Mock
|
||||
grafanarest.Storage
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
|
||||
args := m.Called(ctx, name, options)
|
||||
if err := args.Get(1); err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Error(1)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
|
||||
args := m.Called(ctx, obj, createValidation, options)
|
||||
if err := args.Get(1); err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Error(1)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
|
||||
args := m.Called(ctx, options)
|
||||
if err := args.Get(1); err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Error(1)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) NewList() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, false, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
args := m.Called(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options)
|
||||
if err := args.Get(2); err != nil {
|
||||
return nil, false, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, false, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
|
||||
args := m.Called(ctx, name, deleteValidation, options)
|
||||
if err := args.Get(2); err != nil {
|
||||
return nil, false, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Bool(1), args.Error(2)
|
||||
}
|
||||
|
||||
func (m legacyStoreMock) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, errors.New("context canceled")
|
||||
default:
|
||||
}
|
||||
args := m.Called(ctx, deleteValidation, options, listOptions)
|
||||
if err := args.Get(1); err != nil {
|
||||
return nil, err.(error)
|
||||
}
|
||||
return args.Get(0).(runtime.Object), args.Error(1)
|
||||
}
|
||||
|
||||
// Unified Store
|
||||
func (m storageMock) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
select {
|
||||
|
||||
@@ -36,7 +36,7 @@ type Service interface {
|
||||
ShouldManage(gr schema.GroupResource) bool
|
||||
|
||||
// Create a managed k8s storage instance
|
||||
NewStorage(gr schema.GroupResource, legacy grafanarest.LegacyStorage, storage grafanarest.Storage) (grafanarest.Storage, error)
|
||||
NewStorage(gr schema.GroupResource, legacy grafanarest.Storage, storage grafanarest.Storage) (grafanarest.Storage, error)
|
||||
|
||||
// Check if the dual writes is reading from unified storage (mode3++)
|
||||
ReadFromUnified(ctx context.Context, gr schema.GroupResource) (bool, error)
|
||||
|
||||
@@ -134,7 +134,7 @@ require (
|
||||
github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect
|
||||
github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elazarl/goproxy v1.7.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
@@ -183,7 +183,7 @@ require (
|
||||
github.com/google/cel-go v0.22.1 // indirect
|
||||
github.com/google/flatbuffers v24.3.25+incompatible // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
@@ -199,7 +199,7 @@ require (
|
||||
github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect
|
||||
github.com/grafana/grafana-aws-sdk v0.31.5 // indirect
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // indirect
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0 // indirect
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0 // indirect
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250220163425-b4c4b9abbdc8 // indirect
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.8 // indirect
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20250220164708-c8d4ff28a450 // indirect
|
||||
@@ -382,7 +382,7 @@ require (
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
|
||||
@@ -334,8 +334,8 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/elazarl/goproxy v1.7.0 h1:EXv2nV4EjM60ZtsEVLYJG4oBXhDGutMKperpHsZ/v+0=
|
||||
github.com/elazarl/goproxy v1.7.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/elazarl/goproxy v1.7.1 h1:1P7LPSxbqtNxusFnXclj6O56pjfq1xOQZ6a0mwwKUlY=
|
||||
github.com/elazarl/goproxy v1.7.1/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
@@ -515,8 +515,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
@@ -584,8 +585,8 @@ github.com/grafana/grafana-aws-sdk v0.31.5 h1:4HpMQx7n4Qqoi7Bgu8KHQ2QKT9fYYdHilX
|
||||
github.com/grafana/grafana-aws-sdk v0.31.5/go.mod h1:5p4Cjyr5ZiR6/RT2nFWkJ8XpIKgX4lAUmUMu70m2yCM=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0 h1:YP+iEpXH3HRX9Xo4NHjsrJhN2W7uVTtkLNzMHYbmiLI=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0/go.mod h1:bxkXrBQ4QSmOncsWdIOcpgP+M6wajQNMAPXlbWrqAWY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0 h1:4AcuLEE0UeFG0Eo3P8r1FDS89hgZZ73K550DRz1fgjQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q=
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250220163425-b4c4b9abbdc8 h1:9qOLpC21AmXZqZ6rUhrBWl2mVqS3CzV53pzw0BCuHt0=
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250220163425-b4c4b9abbdc8/go.mod h1:deLQ/ywLvpVGbncRGUA4UDGt8a5Ei9sivOP+x6AQ2ko=
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0=
|
||||
@@ -1552,8 +1553,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
|
||||
@@ -15,7 +15,7 @@ require (
|
||||
github.com/grafana/authlib/types v0.0.0-20250224151205-5ef97131cc82
|
||||
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040
|
||||
github.com/grafana/grafana v11.4.0-00010101000000-000000000000+incompatible
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250220154326-6e5de80ef295
|
||||
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250220154326-6e5de80ef295 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
gocloud.dev v0.40.0
|
||||
golang.org/x/sync v0.11.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
google.golang.org/protobuf v1.36.5
|
||||
k8s.io/apimachinery v0.32.1
|
||||
)
|
||||
|
||||
@@ -82,7 +82,7 @@ require (
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/elazarl/goproxy v1.7.0 // indirect
|
||||
github.com/elazarl/goproxy v1.7.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -110,7 +110,7 @@ require (
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/flatbuffers v24.3.25+incompatible // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/google/wire v0.6.0 // indirect
|
||||
|
||||
@@ -216,8 +216,8 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/elazarl/goproxy v1.7.0 h1:EXv2nV4EjM60ZtsEVLYJG4oBXhDGutMKperpHsZ/v+0=
|
||||
github.com/elazarl/goproxy v1.7.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/elazarl/goproxy v1.7.1 h1:1P7LPSxbqtNxusFnXclj6O56pjfq1xOQZ6a0mwwKUlY=
|
||||
github.com/elazarl/goproxy v1.7.1/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -361,8 +361,9 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo=
|
||||
@@ -413,8 +414,8 @@ github.com/grafana/grafana-aws-sdk v0.31.5 h1:4HpMQx7n4Qqoi7Bgu8KHQ2QKT9fYYdHilX
|
||||
github.com/grafana/grafana-aws-sdk v0.31.5/go.mod h1:5p4Cjyr5ZiR6/RT2nFWkJ8XpIKgX4lAUmUMu70m2yCM=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 h1:OfCkitCuomzZKW1WYHrG8MxKwtMhALb7jqoj+487eTg=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0 h1:YP+iEpXH3HRX9Xo4NHjsrJhN2W7uVTtkLNzMHYbmiLI=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.266.0/go.mod h1:bxkXrBQ4QSmOncsWdIOcpgP+M6wajQNMAPXlbWrqAWY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0 h1:4AcuLEE0UeFG0Eo3P8r1FDS89hgZZ73K550DRz1fgjQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q=
|
||||
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
|
||||
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
|
||||
@@ -1021,8 +1022,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@@ -97,178 +99,315 @@ var (
|
||||
)
|
||||
|
||||
func TestIntegrationConvertPrometheusEndpoints(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
runTest := func(t *testing.T, enableLokiPaths bool) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{"alertingConversionAPI"},
|
||||
// Setup Grafana and its Database
|
||||
dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{"alertingConversionAPI"},
|
||||
})
|
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath)
|
||||
|
||||
// Create users to make authenticated requests
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleAdmin),
|
||||
Password: "password",
|
||||
Login: "admin",
|
||||
})
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
||||
apiClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
||||
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleViewer),
|
||||
Password: "password",
|
||||
Login: "viewer",
|
||||
})
|
||||
viewerClient := newAlertingApiClient(grafanaListedAddr, "viewer", "password")
|
||||
|
||||
namespace1 := "test-namespace-1"
|
||||
namespace2 := "test-namespace-2"
|
||||
|
||||
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
||||
|
||||
t.Run("create rule groups and get them back", func(t *testing.T) {
|
||||
_, status, body := apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
_, status, body = apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup2, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
|
||||
// create a third group in a different namespace
|
||||
_, status, body = apiClient.ConvertPrometheusPostRuleGroup(t, namespace2, ds.Body.Datasource.UID, promGroup3, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
|
||||
// And a non-provisioned rule in another namespace
|
||||
namespace3UID := util.GenerateShortUID()
|
||||
apiClient.CreateFolder(t, namespace3UID, "folder")
|
||||
createRule(t, apiClient, namespace3UID)
|
||||
|
||||
// Now get the first group
|
||||
group1 := apiClient.ConvertPrometheusGetRuleGroupRules(t, namespace1, promGroup1.Name)
|
||||
require.Equal(t, promGroup1, group1)
|
||||
|
||||
// Get namespace1
|
||||
ns1 := apiClient.ConvertPrometheusGetNamespaceRules(t, namespace1)
|
||||
expectedNs1 := map[string][]apimodels.PrometheusRuleGroup{
|
||||
namespace1: {promGroup1, promGroup2},
|
||||
}
|
||||
require.Equal(t, expectedNs1, ns1)
|
||||
|
||||
// Get all namespaces
|
||||
namespaces := apiClient.ConvertPrometheusGetAllRules(t)
|
||||
expectedNamespaces := map[string][]apimodels.PrometheusRuleGroup{
|
||||
namespace1: {promGroup1, promGroup2},
|
||||
namespace2: {promGroup3},
|
||||
}
|
||||
require.Equal(t, expectedNamespaces, namespaces)
|
||||
})
|
||||
|
||||
t.Run("without permissions to create folders cannot create rule groups either", func(t *testing.T) {
|
||||
_, status, raw := viewerClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
||||
requireStatusCode(t, http.StatusForbidden, status, raw)
|
||||
})
|
||||
|
||||
t.Run("delete one rule group", func(t *testing.T) {
|
||||
_, status, body := apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
_, status, body = apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup2, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
_, status, body = apiClient.ConvertPrometheusPostRuleGroup(t, namespace2, ds.Body.Datasource.UID, promGroup3, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
|
||||
apiClient.ConvertPrometheusDeleteRuleGroup(t, namespace1, promGroup1.Name)
|
||||
|
||||
// Check that the promGroup2 and promGroup3 are still there
|
||||
namespaces := apiClient.ConvertPrometheusGetAllRules(t)
|
||||
expectedNamespaces := map[string][]apimodels.PrometheusRuleGroup{
|
||||
namespace1: {promGroup2},
|
||||
namespace2: {promGroup3},
|
||||
}
|
||||
require.Equal(t, expectedNamespaces, namespaces)
|
||||
|
||||
// Delete the second namespace
|
||||
apiClient.ConvertPrometheusDeleteNamespace(t, namespace2)
|
||||
|
||||
// Check that only the first namespace is left
|
||||
namespaces = apiClient.ConvertPrometheusGetAllRules(t)
|
||||
expectedNamespaces = map[string][]apimodels.PrometheusRuleGroup{
|
||||
namespace1: {promGroup2},
|
||||
}
|
||||
require.Equal(t, expectedNamespaces, namespaces)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("with the mimirtool paths", func(t *testing.T) {
|
||||
runTest(t, false)
|
||||
})
|
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
||||
|
||||
// Create users to make authenticated requests
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleAdmin),
|
||||
Password: "password",
|
||||
Login: "admin",
|
||||
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
||||
runTest(t, true)
|
||||
})
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
||||
}
|
||||
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleViewer),
|
||||
Password: "password",
|
||||
Login: "viewer",
|
||||
})
|
||||
viewerClient := newAlertingApiClient(grafanaListedAddr, "viewer", "password")
|
||||
func TestIntegrationConvertPrometheusEndpoints_Conflict(t *testing.T) {
|
||||
runTest := func(t *testing.T, enableLokiPaths bool) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
namespace1 := "test-namespace-1"
|
||||
namespace2 := "test-namespace-2"
|
||||
// Setup Grafana and its Database
|
||||
dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{"alertingConversionAPI"},
|
||||
})
|
||||
|
||||
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath)
|
||||
|
||||
t.Run("create rule groups and get them back", func(t *testing.T) {
|
||||
_, status, body := apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
_, status, body = apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup2, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
// Create users to make authenticated requests
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleAdmin),
|
||||
Password: "password",
|
||||
Login: "admin",
|
||||
})
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
||||
apiClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
||||
|
||||
// create a third group in a different namespace
|
||||
_, status, body = apiClient.ConvertPrometheusPostRuleGroup(t, namespace2, ds.Body.Datasource.UID, promGroup3, nil)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleViewer),
|
||||
Password: "password",
|
||||
Login: "viewer",
|
||||
})
|
||||
|
||||
// And a non-provisioned rule in another namespace
|
||||
namespace3UID := util.GenerateShortUID()
|
||||
apiClient.CreateFolder(t, namespace3UID, "folder")
|
||||
createRule(t, apiClient, namespace3UID)
|
||||
namespace1 := "test-namespace-1"
|
||||
|
||||
// Now get the first group
|
||||
group1 := apiClient.ConvertPrometheusGetRuleGroupRules(t, namespace1, promGroup1.Name)
|
||||
require.Equal(t, promGroup1, group1)
|
||||
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
||||
|
||||
// Get namespace1
|
||||
ns1 := apiClient.ConvertPrometheusGetNamespaceRules(t, namespace1)
|
||||
expectedNs1 := map[string][]apimodels.PrometheusRuleGroup{
|
||||
namespace1: {promGroup1, promGroup2},
|
||||
}
|
||||
require.Equal(t, expectedNs1, ns1)
|
||||
t.Run("cannot overwrite a rule group with different provenance", func(t *testing.T) {
|
||||
// Create a rule group using the provisioning API and then try to overwrite it
|
||||
// using the Prometheus Conversion API. It should fail because the provenance
|
||||
// we set for rules in these two APIs is different and we check that when updating.
|
||||
provisionedRuleGroup := apimodels.AlertRuleGroup{
|
||||
Title: promGroup1.Name,
|
||||
Interval: 60,
|
||||
FolderUID: namespace1,
|
||||
Rules: []apimodels.ProvisionedAlertRule{
|
||||
{
|
||||
Title: "Rule1",
|
||||
OrgID: 1,
|
||||
RuleGroup: promGroup1.Name,
|
||||
Condition: "A",
|
||||
NoDataState: apimodels.Alerting,
|
||||
ExecErrState: apimodels.AlertingErrState,
|
||||
For: prommodel.Duration(time.Duration(60) * time.Second),
|
||||
Data: []apimodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: apimodels.RelativeTimeRange{
|
||||
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
||||
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Get all namespaces
|
||||
namespaces := apiClient.ConvertPrometheusGetAllRules(t)
|
||||
expectedNamespaces := map[string][]apimodels.PrometheusRuleGroup{
|
||||
namespace1: {promGroup1, promGroup2},
|
||||
namespace2: {promGroup3},
|
||||
}
|
||||
require.Equal(t, expectedNamespaces, namespaces)
|
||||
// Create the folder
|
||||
apiClient.CreateFolder(t, namespace1, namespace1)
|
||||
// Create rule in the root folder using another API
|
||||
_, status, response := apiClient.CreateOrUpdateRuleGroupProvisioning(t, provisionedRuleGroup)
|
||||
require.Equalf(t, http.StatusOK, status, response)
|
||||
|
||||
// Should fail to post the group
|
||||
_, status, body := apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
||||
requireStatusCode(t, http.StatusConflict, status, body)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("with the mimirtool paths", func(t *testing.T) {
|
||||
runTest(t, false)
|
||||
})
|
||||
|
||||
t.Run("without permissions to create folders cannot create rule groups either", func(t *testing.T) {
|
||||
_, status, raw := viewerClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
||||
requireStatusCode(t, http.StatusForbidden, status, raw)
|
||||
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
||||
runTest(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationConvertPrometheusEndpoints_CreatePausedRules(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
runTest := func(t *testing.T, enableLokiPaths bool) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{"alertingConversionAPI"},
|
||||
})
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
EnableFeatureToggles: []string{"alertingConversionAPI"},
|
||||
})
|
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
||||
|
||||
// Create users to make authenticated requests
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleAdmin),
|
||||
Password: "password",
|
||||
Login: "admin",
|
||||
})
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
||||
// Create users to make authenticated requests
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleAdmin),
|
||||
Password: "password",
|
||||
Login: "admin",
|
||||
})
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
||||
|
||||
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
||||
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
||||
|
||||
namespace1 := "test-namespace-1"
|
||||
namespace1 := "test-namespace-1"
|
||||
|
||||
namespace1UID := util.GenerateShortUID()
|
||||
apiClient.CreateFolder(t, namespace1UID, namespace1)
|
||||
namespace1UID := util.GenerateShortUID()
|
||||
apiClient.CreateFolder(t, namespace1UID, namespace1)
|
||||
|
||||
t.Run("when pausing header is set, rules should be paused", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
recordingPaused bool
|
||||
alertPaused bool
|
||||
}{
|
||||
{
|
||||
name: "do not pause rules",
|
||||
recordingPaused: false,
|
||||
alertPaused: false,
|
||||
},
|
||||
{
|
||||
name: "pause recording rules",
|
||||
recordingPaused: true,
|
||||
alertPaused: false,
|
||||
},
|
||||
{
|
||||
name: "pause alert rules",
|
||||
recordingPaused: false,
|
||||
alertPaused: true,
|
||||
},
|
||||
{
|
||||
name: "pause both recording and alert rules",
|
||||
recordingPaused: true,
|
||||
alertPaused: true,
|
||||
},
|
||||
}
|
||||
t.Run("when pausing header is set, rules should be paused", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
recordingPaused bool
|
||||
alertPaused bool
|
||||
}{
|
||||
{
|
||||
name: "do not pause rules",
|
||||
recordingPaused: false,
|
||||
alertPaused: false,
|
||||
},
|
||||
{
|
||||
name: "pause recording rules",
|
||||
recordingPaused: true,
|
||||
alertPaused: false,
|
||||
},
|
||||
{
|
||||
name: "pause alert rules",
|
||||
recordingPaused: false,
|
||||
alertPaused: true,
|
||||
},
|
||||
{
|
||||
name: "pause both recording and alert rules",
|
||||
recordingPaused: true,
|
||||
alertPaused: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
headers := map[string]string{}
|
||||
if tc.recordingPaused {
|
||||
headers["X-Grafana-Alerting-Recording-Rules-Paused"] = "true"
|
||||
}
|
||||
if tc.alertPaused {
|
||||
headers["X-Grafana-Alerting-Alert-Rules-Paused"] = "true"
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
headers := map[string]string{}
|
||||
if tc.recordingPaused {
|
||||
headers["X-Grafana-Alerting-Recording-Rules-Paused"] = "true"
|
||||
}
|
||||
if tc.alertPaused {
|
||||
headers["X-Grafana-Alerting-Alert-Rules-Paused"] = "true"
|
||||
}
|
||||
|
||||
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, headers)
|
||||
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, headers)
|
||||
|
||||
gr, _, _ := apiClient.GetRulesGroupWithStatus(t, namespace1UID, promGroup1.Name)
|
||||
gr, _, _ := apiClient.GetRulesGroupWithStatus(t, namespace1UID, promGroup1.Name)
|
||||
|
||||
require.Len(t, gr.Rules, 3)
|
||||
require.Len(t, gr.Rules, 3)
|
||||
|
||||
pausedRecordingRules := 0
|
||||
pausedAlertRules := 0
|
||||
pausedRecordingRules := 0
|
||||
pausedAlertRules := 0
|
||||
|
||||
for _, rule := range gr.Rules {
|
||||
if rule.GrafanaManagedAlert.IsPaused {
|
||||
if rule.GrafanaManagedAlert.Record != nil {
|
||||
pausedRecordingRules++
|
||||
} else {
|
||||
pausedAlertRules++
|
||||
for _, rule := range gr.Rules {
|
||||
if rule.GrafanaManagedAlert.IsPaused {
|
||||
if rule.GrafanaManagedAlert.Record != nil {
|
||||
pausedRecordingRules++
|
||||
} else {
|
||||
pausedAlertRules++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tc.recordingPaused {
|
||||
require.Equal(t, 1, pausedRecordingRules)
|
||||
} else {
|
||||
require.Equal(t, 0, pausedRecordingRules)
|
||||
}
|
||||
if tc.recordingPaused {
|
||||
require.Equal(t, 1, pausedRecordingRules)
|
||||
} else {
|
||||
require.Equal(t, 0, pausedRecordingRules)
|
||||
}
|
||||
|
||||
if tc.alertPaused {
|
||||
require.Equal(t, 2, pausedAlertRules)
|
||||
} else {
|
||||
require.Equal(t, 0, pausedAlertRules)
|
||||
}
|
||||
})
|
||||
}
|
||||
if tc.alertPaused {
|
||||
require.Equal(t, 2, pausedAlertRules)
|
||||
} else {
|
||||
require.Equal(t, 0, pausedAlertRules)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("with the mimirtool paths", func(t *testing.T) {
|
||||
runTest(t, false)
|
||||
})
|
||||
|
||||
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
||||
runTest(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -254,7 +254,8 @@ func convertGettableGrafanaRuleToPostable(gettable *apimodels.GettableGrafanaRul
|
||||
}
|
||||
|
||||
type apiClient struct {
|
||||
url string
|
||||
url string
|
||||
prometheusConversionUseLokiPaths bool
|
||||
}
|
||||
|
||||
type LegacyApiClient struct {
|
||||
@@ -1121,7 +1122,13 @@ func (a apiClient) ConvertPrometheusPostRuleGroup(t *testing.T, namespaceTitle,
|
||||
|
||||
func (a apiClient) ConvertPrometheusGetRuleGroupRules(t *testing.T, namespaceTitle, groupName string) apimodels.PrometheusRuleGroup {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/convert/prometheus/config/v1/rules/%s/%s", a.url, namespaceTitle, groupName), nil)
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s/%s"
|
||||
if a.prometheusConversionUseLokiPaths {
|
||||
path = "%s/api/convert/api/prom/rules/%s/%s"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(path, a.url, namespaceTitle, groupName), nil)
|
||||
require.NoError(t, err)
|
||||
rule, status, raw := sendRequestYAML[apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
@@ -1130,7 +1137,13 @@ func (a apiClient) ConvertPrometheusGetRuleGroupRules(t *testing.T, namespaceTit
|
||||
|
||||
func (a apiClient) ConvertPrometheusGetNamespaceRules(t *testing.T, namespaceTitle string) map[string][]apimodels.PrometheusRuleGroup {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/convert/prometheus/config/v1/rules/%s", a.url, namespaceTitle), nil)
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s"
|
||||
if a.prometheusConversionUseLokiPaths {
|
||||
path = "%s/api/convert/api/prom/rules/%s"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(path, a.url, namespaceTitle), nil)
|
||||
require.NoError(t, err)
|
||||
ns, status, raw := sendRequestYAML[map[string][]apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
@@ -1139,13 +1152,47 @@ func (a apiClient) ConvertPrometheusGetNamespaceRules(t *testing.T, namespaceTit
|
||||
|
||||
func (a apiClient) ConvertPrometheusGetAllRules(t *testing.T) map[string][]apimodels.PrometheusRuleGroup {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/convert/prometheus/config/v1/rules", a.url), nil)
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules"
|
||||
if a.prometheusConversionUseLokiPaths {
|
||||
path = "%s/api/convert/api/prom/rules"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(path, a.url), nil)
|
||||
require.NoError(t, err)
|
||||
result, status, raw := sendRequestYAML[map[string][]apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
return result
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusDeleteRuleGroup(t *testing.T, namespaceTitle, groupName string) {
|
||||
t.Helper()
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s/%s"
|
||||
if a.prometheusConversionUseLokiPaths {
|
||||
path = "%s/api/convert/api/prom/rules/%s/%s"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf(path, a.url, namespaceTitle, groupName), nil)
|
||||
require.NoError(t, err)
|
||||
_, status, raw := sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
|
||||
requireStatusCode(t, http.StatusAccepted, status, raw)
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusDeleteNamespace(t *testing.T, namespaceTitle string) {
|
||||
t.Helper()
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s"
|
||||
if a.prometheusConversionUseLokiPaths {
|
||||
path = "%s/api/convert/api/prom/rules/%s"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf(path, a.url, namespaceTitle), nil)
|
||||
require.NoError(t, err)
|
||||
_, status, raw := sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
|
||||
requireStatusCode(t, http.StatusAccepted, status, raw)
|
||||
}
|
||||
|
||||
func sendRequestRaw(t *testing.T, req *http.Request) ([]byte, int, error) {
|
||||
t.Helper()
|
||||
client := &http.Client{}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Props } from 'react-virtualized-auto-sizer';
|
||||
import { render, screen, waitFor, within } from 'test/test-utils';
|
||||
import { byLabelText, byRole } from 'testing-library-selector';
|
||||
|
||||
import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types';
|
||||
import { CodeEditor } from '@grafana/ui';
|
||||
import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList';
|
||||
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
||||
import { testWithFeatureToggles } from 'app/features/alerting/unified/test/test-utils';
|
||||
@@ -14,6 +14,8 @@ import { AccessControlAction } from 'app/types';
|
||||
import Templates from './Templates';
|
||||
import { grantUserPermissions } from './mocks';
|
||||
|
||||
type CodeEditorProps = React.ComponentProps<typeof CodeEditor>;
|
||||
|
||||
jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({
|
||||
AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) => <div>{actions}</div>,
|
||||
}));
|
||||
|
||||
+2
-2
@@ -4,10 +4,10 @@ import { PluginExtensionLink, PluginExtensionPoints } from '@grafana/data';
|
||||
import { usePluginLinks } from '@grafana/runtime';
|
||||
import { Dropdown, IconButton } from '@grafana/ui';
|
||||
import { ConfirmNavigationModal } from 'app/features/explore/extensions/ConfirmNavigationModal';
|
||||
// We might want to customise this in future but right now the toolbar menu from the Explore view is fine.
|
||||
import { ToolbarExtensionPointMenu as AlertExtensionPointMenu } from 'app/features/explore/extensions/ToolbarExtensionPointMenu';
|
||||
import { Alert, CombinedRule } from 'app/types/unified-alerting';
|
||||
|
||||
import { AlertExtensionPointMenu } from './AlertInstanceExtensionPointMenu';
|
||||
|
||||
interface AlertInstanceExtensionPointProps {
|
||||
rule?: CombinedRule;
|
||||
instance: Alert;
|
||||
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
// We might want to customise this in future but right now the toolbar menu from the Explore view is fine.
|
||||
export { ToolbarExtensionPointMenu as AlertExtensionPointMenu } from 'app/features/explore/extensions/ToolbarExtensionPointMenu';
|
||||
@@ -4,16 +4,15 @@
|
||||
* It includes auto-complete for template data and syntax highlighting
|
||||
*/
|
||||
import { IDisposable, editor } from 'monaco-editor';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { ComponentProps, useEffect, useRef } from 'react';
|
||||
|
||||
import { CodeEditor } from '@grafana/ui';
|
||||
import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types';
|
||||
|
||||
import { registerGoTemplateAutocomplete } from './editor/autocomplete';
|
||||
import goTemplateLanguageDefinition, { GO_TEMPLATE_LANGUAGE_ID } from './editor/definition';
|
||||
import { registerLanguage } from './editor/register';
|
||||
|
||||
type TemplateEditorProps = Omit<CodeEditorProps, 'language' | 'theme'> & {
|
||||
type TemplateEditorProps = Omit<ComponentProps<typeof CodeEditor>, 'language' | 'theme'> & {
|
||||
autoHeight?: boolean;
|
||||
};
|
||||
|
||||
|
||||
+52
-25
@@ -1,8 +1,9 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, Input, useStyles2 } from '@grafana/ui';
|
||||
import { Button, Input, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
|
||||
import { ActionIcon } from '../../../rules/ActionIcon';
|
||||
|
||||
@@ -15,7 +16,7 @@ interface Props {
|
||||
export const KeyValueMapInput = ({ value, onChange, readOnly = false }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const [pairs, setPairs] = useState(recordToPairs(value));
|
||||
useEffect(() => setPairs(recordToPairs(value)), [value]);
|
||||
const [currentNewPair, setCurrentNewPair] = useState<[string, string] | undefined>(undefined);
|
||||
|
||||
const emitChange = (pairs: Array<[string, string]>) => {
|
||||
onChange(pairsToRecord(pairs));
|
||||
@@ -30,15 +31,6 @@ export const KeyValueMapInput = ({ value, onChange, readOnly = false }: Props) =
|
||||
}
|
||||
};
|
||||
|
||||
const updatePair = (values: [string, string], index: number) => {
|
||||
const old = pairs[index];
|
||||
const newPairs = pairs.map((pair, i) => (i === index ? values : pair));
|
||||
setPairs(newPairs);
|
||||
if (values[0] || old[0]) {
|
||||
emitChange(newPairs);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!!pairs.length && (
|
||||
@@ -54,22 +46,18 @@ export const KeyValueMapInput = ({ value, onChange, readOnly = false }: Props) =
|
||||
{pairs.map(([key, value], index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<Input
|
||||
readOnly={readOnly}
|
||||
value={key}
|
||||
onChange={(e) => updatePair([e.currentTarget.value, value], index)}
|
||||
/>
|
||||
<Input readOnly={readOnly} value={key} disabled />
|
||||
</td>
|
||||
<td>
|
||||
<Input
|
||||
readOnly={readOnly}
|
||||
value={value}
|
||||
onChange={(e) => updatePair([key, e.currentTarget.value], index)}
|
||||
/>
|
||||
<Input readOnly={readOnly} value={value} disabled />
|
||||
</td>
|
||||
{!readOnly && (
|
||||
<td>
|
||||
<ActionIcon icon="trash-alt" tooltip="delete" onClick={() => deleteItem(index)} />
|
||||
<ActionIcon
|
||||
icon="trash-alt"
|
||||
tooltip={t('alerting.common.delete', 'Delete')}
|
||||
onClick={() => deleteItem(index)}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
@@ -77,6 +65,44 @@ export const KeyValueMapInput = ({ value, onChange, readOnly = false }: Props) =
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
{currentNewPair && (
|
||||
<table className={styles.table}>
|
||||
<tr>
|
||||
<Stack gap={1}>
|
||||
<td>
|
||||
<Input
|
||||
value={currentNewPair[0]}
|
||||
onChange={(e) => setCurrentNewPair([e.currentTarget.value, currentNewPair[1]])}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Input
|
||||
value={currentNewPair[1]}
|
||||
onChange={(e) => setCurrentNewPair([currentNewPair[0], e.currentTarget.value])}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Stack gap={1}>
|
||||
<ActionIcon
|
||||
icon="check"
|
||||
tooltip={t('alerting.contact-points.key-value-map.confirm-add', 'Confirm to add')}
|
||||
onClick={() => {
|
||||
setPairs([...pairs, currentNewPair]);
|
||||
setCurrentNewPair(undefined);
|
||||
emitChange([...pairs, currentNewPair]);
|
||||
}}
|
||||
/>
|
||||
<ActionIcon
|
||||
icon="times"
|
||||
tooltip={t('alerting.common.cancel', 'Cancel')}
|
||||
onClick={() => setCurrentNewPair(undefined)}
|
||||
/>
|
||||
</Stack>
|
||||
</td>
|
||||
</Stack>
|
||||
</tr>
|
||||
</table>
|
||||
)}
|
||||
{!readOnly && (
|
||||
<Button
|
||||
className={styles.addButton}
|
||||
@@ -84,9 +110,10 @@ export const KeyValueMapInput = ({ value, onChange, readOnly = false }: Props) =
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
size="sm"
|
||||
onClick={() => setPairs([...pairs, ['', '']])}
|
||||
disabled={!!currentNewPair}
|
||||
onClick={() => setCurrentNewPair(['', ''])}
|
||||
>
|
||||
Add
|
||||
<Trans i18nKey="alerting.contact-points.key-value-map.add">Add</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { ComponentProps, ReactNode } from 'react';
|
||||
import { render, screen, userEvent } from 'test/test-utils';
|
||||
|
||||
import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types';
|
||||
import { CodeEditor } from '@grafana/ui';
|
||||
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
||||
import { grantUserPermissions } from 'app/features/alerting/unified/mocks';
|
||||
import { getAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
|
||||
@@ -15,6 +15,8 @@ import { AccessControlAction, NotificationChannelOption } from 'app/types';
|
||||
import { TemplatesPicker, getTemplateOptions } from './TemplateSelector';
|
||||
import { parseTemplates } from './utils';
|
||||
|
||||
type CodeEditorProps = ComponentProps<typeof CodeEditor>;
|
||||
|
||||
const alertmanagerConfigMock = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
|
||||
|
||||
jest.mock('@grafana/ui', () => ({
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ export const getPayloadToExport = (
|
||||
} else {
|
||||
// we have to create a new group with the updated rule
|
||||
return {
|
||||
name: existingGroup?.name ?? '',
|
||||
name: existingGroup?.name ?? formValues.group,
|
||||
rules: [updatedRule],
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user